Elixir. Aby programowanie znów było przyjemnością - ebook
Elixir. Aby programowanie znów było przyjemnością - ebook
Ebook Elixir. Aby programowanie znów było przyjemnością autorstwa Dave’a Thomasa, jednego z twórców manifestu Agile, to doskonałe wprowadzenie do języka Elixir. Nie bez powodu jest on określany jednym z najbardziej „developers friendly” językiem. Z publikacji czytelnik dowie się o wzorcach i przekształceniach w Elixirze, a także o funkcjach anonimowych i nazwanych. Przeczyta, jak przeprowadzić refaktoryzacja kodu do stylu funkcyjnego. Zobaczy, na czym polega równoległość w Elixirze, a także pozna model aktora i komunikaty.
Kategoria: | Programowanie |
Zabezpieczenie: |
Watermark
|
ISBN: | 978-83-01-21893-5 |
Rozmiar pliku: | 1,2 MB |
FRAGMENT KSIĄŻKI
AUTOR – Dave Thomas
Piętnaście lat temu język programowania Ruby potrafił mnie ekscytować. Od tego czasu nie znalazłem niczego równie pasjonującego.
Nie wynikało to z tego, że nie próbowałem. Analizowałem wszystkie języki w miarę ich pojawiania się, ale żaden mnie nie porwał – żaden nie sprawił, że chciałbym spędzić kilka lat na jego zgłębianiu.
A potem odkryłem Elixir. Dwukrotnie. Za pierwszym razem pomyślałem, że jest przyjemny, ale nie porywający. Ale Corey Haines zachęcił mnie do ponownego spojrzenia. I miał rację. Elixir jest wyjątkowy.
Oto co należy o nim wiedzieć:
Elixir to funkcyjny język programowania, który działa na wirtualnej maszynie Erlanga. Ma składnię przypominającą Ruby i prezentuje protokoły (do rozszerzania modułów bez zmiany ich źródła), makra i bardzo dobrą obsługę metaprogramowania. Ma wiele nowoczesnych cech dzięki wykorzystaniu doświadczeń z innych języków. Na przykład makra mają zakres leksykalny, więc metaprogramowanie nie grozi już zaburzeniem globalnego środowiska wykonawczego.
Pracując na wirtualnej maszynie (VM) Erlanga, o nazwie Beam, Elixir jest w pełni kompatybilny z istniejącym kodem Erlanga. Oznacza to, że możemy korzystać z dużej dostępności, wysokiej współbieżności i rozproszonej natury Erlanga. Oznacza to także, że możemy używać tysięcy istniejących bibliotek Erlanga, tych wbudowanych i tych innych firm. W szczególności Elixir może działać w ramach struktury OTP (OTP to zbiór użytecznych bibliotek i narzędzi Erlanga, a także akronim, który w istocie nie oznacza niczego. To po prostu OTP).
Popatrzmy na kod. Jeśli chcemy działać samodzielnie, możemy pobrać Elixira. Odpowiednie instrukcje znajdują się na witrynie Elixira.
Poniższy kod oblicza sumę elementów z listy.
elixir/sum.ex
defmodule MyList do
def sum(), do: 0
def sum(), do: head + sum(tail)
end
IO.puts MyList.sum #=> 6
Nasza funkcja sum znajduje się w module o nazwie MyList. Funkcja jest zdefiniowana w dwóch klauzulach. Skąd więc Elixir wie, którą z nich uruchomić?
Dopasowywanie do wzorców
Tu wchodzi do gry _dopasowywanie do wzorców_. Lista parametrów z klauzuli pierwszej funkcji jest po prostu pusta. Gdy wywołujemy sum, ta klauzula będzie pasować wtedy i tylko wtedy, gdy wywołamy ją z pustą listą. Jeśli będzie pasować, funkcja zwróci 0.
Druga klauzula ma skomplikowaną listę parametrów – . Ten wzorzec pasuje do listy (mówią nam o tym nawiasy kwadratowe). Lista musi mieć pierwszy element (head) oraz tail (resztę listy). Jeśli przekazana lista ma tylko jeden element, początek (head) zostanie ustawiony na ten element, a koniec (tail) będzie pustą listą.
Wywołajmy więc MyList.sum z argumentem . Elixir szuka pierwszej klauzuli, której wzorzec pasuje do argumentu. Pierwsza klauzula nie pasuje – argumentem nie jest pusta lista – ale druga klauzula pasuje. head zostaje ustawione na 1, a tail na . Elixir wyznacza wartość treści, head+sum(tail), co oznacza, że funkcja sum jest rekurencyjnie wywoływana ponownie. Przy ostatnim wywołaniu pierwsza klauzula pasuje, więc nie ma kolejnego rekurencyjnego wywołania, a wynik zostaje zwrócony. Sekwencja wywołań wygląda mniej więcej tak:
sum()
1 + sum()
1 + 2 + sum()
1 + 2 + 3 + sum()
1 + 2 + 3 + 0
Dopasowywanie do wzorców to samo sedno Elixira. W istocie jest to jedyny sposób powiązania wartości ze zmienną. Gdy piszemy coś, co przypomina nadanie wartości:
a =
Elixir próbuje dopasować do siebie lewą i prawą stronę. W tym przypadku robi to, wiążąc listę ze zmienną a.
Gdy a ma tę wartość, możemy napisać:
= a
A Elixir będzie całkiem zadowolony – wartość po lewej pasuje do wartości po prawej. Ale jeśli napiszemy tak:
99 = a
Elixir się poskarży, że nie może znaleźć dopasowania. Dzieje się tak, gdyż Elixir zmieni wartość powiązaną ze zmienną tylko wtedy, gdy jest ona po lewej stronie operatora dopasowania.
Dopasowywanie do wzorców dla danych strukturalnych
Dopasowywanie postępuje – podczas dopasowywania Elixir będzie szukał struktury dwóch stron:
= # a = 1, b = 2, c = 3
= # head = 1, tail =
Po powiązaniu zmienna zatrzymuje tę samą wartość przez czas dopasowywania do wzorców. Tak więc kolejne dopasowywanie powiedzie się tylko wtedy, gdy zmienna list jest listą trzyelementową, gdzie pierwszy i ostatni element mają taką samą wartość:
= list
Możemy mieszać stałe i zmienne po lewej stronie. Aby to pokazać, wprowadzimy do Elixira nowy typ danych – _krotkę_. Krotka jest kolekcją wartości stałej długości. Zapisujemy stałe krotki między nawiasami sześciennymi:
{ 1, 2, "cat" } { :ok, result }
(Zapis :ok w drugim wierszu kodu to symbol Elixira. Możemy go traktować jak stały łańcuch lub symbol z języka Ruby. Możemy też traktować go jako stałą, której wartością jest jej nazwa, ale może to prowadzić do stanów katatonicznych).
Wiele funkcji bibliotecznych zwraca dwuelementowe krotki. Pierwszy element to status wyniku – jeśli jest to :ok, wywołanie się powiodło, a jeśli jest to :error, nastąpiło niepowodzenie. Druga wartość to prawdziwy wynik wywołania, z większą ilością informacji na temat błędu.
Możemy użyć dopasowywania do wzorców do określenia, czy wywołanie zakończyło się powodzeniem:
{ :ok, stream } = File.open(_"somefile.txt"_)
Jeśli File.open się powiodło, to stream będzie zawierać wynik. Jeśli się nie powiodło, wzorce nie będą zgodne, a Elixir pokaże błąd wykonania. (W tej sytuacji lepiej będzie otwierać plik za pomocą File.open!(), co w przypadku niepowodzenia da więcej mówiący opis błędu).
Dopasowywanie do wzorców i funkcje
Dla przykładu: MyList.sum pokazuje, że dopasowywanie do wzorców ma zastosowanie także do wywoływania funkcji. Widać to? Parametry funkcji działają jak lewa strona dopasowywania, a przekazywane argumenty to odpowiednik prawej strony.
Oto inny przykład (z długą brodą): wyznacza wartość n-tej liczby w ciągu Fibonacciego. Zacznijmy od definicji liczb Fibonacciego:
fib(0) -> 1
fib(1) -> 1
fib(n) -> fib(n-2) + fib(n-1)
Gdy korzystamy z dopasowywania do wzorców, możemy włączyć tę specyfikację do kodu wykonywanego przy minimalnym wysiłku:
elixir/fib.ex
defmodule Demo do
def fib(0), do: 1
def fib(1), do: 1
def fib(n), do: fib(n-2) + fib(n-1)
end
Elixir oferuje interakcyjną powłokę o nazwie iex. Pozwala ona nam pobawić się metodą fib:
elixir _% iex fib.ex_
Erlang R15B03 ...
Interactive Elixir (...)
iex(1)> Demo.fib(0)
1
iex(2)> Demo.fib(1)
1
iex(3)> Demo.fib(10)
89
Wspaniała jest w tym łatwość, z jaką przekształcamy specyfikację w wykonywalny kod. A gdy kod jest już napisany, łatwo jest go czytać i zobaczyć, co robi.
Warto odnotować coś jeszcze – w implementacji naszej funkcji nie ma instrukcji warunkowych. To także sprawia, że kod jest bardziej czytelny (i łatwiejszy w utrzymaniu). W istocie wiele dość dużych modułów Elixira zostało napisanych z niewielką liczbą instrukcji warunkowych lub bez nich.
Przekształcenie to zadanie numer 1
Można pomyśleć, że wszystko jest świetnie, ale pisanie matematycznych funkcji nie jest naszą codzienną pracą. Programowanie funkcyjne nie dotyczy jednak funkcji matematycznych.
Funkcje służą do przekształcania danych. Funkcja trygonometryczna _sin_ przekształca wartość 90° na wartość 1,0. I to jest wskazówka.
W programowaniu nie chodzi o dane. Chodzi o _przekształcanie_ danych. Każdy pisany przez nas program pobiera coś na wejściu i przekształca na jakieś wyjście. Wejściem może być żądanie z internetu, jakieś parametry z wiersza poleceń lub dane o pogodzie w Boice w Idaho. Czymkolwiek to będzie, nasz kod przekształca to wiele razy na drodze do uzyskania pożądanego wyniku.
Właśnie dlatego uważam, że programowanie funkcyjne jest naturalnym następcą programowania obiektowego. W programowaniu obiektowym jesteśmy cały czas zainteresowani stanem naszych danych. W programowaniu funkcyjnym skupiamy się na _przekształcaniu_ danych. A przekształcanie jest miejscem, w którym jest wartość dodana.
W następnym rozdziale przyjrzymy się funkcjom, zarówno tym nazwanym, jak i anonimowym. I poznamy operator potokowy, który pozwala nam robić coś takiego:
request |> authenticate |> lookup_data |> format_response
A to wygląda jak magia.