1 Erlang podstawy

Notatki Link

instalacja konsoli:

http://erlang.org/download/otp_win64_21.3.exe

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.

rozwiązanie:
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ą recursion
wydrukuj 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

definiowane w miejscu dzialania, mozemy dzięki temu funkcje przypisać do zmiennej i zapisać potem w liście albo przyjąć funkcję jako argument.
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.
?>




f()  - kasuje w konsoli wszystkie zmienne
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