======= Erlang! ======= :Autor: Łukasz Cieśnik :Kontakt: lukasz.ciesnik@gmail.com :Organizacja: Politechnika Poznańska :Data: 2009-10-29 :Rewizja: 1 .. role:: erlang(inline-code) Plan prezentacji ================ Plan prezentacji ---------------- .. .. contents:: * Geneza języka .. note:: Geneza - w jaki sposób powstał język i skąd w ogóle potrzeba "nowego" języka * Programowanie sekwencyjne * Współbieżność w Erlangu * Rozpraszanie * Obsługa błędów * Mnesia Geneza ====== Geneza ------ .. note:: Język "przemysłowy". Powstały pod konkretne zastosowania - systemy telekomunikacyjne w firmie Ericson. Początek w 1986 w firmie Ericsson. Napisany przez Joe Armstronga na potrzeby oprogramowania telekomunikacyjnego: .. note:: Nazwany na cześć duńskiego matematyka - twórcy teorii kolejek i ruchu telekomunikacyjnego. Kod otwarty w 1998. * :emphasis:`soft real time systems` --- wymagany jest niski czas odpowiedzi, ale bez sztywnych ograniczeń czasowych, * działające nieprzerwanie (:english:`non-stop systems`), .. note:: Wymagany bardzo niski down-time. Brak możliwości wyłączenia systemu nawet przy pracach konserwatorskich. * obsługujące asynchroniczne zdarzenia, najłatwiej modelowane przy pomocy procesów .. note:: porównanie z twisted? * duże (złożone) systemy * skalowalne, wymagające rozpraszania .. note:: porównanie do map-reduce? * odporne na błędy (:english:`robust`) Wprowadzenie ============ Cechy ----- * paradygmat funkcyjny: - funkcje (w tym domknięcia) to :english:`first-order objects`, - ograniczenie efektów ubocznych, * zachłanne obliczanie, .. note:: słowo o językach czysto funkcyjnych, leniwej ewaluacji * wbudowane silne wsparcie dla współbieżnie wykonywanych procesów komunikacja na zasadzie przesyłania komunikatów, .. note:: Z powodu niemodyfikowalnych (:english:`immutable`) obiektów jedyny możliwy sposób komunikacji to przesyłanie wiadomości. Stąd łatwo było wprowadzić rozproszenie przetwarzania (język tworzony był z takim zamiarem, jednak możliwość rozpraszania przetwarzania została zaimplementowana później). * rozproszone przetwarzanie, * obsługa błędów, * ładowanie kodu w działającym systemie (:english:`hot code loading`) bez naruszania działających procesów, .. note:: Kod jest wymieniany na poziome modułów. Stary kod pozostaje w systemie i może być wykonywany równolegle z nowym tak długo, jak długo procesy mają go w stosie wywołań. * komunikacja ze światem zewnętrznym. Programowanie funkcyjne w Erlangu --------------------------------- W Erlangu same obiekty (:english:`term`) są niemodyfikowalne, ale istnieją efekty uboczne: * tworzenie procesów * wysyłanie, odbieranie wiadomości * obsługa wejścia/wyjścia .. note:: Przy modularnej budowie systemów efekty uboczne wywołane zarządzeniem procesami i komunikacją mogą być ograniczone do modułów. Możemy więc stosunkowo łatwo zagwarantować idempotentność, co np. ułatwia testowanie. Zachłanne wartościowanie i obecność jawnej komunikacji lepiej służy modelowaniu procesów komunikujących się ze światem zewnętrznym i obsługujących asynchronicznych zdarzenia. .. note:: słowo o pattern matching Typy danych ----------- .. note:: Co ważniejsze typy danych. * liczby (całkowite, zmiennoprzecinkowe), * atomy (symbole), .. note:: Pisane z małej litery lub w pojedynczych cudzysłowach (zmienne pisane z wielkiej litery) * listy, .. note:: Single linked jak w lispie. .. code-block:: erlang [1, 2, 3] == [1 | [2 | [3 | []]]] * krotki, .. note:: Stałej długości. Różnią się od list czasem dostępu .. code-block:: erlang {1, 2, 3} * brak osobnego typu łańcuchowego --- tekst reprezentowany jest przez listę kodów znaków, .. note:: Literały w podwójnych cudzysłowach. Wsparcie dla Unicode. * rekordy (implementowane jako krotki), .. note:: Rekordy - krotki z nazwanymi polami. Na etapie kompilacji tłumaczone na odwołania do krotek. Nie widoczne w czasie wykonania (w tym w shellu). Kompilator dokłada co najwyżej funkcje umożliwiające introspekcję. .. code-block:: erlang -record(person, {firstname, lastname}). #person{firstname="John", lastname="Kowalski"} == {person, "John", "Kowalski"} * funkcje (przeciążane ze względu na ilość argumentów), .. note:: Funkcje o różnej długości argumentów mogą mieć tę samą nazwę. Przy odwołaniach do funkcji trzeba zawsze podać ilość argumentów. * identyfikatory procesów. Składnia -------- Przykładowy kod: .. code-block:: erlang -module(factor). -export([factor/1]). factor(N) when N > 0 -> factor(N, 2). factor(N, Div) when N rem Div == 0 -> [Div | factor(N div Div, Div)]; factor(N, Div) when Div*Div =< N -> factor(N, Div+1); factor(N, _) when N > 1 -> [N]; factor(_, _) -> []. .. * <- for vim .. note:: powiedzieć o: - pytanie: co oznacza factor/1 w dyrektywie export? - 2 funkcjach --- factor/1 i factor/2 - strażnikach (guard), - zmiennych anonimowych ``_``, - pytanie: dla jakiego N będzie wywołana przedostatnia i ostatnia część funkcji factor/2? Współbieżność ============= Procesy ------- Procesy w ramach maszyny wirtualnej: * tanie w tworzeniu, * niski narzut przełączania kontekstu, * brak ograniczenia na ilość procesów, .. note:: Każde osobne przetwarzanie może być realizowane w ramach odrębnego procesu. Utworzenie nowego procesu: .. code-block:: erlang Pid = spawn(Module, Function, Arguments) Identyfikator procesu: :erlang:`self()` Wysłanie wiadomości: :erlang:`Pid ! Message` Ping: serwer ------------ .. code-block:: erlang echo() -> receive {echo_request, From, Seq} -> From ! {echo_reply, self(), Seq}, echo(); stop -> ok end. Ping: wysyłanie --------------- .. code-block:: erlang ping_send(_, Receiver, Stop, Stop, _) -> Receiver ! stop; ping_send(Peer, Receiver, Seq, Stop, Interval) -> Peer ! {echo_request, Receiver, Seq}, receive after Interval -> ping_send(Peer, Receiver, Seq+1, Stop, Interval) end. Ping: odbiór ------------ .. code-block:: erlang ping(Peer, N, Interval) -> Receiver = self(), spawn(fun() -> ping_send(Peer, Receiver, 0, N, Interval) end), ping_recv(Peer). ping_recv(Peer) -> receive {echo_reply, From, Seq} -> io:format("reply from ~w seq=~w\n", [From, Seq]), ping_recv(Peer); stop -> ok end. Ping: wywołanie --------------- .. code-block:: erlang -module(ping). -export([start/0, echo/0, ping/3]). start() -> Echo_server = spawn(fun echo/0), ping(Echo_server, 3, 500), Echo_server ! stop. .. * Przykład: ping - serwer .. * ----------------------- .. * .. * .. code-block:: erlang .. * .. * echo() -> .. * receive .. * {echo_request, From, Seq} -> .. * From ! {echo_reply, self(), Seq}, .. * echo(); .. * stop -> .. * true .. * end. .. * .. * Przykład: ping - klient .. * ----------------------- .. * .. * .. code-block:: erlang .. * .. * ping(Peer, Stop, Stop) -> .. * Peer ! stop; .. * ping(Peer, Seq, Stop) -> .. * Peer ! {echo_request, self(), Seq}, .. * receive .. * {echo_reply, Peer, Seq} -> .. * io:format("reply from ~w seq=~w/~w~n", .. * [Peer, Seq, Stop]); .. * Invalid -> .. * io:format("invalid: ~w~n", [Invalid]) .. * end, .. * ping(Peer, Seq+1, Stop). .. * .. * Przykład: ping - wywołanie .. * -------------------------- .. * .. * .. code-block:: erlang .. * .. * -module(ping). .. * -export([main/0, echo/0]). .. * .. * main() -> .. * Peer = spawn(ping, echo, []), .. * ping(Peer, 3). .. * .. * ping(Peer, Stop) -> .. * ping(Peer, 0, Stop). Rozpraszanie ------------ Dzięki komunikacji poprzez wymianę pakietów wsparcie dla rozproszonych systemów jest naturalne. Można bez większych zmian program działający lokalnie uruchomić na wielu węzłach, ale trzeba określić lokalizację procesów na etapie ich tworzenia --- częściowa przezroczystość lokalizacji. .. note:: Komunikacja pomiędzy procesami jest niezależna od ich lokalizacji. Identyfikator procesu zawiera adres maszyny w której jest wykonywany. .. TODO: sample code? Obsługa błędów ============== Odporność na awarie ------------------- Erlang wspiera tworzenie systemów odpornych na błędy programistyczne czy awarie sprzętu: * Zapewnia izolację procesów, łatwy podział systemów na niezależne moduły i warstwy. * Mechanizmy wykrywania błędów, monitorowania procesów. * Możliwość powtórzenia przetwarzania. * Możliwość poprawienia kodu w działającym systemie (bez restartowania). .. TODO: sample code? Monitorowanie procesów ---------------------- Procesy można organizować w graf nieskierowany połączeń ustanawianych przez wywołanie :erlang:`link(Pid)`. .. note:: Istnieją też funkcje :erlang:`spawn_link`, które atomowo tworzą proces i wiążą go z rodzicem. Proces przy zakończeniu (pomyślnym lub nie) wysyła informację o tym do procesów z nim połączonych. Domyślnym zachowaniem procesów przy dostarczeniu wiadomości o pozytywnym zakończeniu jest jej ignorowanie, a przy błędnym --- zakończenie przetwarzania i dalsze rozpropagowanie błędu. .. note:: W ten sposób gdy proces odpowiedzialny za część przetwarzania wykryje błąd, jest w stanie w prosty sposób zakończyć całe przetwarzanie. Z kolei warstwa nadzorująca przetwarzanie, która odbiera informację o zakończeniu procesów może zareagować na pojawiające się błędy. Po wywołaniu :erlang:`process_flag(trap_exit, true)` proces będzie odbierał informację o identyfikatorach zakończonych procesów oraz przyczynach zakończenia. Mnesia ====== Mnesia ------ Mnesia to obiektowo-relacyjna baza danych. * Szybki czas dostępu --- baza działa w tej samej maszynie wirtualnej co aplikacja. .. note:: Baza działa w tej samej przestrzeni adresowej co korzystająca z niej aplikacja. Nie ma zagrożenia niezawodnej pracy bazy z racji izolacji procesów w Erlangu. * Język zapytań zrealizowany na bazie składni Erlanga. .. note:: brak potrzeby ORM * Wartościami atrybutów mogą być dowolne struktury danych. * Tranzakcje mogą być rozproszone i zagnieżdżone. * Operacje krytyczne pod względem czasu wykonania można realizować z obejściem tranzakcyjności. * Tabele mogą być replikowane w pamięci i/lub na dysku wielu węzłów. Gwarantuje to mniejszy czas dostępu oraz odporność na awarię --- można operować na tabeli tak długo jak dostępna jest jedna z replik. .. note:: Każda replika jest równoważna. Może dojść do utraty spójności pomiędzy replikami przy awarii łącza pomiędzy nimi. * Zapytania są niezależne od lokalizacji tabeli. Zapytania 1 ----------- .. code-block:: erlang -record(person, {id, first_name, last_name, sex}). -record(project, {id, name}). -record(project_assignment, {project_id, person_id, hours}). list_females() -> mnesia:transaction(fun() -> Q = qlc:q([{P#person.first_name, P#person.last_name} || P <- mnesia:table(person), P#person.sex == female]), qlc:e(Q) end). Zapytania 2 ----------- .. code-block:: erlang -record(person, {id, first_name, last_name, sex}). -record(project, {id, name}). -record(project_assignment, {project_id, person_id, hours}). assigned_people(Project) -> Q = qlc:q([Person || Person <- mnesia:table(person), Assign <- mnesia:table(project_assignment), Assign#project_assignment.project_id == Project#project.id, Assign#project_assignment.person_id =:= Person#person.id]) qlc:e(Q). Linki ----- * Erlang --- oficjalna strona: * prezentacja: .. vim700: spell spelllang=pl .. vim: ft=rest .. vim: ts=4 sw=4 et