class: center, middle # Erlang ## Języki i Paradygmaty Programowania ### 13.06.2016 --- # Kontekst - urządzenia telekomunikacyjne - wysoka współbieżność (10^5-10^6 jednoczesnych akcji) - czas rzeczywisty (soft real-time) - rozproszenie - wysoka dostępność (9 dziewiątek, downtime<4min./rok) - miliony linii kodu - ciągła operacja - ciągła ewolucja - aktualizacje bez wyłączeń --- # Erlang - bardzo lekkie i tanie procesy - szybkie przekazywanie komunikatów - pełna izolacja procesów - automatyczna serializacja - programowanie funkcyjne - typowanie dynamiczne - szybki kod sekwencyjny - połączenie programowania sekwencyjnego i współbieżnego --- # Functions + Messages + Concurrency = Erlang * programowanie funkcyjne * przekazywanie komunikatów * programowanie współbieżne * fault-tolerance * wpływy Prologu (początkowo współbieżne rozszerzenie Prologu) * multicore --- # Programowanie funkcyjne Haskell ```haskell qsort [] = [] qsort (x:xs) = qsort [y | y <- xs, y
=x] ``` Erlang ``` Erlang qsort([]) -> []; qsort([X|Xs]) -> qsort([Y || Y <- Xs, Y
=X]). ``` --- # Multicore ☞ Szybkość procesorów praktycznie przestała rosnąć ok 2006 Rośnie liczba rdzeni Program sekwencyjny będzie z roku na rok działał coraz wolniej Program współbieżny będzie z roku na rok działał coraz szybciej --- # Równoległość w Haskellu ```shell ben@azor$ time ./sudoku-par3 sudoku17.1000.txt +RTS -N1 real 0m2.646s user 0m2.610s sys 0m0.044s ben@azor$ time ./sudoku-par3 sudoku17.1000.txt +RTS -N2 real 0m1.532s user 0m2.971s sys 0m0.088s ben@azor$ time ./sudoku-par3 sudoku17.1000.txt +RTS -N4 real 0m0.900s user 0m3.396s sys 0m0.177s ben@azor$ time ./sudoku-par3 sudoku17.1000.txt +RTS -N8 real 0m0.598s user 0m4.316s sys 0m0.357s ``` ``` haskell solve :: String -> Maybe Grid main = do [f] <- getArgs grids <- fmap lines $ readFile f print (length (filter isJust (runPar $ parMap solve grids))) ``` ---  --- # Odporność na awarie (fault tolerance) Dla zbudowania systemu odpornego na awarie potrzeba co najmniej *dwóch komputerów* W razie awarii jednego, drugi przejmie jego funkcje Konsekwencje: - nie ma dzielonej pamięci - programowanie rozproszone - czyste przekazywanie komunikatów --- # Równoległość - Haskell vs Erlang Procesy w Haskellu *współdzielą pamięć* W Erlangu każdy proces ma własny heap: - komunikaty są kopiowane - nie ma globalnego odśmiecania - każdy proces odśmieca swój własny heap W Smalltalku przekazywanie komunikatów, ale przy współdzielonej pamięci --- # Wysoka odporność na awarie Dla zbudowania systemu odpornego na awarie potrzeba co najmniej *dwóch komputerów* Dla zbudowania systemu wysoce odpornego na awarie potrzeba wielu komputerów ### = Skalowalność --- # Teza Armstronga ### Odporność ### Rozproszenie ### Współbieżność ### Skalowalność ### są nierozłączne --- # Dwa modele współbieżności ## Dzielona pamięć - semafory - wątki ## Przekazywanie komunikatów - procesy - komunikaty --- # Przekazywanie komunikatów ### Nie ma dzielonych zasobów ### Czyste przekazywanie komunikatów ### Nie ma semaforów ### Obliczenia rozproszone (odporność, skalowalność) ### Programowanie funkcyjne (nie ma efektów ubocznych) --- # Concurrency Oriented Programming Wiele procesów Pełna izolacja między procesami Niezależne od lokalizacji - Możemy przenieść proces na inną maszynę Nie ma współdzielonych danych Tylko przesyłanie komunikatów ### COP jest w pewnym sensie naturalnym rozwinięciem OOP ### 'The big idea in Smalltalk is messaging' - Alan Kay --- # Dlaczego COP? Bliskie intuicyjnemu rozumieniu współbieżności Świat realny jest współbieżny Świat realny jest rozproszony Świat realny jest poznawalny ;) Tworzenie realnych aplikacji jest oparte na obserwacji rzeczywistych wzorców współbieżności i kanałów komunikacji Ułatwia budowanie skalowalnych, rozproszonych programów. --- # Erlang w 11 minut Niewiele o składni (czerpie z Prologu) Programowanie sekwencyjne Programowanie współbiezne Programowanie rozproszone Odporność na błędy --- # Podstawowe typy danych - Liczby: `1, 2, 3.1415` - Atomy: `hello` - Napisy: `"hello"` - Ciągi bitów ``` erlang 11> <<16#48,101,$l,"lo">>. <<"Hello">> 13> <<2#1010>>. <<"\n">> 14> <<2#1010:4>>. <<10:4>> ``` - Rekordy: `{color, "red"}` - Listy: `[red,green,blue]` ``` erlang 16> [1|[2|[]]]. [1,2] ``` --- # Zmienne ``` erlang 16> X = 1. 1 17> X=X+1. ** exception error: no match of right hand side value 2 18> X = 2. ** exception error: no match of right hand side value 2 19> {Y,Z} = {1,2}. {1,2} 20> {V1,2} = {1,V2}. * 1: variable 'V2' is unbound ``` NB Prolog: ``` prolog ?- X=1, X=2. false. ?- .(V1,2) = .(1,V2). V1 = 1, V2 = 2. ``` --- # Listy ``` erlang 21> L = [1,2,3]. [1,2,3] 22> [0,1,2,3] = [0|L]. [0,1,2,3] 23> [H|T] = L. [1,2,3] 24> H. 1 25> T. [2,3] ``` Funkcje ``` erlang 27> Increment = fun(X) -> X + 1 end, 27> [2,3,4] = lists:map(Increment,L). [2,3,4] ``` --- # Silnia ``` erlang -module(math). -export([fac/1]). fac(0) -> 1; fac(X) when X > 0 -> X*fac(X-1). ``` ``` erlang 1> c(math). {ok,math} 2> math:fac(5). 120 3> math:fac(-1). ** exception error: no function clause matching math:fac(-1) (math.erl, line 4) ``` --- # Geometry ``` erlang -module(geometry). -export([area/1]). area({rectangle, W,H}) -> W*H; area({square, X}) -> X*X; area({circle, R}) -> 3.14159*R*R. ``` ``` Eshell V6.4 (abort with ^G) 1> c(geometry). {ok,geometry} 2> geometry:area({square, 10}). 100 3> geometry:area({circle, 10}). 314.159 4> geometry:area(7). ** exception error: no function clause matching geometry:area(7) (geometry.erl, line 4) ``` --- # Obsługa błędów ``` erlang area2({rectangle, W,H}) -> W*H; area2({square, X}) -> X*X; area2({circle, R}) -> 3.14159*R*R; area2(Other) -> error. ``` ``` erlang 47> self(). <0.11254.0> 48> geometry:area2(7). error 49> self(). <0.11254.0> 50> geometry:area(7). ** exception error: no function clause matching geometry:area(7) (geometry.erl, line 4) 51> self(). <0.11259.0> ``` NB: wyjątek powoduje zakończenie procesu interpretera, nadzorca uruchamia nowy proces. --- # Drzewa BST ``` erlang lookup(Key, {Key, Val,_,_}) -> {ok, Val}; lookup(Key, {Key1,Val,S,B}) when Key < Key1 -> lookup(Key, S); lookup(Key, {Key1, Val, S, B})-> lookup(Key, B); lookup(key, nil) -> not_found. ``` --- # Komunikaty Komunkacja między procesami jest asynchroniczna Każdy proces ma swoją 'skrzynkę pocztową' (mailbox) wysłanie komunikatu (do skrzynki): `proces ! komunikat` Odbiór komunikatu ``` erlang receive wzorzec -> wyrażenie; wzorzec -> wyrażenie; ... end ``` Wyczyszczenie skrzynki: `flush()` - w shellu dodatkowo wypisuje komunikaty. --- # Komunikaty - przykład ``` erlang 1> self() ! hello. hello 2> self() ! testing. testing 3> self() ! [1,2,3]. [1,2,3] 4> flush(). Shell got hello Shell got testing Shell got [1,2,3] ok 5> flush(). ok ``` ``` erlang 1> receive 1> ok -> cool 1> after 1> 1000 -> timeout 1> end. timeout 2> self() ! ok. ok 3> receive ok -> cool after 100 -> timeout end. cool ``` --- # Procesy ``` erlang 8> Shell = self(). <0.36.0> 9> SendHello = fun()-> Shell ! hello end. #Fun
10> spawn(SendHello). <0.51.0> 11> spawn(SendHello). <0.53.0> 12> spawn(SendHello). <0.55.0> 13> flush(). Shell got hello Shell got hello Shell got hello ok ``` --- # Współbieżność ``` erlang 1> Send = fun(From)->fun()->From ! { ok, {sent, From}, {rcvd, self()} } end end. #Fun
2> spawn(Send(self())). <0.35.0> 3> spawn(Send(self())). <0.37.0> 4> spawn(Send(self())). <0.39.0> 5> flush(). Shell got {ok,{sent,<0.32.0>},{rcvd,<0.35.0>}} Shell got {ok,{sent,<0.32.0>},{rcvd,<0.37.0>}} Shell got {ok,{sent,<0.32.0>},{rcvd,<0.39.0>}} ok 6> spawn(Send(self())). <0.42.0> 7> receive {ok, {sent, From}, {rcvd, By}} -> {ok, From, By} end. {ok,<0.32.0>,<0.42.0>} 8> flush(). ok ``` --- # Rozproszony Erlang ``` erlang -module(dist). -export([t/1]). t(From) -> From ! { ok, node(), self() }. ``` ``` erlang $ erl -sname bar (bar@marbook)1> node(). bar@marbook (bar@marbook)2> c(dist). {ok,dist} ``` ``` erlang $ erl -sname foo (foo@marbook)1> c(dist). {ok,dist} (foo@marbook)2> spawn('bar@marbook',dist,t,[self()]). <9728.49.0> (foo@marbook)3> flush(). Shell got {ok,bar@marbook,<9728.49.0>} ok ``` --- # Code hotswapping Erlang pozwala na zmianę kodu w trakcie działania programu. Po skompilowaniu modułu, kolejne wywołanie użyje nowej wersji --- # Procesy są niezależne ``` erlang 21> Crash=fun()->timer:sleep(1000),1/0 end. #Fun
22> self(). <0.46.0> 23> Crash(). ** exception error: an error occurred when evaluating an arithmetic expression in operator '/'/2 called as 1 / 0 24> self(). <0.65.0> 25> spawn(Crash). <0.68.0> =ERROR REPORT==== 12-Jun-2016::07:00:42 === Error in process <0.68.0> with exit value: {badarith,[{erlang,'/',[1,0],[]}]} 26> self(). <0.65.0> ``` Błąd w procesie nie wpływa na interpreter. --- # Wiązanie procesów Możemy powiązać procesy tak, że awaria jednego z procesów powoduje zakończenie drugiego: ``` 28> self(). <0.65.0> 29> spawn_link(fun() -> receive after 200 -> exit(normal) end end). <0.80.0> 30> self(). <0.65.0> 31> spawn_link(fun() -> receive after 200 -> exit(error) end end). <0.83.0> ** exception error: error 32> self(). <0.85.0> ``` --- # trap_exit Po ustawieniu flagi `trap_exit`, zakończenie powiązanego procesu powoduje wysłanie komunikatu, co pozwala na zrestartowanie go. ``` erlang 14> process_flag(trap_exit,true). false 15> process_flag(trap_exit,true). true 16> flush(). ok 17> spawn_link(fun() -> receive after 200 -> exit(error) end end). <0.57.0> 18> 18> flush(). Shell got {'EXIT',<0.57.0>,error} ok 19> spawn_link(fun() -> receive after 200 -> exit(normal) end end). <0.60.0> 20> flush(). Shell got {'EXIT',<0.60.0>,normal} ok ``` --- # Monitorowanie ``` erlang 1> spawn_link(fun() -> receive after 200 -> exit(normal) end end). <0.34.0> 2> spawn_link(fun() -> receive after 200 -> exit(error) end end). <0.36.0> ** exception error: error 3> P = spawn(fun() -> receive after 10000 -> ok end end). <0.39.0> 4> monitor(process, P). #Ref<0.0.0.44> 5> monitor(process, P). #Ref<0.0.0.49> 6> monitor(process, P). #Ref<0.0.0.54> 7> flush(). Shell got {'DOWN',#Ref<0.0.0.44>,process,<0.39.0>,normal} Shell got {'DOWN',#Ref<0.0.0.49>,process,<0.39.0>,noproc} Shell got {'DOWN',#Ref<0.0.0.54>,process,<0.39.0>,noproc} ok ``` --- ``` 8> P = spawn(fun() -> receive after 10000 -> exit(error) end end). ** exception error: no match of right hand side value <0.45.0> 9> Q = spawn(fun() -> receive after 10000 -> exit(error) end end). <0.48.0> 10> monitor(process, Q). #Ref<0.0.0.78> 11> monitor(process, Q). #Ref<0.0.0.83> 12> monitor(process, Q). #Ref<0.0.0.88> 13> flush(). Shell got {'DOWN',#Ref<0.0.0.78>,process,<0.48.0>,error} Shell got {'DOWN',#Ref<0.0.0.88>,process,<0.48.0>,error} Shell got {'DOWN',#Ref<0.0.0.83>,process,<0.48.0>,error} ok ``` --- # Open Telecom Platform `erlang:monitor/2` jest częścią biblioteki OTP Biblioteka ta dostarcza funkcji obsługujących: * współbieżność * obliczenia rozproszone * monitorowanie procesów * protokoły komunikacyjne * tworzenie serwerów * ... Przykłady zastosowań: * RabbitMQ * CouchDB * Riak