http://erlang.org/download/
w konsoli elrang:
wyświetlenie bierzącego katalogu:
>pwd().
zmiana katalogu:
>cd("/erl").
wyswietlenie plików
>ls().
budowa pliku tt.erl
-module(tt). -export([start/1]). %komentarz start(X) -> io:format("KUKU!"), X*X .
kompilacja pliku tt.erl (powstaje plik tt.beam)
>c(tt).
uruchomienie wyeksportowanej funkcji ze skompilowanego tt.bean
>tt:start(5).
zwróci:
witaj!25
List comprehensions
budowa Lisy Comprehensions:
[EXPR||GENERATOR,FILTR]
generator rozpoznajemy po <-
np:
[2*N || N<-[1,2,3,4,5,6,7,8,9], N<5]
wynik:
[2,4,6,8]
filtry mogą być podwójne, generatory też
Zadanie1:
Using a list comprehension, write a function which generates a list of positive integers {A,B,C} such that A2 + B2 = C2 and A+B+C is less than or equal to N.
tworzymy listę:
> L=lists:seq(1,30).
> [ {A,B,C}|| A<-L , B<-L , C<-L , A*A+B*B==C*C ,A+B+C<30 ]
odpowiednikiem printf jest io:format, ale zamiast \d stosujemy ~d itp, tu element listy i nowa linia
io:format( "element: ~w~n" , [N] )
(albo io:fwrite)
Lista
Listy mogę składać się z różnych elementów różnego typu np int, krotka, float, atom:
[1,2,3, {numbers , [4,5,6] } , 5.34 , atom ].
pusta lista
[].
głowa i ogon:
[HD | ogon]
np
[1 | [2 | [3]]].
czyli dokleja głowe HD na początek
możemy sklejać ++ i rozklejać --
[1,2,3]++4,5]
[1,2,3,4,5]
oraz porównywać ==: == znaczy równe a =:= identyczne
[1,2,3] == [1,2,3]
true
ale:
[1,2,3] ==[2,1,3]
false
więc:
[1,2,3]--[2,1,3]==[]
true
Krotki mają stałą ilość elementów, nie można doklejać, żeby obrabiać krotki trzeba znać rozmiar ale jest dostęp do elementów za pomocą pozycji.
Pattern Matching
(dopasowanie wzorców)
Variable=12.23
dekompozycja danych:
{Key, Val} = {1, {sth,10}}
i przypisze
_ podłoga zastępuje dowolny znak, jest jak VOID, dlatego
{_,_} = {4,5}
zwróci:
{4,5}
ilość argumentów musi być jednak taka sama!
[A | B ] = [1,2,3]
wynik:
A =1
B=[2,3]
f() - kasuje w konsoli wszystkie zmienne
b() - wyswietla zmienne
abc=A sprawdza czy w A jest atom abc i jesli jest zwraca true
ale
A=abc przypisuje do A atom abc (jesli wczesniej nic nie było przypisane)
Pętle
można w Erlangu zrobić za pomocą list comprthension albo za pomocą recursionwydrukuj numery od X do 0:
loop(0)->done;
loop(X)->io:format("~w~n",[X]) , loop(X-1).
i za pomocą rekurencji wywoła loop od X do 0
Odczyt danych z klawiatury
{ok,[N]}=io:fread("Podaj liczbe:","~d")
funkcja zwraca krotkę {ok,podana liczba} dlatego przypisujemy do takiej krotki
FUNKCJE
w pliku można enkapsulować, tak by funkcja bez parametru wywoływała taką z parametrem-module(tt2).
-export([wczytuj/0]).
wczytuj() -> wczytuj([1,2])
.
wczytuj(L)->
io:format("~w",[L])
.
i teraz samo uruchomienie (po kompilacji)
tt2.wczytuj()
zwróci:
{1,2}ok.
W pliku funkcja kończy się dopiero w miejscu kropki, linie oddzielone ; (średnik czyli lub) wykonywane są po kolei, przecinek symbolizuje AND. Kropka wykonaj i zwróć wartość.
rekurencja:
-module(krok1).
-export([loop/1]).
loop(0)->koniec;
loop(N)->
io:format("It: ~w~n",[N]),
loop(N-1)
.
zwróci:
krok1:loop(10).
It: 10
It: 9
It: 8
It: 7
It: 6
It: 5
It: 4
It: 3
It: 2
It: 1
koniec
Zadanie2:
Wczytuj liczby i dopisuj do listy az trafisz na liczbę 42 (przyklad cese of)
-module(krok1). -export([wczytuj/0]). wczytuj() -> wczytuj([]) . wczytuj(L)-> {ok,[N]}=io:fread("Podaj liczbe:","~d"), case N of 42 -> lists:reverse(L); Cokolwiek ->io:format("~w",[Cokolwiek]), wczytuj([Cokolwiek | L]) end .
Conditionals case + if
Funkcja która bierze liste i wylicz z niej srednia:
case of
avarage (L) -> avarage (L, 0, 0). avarage (L, Sum, Len) -> case L of [H | T] -> avarage (T, Sum + H, Len + 1); [] -> Sum / Len end.
Enkapsulujemy tak nasza pierwotna funkcję z Listą, że dostaje 2 dodatkowe argumenty.
Nasze case of sprawdza Listę L. Jeśli da się ją rozbić na głowę H i ogon T wywołuje rekurencyjnie samą siebie z ogonem, i argumentami w postaci Sumy i długości.
; - oznacza lub
lub jeśli lista jest pusta, czyli ostatni element rekurencji, zwraca Sumę/Dlugosc, czyli srednia.
Case działa tak, że musi pasować choć jeden wybór, inaczej zwróci błąd.
if
avarage (L) -> avarage (L, 0, 0). avarage (L, Sum, Len) -> if L== [] -> Sum / Len; true -> [H | T] = L avarage (T, Sum + H, Len + 1) end.
Nasze if najpierw sprawdza czy lista pusta (wtedy zwróci średnią) a jeśli nie, przechodzi do warunku true i wykonuje podział na głowę i ogon oraz rekurencję.
Jeśli pierwszy warunek nie jest spełniony na pewno wykona drugi (nie sprawdza drugiego dopasowania)
Guard
Dodatkowe warunki, jeśli oddzielone przecinkami AND jeśli średnikami OR.
when rozpoczyna guard
Przykład silni z guardami:
fact(0) -> 1; fact(N) when N > 0 , is_integer(N) -> N * fact(N-1).
wyświetl wszystkie numery od X do Y:
print_interval(X,Y) when X > Y -> done; print_interval(X, Y) -> io:format("~p~n", [X]), print_interval(X+1, Y).
zamiast przecinka i średnika można stosować andalso orelse
różnica jest taka, że średnik i przecinek nie reagują na błędy, więc jeśli w pierwszym guardzie będzie błąd przejdzie do drugiego. W Przypadku andalso i orelse po błędzie nie sprawdzi drugiego warunku.
Za to andalso i orelse mogą być zagniueżdżane w środku guardów (w nawiasach)
Input / Output
Zad3
Input (STDIN):The first line contains T, the number of test cases. The next T lines contain a string each.
Output (STDOUT):
For each test case, output "YES" if the string is palindrome, and "NO" otherwise.
Sample input:
2
aba
ab
Sample output:
YES
NO
-module(zad3). -export([uruchom/0]). uruchom() -> {ok,[N]}=io:fread("Podaj ilosc","~d"), sprawdzaj(N,[]). sprawdzaj(0,WW)-> wynik(WW); sprawdzaj(X,WW) -> {ok,[L]}=io:fread("Podaj wyraz: ","~s"), L1=lists:reverse(L), if L1==L -> W1= ["YES\n" | WW]; true -> W1=["NO\n" | WW] end, sprawdzaj(X-1,W1). wynik(W) -> W2=lists:reverse(W), io:format(W2).
Funkcje anonimowe
Mają dostęp do zmiennych z okolic z których zostały wywołane.
F=fun(X) -> X +1 end.
i wtedy F(5).
zwróci
6
F2=fun(0) -> err; (X) -> 1/X end.
i wtedy F2(wartość) - najpierw sprawdzi czy wartosc nie jest 0 i jeśli nie zwróci 1/wartość.
można obudować funkcję biblioteczną
F3=fun(X) -> io:format("wyswietl~d",[X]) end.
Przykład podobny do FOREACH czyli jak wywołać funkcję na każdym elemencie Listy.
map wywołuje funkcję na HEAD i siebie na TAIL czyli rekurencyjnie aż lista pusta.
-module(funsy). -export([map/2]). map(Fun , [Head | Tail ]) -> [Fun(Head) | map(Fun, Tail)]; map(Fun, [] ) -> [].
i wywołujemy po kompilacji
> funsy:map(fun(X) -> X+1 end , [1,2,3]).
tym sposobem funkcja fun(X) zostanie wywołana na każdym elemencie listy
Named Funs(od wersji 17 erl)
nazwana po to by potrafiła wywołać samą siebie. Anonimowe, ale nazwane jednak nazwa dostępna tylko w zakresie funkcji. fun Iter funkcja nazwana:>Map = fun Iter(Fun, [Head | Tail] ) -> [Fun(Head) | Iter(Fun,Tail)]; Iter(Fun,[])-> [] end.
i można wywołać:
> Map(fun(X) -> X+1 end, [1,2,3]).
Funkcje wyższego rzędu (funkcje zwracające inne funkcję)
Multiply = fun(Times) -> ( fun(X) -> X *Times end ) end.
i teraz:
Triple = Multiply(3).
Triple(5).
15
Records #
Nazwane krotki, Pola w rekordach są dostępne po nazwach.
Definiowanie:
-record (person, {name, location=home}).
rd(person, {name, location = home}).
Użycie:
Person = #person{name="Smith"},
PersonName = Person#person.name,
PersonLocation = Person#person.location.
przykłady instancji (różna ilość pól może być podana podczas tworzenia instancji)
P1 = #person{}.
P2 = #person{name = "Chuck"}.
P3 = #person{name = "Chuck", location = usa}.
P4 = #person{name = "Chuck", _ = unknown}.
odczyt danych:
Name = P2#person.name
kopiowanie i update danych:
P6 = P1#person{name = "JoeAmstrong"}. tu przypisze do P6 jak było poza name
nietypowa kolejność przy =
#person{location = L} = P3 tu odczyta z P3 i wpisze w L !!!
odczyt i uzycie w funkcji:
get_name( #person{name = N} ) -> N
Zad4:
Purpose of this exercise is to get familiar with:
- records
- processing lists in natural order
Write a small module for managing a store. Module should have two functions: one
for adding and one for removing merchandise from the store.
Merchandise shall be represented with Erlang records having following attributes:
name of the merchandise, price and its current quantity in the store.
A store is a list of merchandise records.
Sample operations on the store:
1> Apples = #merchandise{name=apple, price=2.49, quantity=100}.
#merchandise{name = apple,price = 2.49,quantity = 100}
2> Bananas = #merchandise{name=banana, price=3.99, quantity=50}.
#merchandise{name = banana,price = 3.99,quantity = 50}
3> Oranges = #merchandise{name=orange, price=4.05, quantity=200}.
#merchandise{name = orange,price = 4.05,quantity = 200}
4> Store1 = store:add(Apples,[]).
[#merchandise{name = apple,price = 2.49,quantity = 100}]
5> Store2 = store:add(Bananas,Store1).
[#merchandise{name = banana,price = 3.99,quantity = 50},
#merchandise{name = apple,price = 2.49,quantity = 100}]
-module(zad4). -export([add/2]). -record(sklep,{name,price,quantity}). add(WH,LL) -> El=[C || C<- LL, C#sklep.name == WH#sklep.name], if El == [] -> [WH|LL]; true-> [EE]=El, NR=WH#sklep.quantity+EE#sklep.quantity, WH2=WH#sklep{quantity=NR}, [WH2|LL--El] end. ?>
b() - wyswietla zmienne
rf() - oddefiniowywuje recordy (zostaja same krotki)
rr() - zaciaga z pliku definicje recordow
Referencja
Przydaje się do tagowania, wysyłamy do procesu i jak z taką referencją zwróci wiemy, że to on, bo tylko on ma taką referencję.MyUniqueRef = make_ref().
Programowanie współbieżne
spawn - uruchamia jako osobny proces i zwraca Pid
zwykle uruchomienie funkcji,
>zad4:run(A1,A2).
uruchomienie ze spawn
>spawn(zad4,run,[A1,A2] ). jeśli bez argumentow []
błąd przerywa wykonywanie procesu
przerwać może inny proces który zna Pid
procesy nie dziela pamięci, ale jest wymiana informacji za pomocą wysyłania i odbierania wiadomości:
wysyłanie
>Pid ! hello
odbieranie (blok kodu)
receive
hello ->
end.
odbieranie z lini komend:
>flush().
w kodzie moze być kilka receive, ale skrzynka jest tylko jedna (trafia w dopasowanie)
i(). lista procesów
observer:Start(). program do obserwowania
self(). zwraca wlasne Pid
exit(Pid , kill). ubicie watku z Pid
zad6a:
Write a calculating server which will receive 2 numbers to add or multiply and send the answer back.
1. module name: calc_server
2. exported functions: main/0, server/0
3. message format, ex: {Pid, [3, 4, '+']}
-module(zad6). -export([server/0]). server() -> io:format("Zglasza sie ~p ~n",[?MODULE]), receive {Pid,[Nr1,Nr2,"+"]}-> io:format("Dodawanie ~n"), Pid ! Nr1+Nr2, server(); {Pid,[Nr1,Nr2,"-"]}-> io:format("Odejmowanie ~n"), Pid ! Nr1-Nr2, server() end .
Po kompilacji:
>c(zad6).
Uruchamiamy w shelu na osobnym wątku:
>Pid6=spawn(zad6,server,[]).
Wysyłamy wiadomość do Pid6, który odeśle do shela (którego pid podajemy self() )
>Pid6 ! {self(), [3,4,"+"]}.
Wyświetlamy wiadomości otrzymane przez shella
>flush().
shell got 7
Procesy
Procesy można powiązać (linked Processes), wtedy jeśli jeden zginie inne mogą się zamknąć.
Jest jednak falga trap_exit, wtedy dostaje tylko informację o zamknięciu ale nie zamyka
process_flag(trap_exit,true).
do procesu zostanie wtedy wysłana informacja:
{'EXIT',Pid,Reason}
Tabela zachowań flagi trap_exit pdf page 104
można to wykorzystać w sytuacji kiedy chcemy kontrolować działanie programu, jeśli dostaniemy informacje, że program przerwał działanie możemy uruchomić go ponownie.
Przykład: Kierownik uruchamia na osobnych wątkach trzech robotników i zapamiętuje ich Pid.
Ustawia flagę trap_exit
Po czym ponownie uruchamia siebie ale z argumentami Pid1, Pid2,Pid3
w tym bloku tylko oczekuje na wiadomość
receive
end.
jeśli dostanie wiadomość o budowie
{'EXIT', Pid1, killed}
na nowo uruchomi robotnika, przypisze jego Pid do zmiennej Nowy1 i ponownie uruchomi siebie przekazująć jako pierwszy argument powyższą zmienna
robotnik potrafi wywołać błąd i tym samym się zabić
-module(procesy_linki). -compile(export_all). kierownik(start) -> Pid1=spawn_link(procesy_linki,robotnik,[1]), Pid2=spawn_link(procesy_linki,robotnik,[2]), Pid3=spawn_link(procesy_linki,robotnik,[3]), process_flag(trap_exit,true), kierownik([Pid1,Pid2,Pid3]); kierownik([P1,P2,P3]) -> receive {'EXIT',P1,killed} -> io:format("Zabito mi robotnika 1 ~n"), Nowy1=spawn_link(procesy_linki,robotnik,[1]), kierownik([Nowy1,P2,P3]); Mesg -> io:format("Jako kierownik dostalem: ~p~n",[Mesg]), kierownik([P1,P2,P3]) end. robotnik(N) -> io:format("Jestem robotnik nr ~p~n",[N]), receive zrob_blad -> N=6; M -> io:format("Dostalem ~p i koncze ~n",[M]) end. server() -> receive {Pid,A,B,'+'} -> Pid ! A+B,server(); {Pid,A,B,'-'} -> Pid ! A-B,server(); Msg -> io:format("Dostalem ~p ~n",[Msg]) end.
żeby wysłać wiadomość do robotnika, musimy odczytać numer procesu z listy procesów i ręcznie wysłać:
<odczytany numer> ! zrob_blad
Jest to strategia "let it crash"
Try & Catch
p84
program bledy.erl
Erlang rozproszony
programy komunikują się ze sobą, każdy jest nodem, zajmuje się tym EPMD (tcp/ip)
przekazują sobie informacje o kolejnych nodach
magick cookie to atom który umożliwia rozpoznawanie i łączność
wejść na CMD windows i wpisać z folderu elranga:
werl -name marcin1@100.93.44.91 -setcookie secret_cookie
otworzy okienko, nazwa moze byc dowolna, tu zawiera ip ale nie musi,
drugie:
werl -name marcin2@100.93.44.91 -setcookie secret_cookie
cookie muszą być takie same, żeby komunikacja była możliwa.
nodes(). pokazuje podłączone nody
na pierwszym oknie wpisujemy
>{shell2 , 'marcin2@100.93.44.91'} ! {self(), "To jaa"}.
tym samym wysyłamy do drugiego wiadomosc, ze swoim Pid
w drugim:
>erlang:register(shell2,self()). rejestruje otwarta linie komend jako program shell2
>receive Msg -> Msg end.
i wyświetla:
{<8632.85.0>,"To jaa"}
żeby w drugi przypisać powyższy numer do zmiennej
>{Pid,_}=Msg
>Pid ! {self(), "W druga strone"}.
i żeby na drugim odczytać albo wpisujemy receive... albo
>flush().
gdy na innym komputerze (w tej samej podsieci) mieliśmy otwartą linie komend o nadanej nazwie z tym samym cookie i uruchomiony program listonosz to zeby wyslac:
{listonosz, 'michal1@100.93.44.99'} ! hejsiup.
jeśli potrebujemy zmienić cookie w otwartej lini komend:
>erlang:set_cookie(node(), 'secret_cookie').
Dynamic Code Upgrade
p.116 i przykład upgr w listonoszu gdzie uruchamia start ponownie
ETS
prosta baza danych, tabela, można ich powołać wiele, może być dzielona pomiędzy procesami (Erlang dba o dostęp)
tworzenie:
>ets:new(nazwa, [set, public, named_table]). nazwa bazy, set-index, publiczny dostęp, nazwana
dodawanie:
>ets:insert(ingr,{zupa, rosol}).
odczyt:
>ets:lookup(ingr,zupa).
[{zupa,rosol}] [] jeśli nic nie ma
kasowanie:
ets:delete albo po kluczu albo całą baza
TYPY które można wkładać:
set
ordered_set
bag
duplicate_bag
Rodzaje dostępu:
domyślnie baza jest protected, czyli wszyscy mogą czytać, właściciel zapisywać
public otwarta dla wszystkich, provate zamknięta dla wszystkich
można zmienić które pole krotki jest kluczem, dzięki czemu można posługiwać sie rekordami (w rekordach pierwsze pole krotki to nazwa rekordu, więc się nie zmienia) wystarczy przestawić np na drugą
>ets:new(nazwa, [set,public,named_table,{keypos,2}]).
po takim uruchomieniu bazy warto przekazać jego właścicielstwo innemu wątkowi, ponieważ teraz jeśli na naszej lini komend pojawi się w toku dalszego pisania błąd, przerwie działanie bazy!
jeśli mamy coś stale działającego uruchomione na innym wątku o znanym Pid(nawet prosty program czekające na receive, albo interfejs bazy) to:
ets:give_away(nazwa,Pid,"komunikat").
ets.tab@list(nazwa). listuje baze, pokazuje w formie listy
ets:i(). pokaże wszystkie bazy (nawet systemowe), można observer:Start().
wersja programu z zad4 ale zamiast zapisywać rekordy na podanej liście zapisuje na podanej bazie:
-module(zad4ets). -export([add/2]). -record(sklep,{name,price,quantity}). add(WH,LL) -> El= ets:lookup(LL,WH#sklep.name) , if El == [] -> ets:insert(LL,WH); true-> [EE]=El, NR=WH#sklep.quantity+EE#sklep.quantity, WH2=WH#sklep{quantity=NR}, ets:insert(LL,WH2) end.
i teraz po kompilacji
tworzymy baze:
>ets:new(sklepik, [set ,public, named_table, {keypos,2} ]).
tworzymy rekord:
Bannanas=#sklep{name=bannana,price=3.99,quantity=45}.
sprawdzamy czy baza jest pusta:
> ets:tab2list(sklepik).
[]
dodajemy za pomocą funkcji add
> zad4ets:add(Bannanas,sklepik).
true
i ponownie sprawdzamy
> ets:tab2list(sklepik).
[#sklep{name = bannana,price = 3.99,quantity = 45}]
tworzymy nowy rekord
>Bannanas2=#sklep{name=bannana,price=3.99,quantity=45}.
i dodajemy jego:
> zad4ets:add(Bannanas2,sklepik).
true
i ponownie sprawdzamy
> ets:tab2list(sklepik).
[#sklep{name = bannana,price = 3.99,quantity = 90}]
rekord o tej samej nazwie, zachował cenę, ale dodał ilość
Binary Data
page 69 + program codecs (zad5a)
Map
https://tohtml.com
Bred3