Wczoraj opisałem co to jest Pattern Matching. Wstęp był potrzebny, dzięki czemu ten post będzie krótki i treściwy. Zobaczę, może w ten sposób będę robił resztę postów? Wstęp merytryczny opisujący zagadnienie języka funkcjnego i potem jak to jest w elixir rozwiązane? Nie wiem. Szczerze nie mam żadnego planu z tym elixirem. Uczę się dosłownie tego co mnie zaciekawi :) I tak, pattern matching w elixir mnie zaciekawił – dokładnie zdzwiił gdyż prawie wszędzie jest wykorzystywany i to nawet nieświadomie. Dlatego zachęcam do lektury – same, ale to SAME ciekawostki :)
Operator dopasowania
Normalnie w językach programowania kiedy widzimy zapis:
a = 10
Oznacza to, że przypisujemy wartość 10 do zmiennej a. W elixir zaś znaczy to, że zmienna a została z boundowana do wartości 10. Dokładnie mówiąc wartość 10 dopasowuje się do wzorca a. Czyli operator = nie jest operatorem przypisania a dopasowania wzorcowego.
Czyli zapis:
0 = 1
Podobnie jak z funkcją w F# nie powiedzie się. Gdyż wzorzec 0 nie pasuje do wartości 1. Zresztą odpalcie sobie iex i sami zobaczcie co się stanie jak wykonacie powyższe polecenie?
iex(1)> 0 = 1 ** (MatchError) no match of right hand side value: 1
Widzicie error? MatchError. Nie jakiś AssignmentError (wymyślony), ale MatchError.
Podstawowe dopasowania
Kilka prostych przykładów. Będę opisywał przykład komentarzem a następnie pokazywał jego rezultat. Wszystko powinno dać się skopiować/wkleić do iex.
# dopasowanie tuple do zmiennej
dog = {"Eldir", 10}
dog
# dopasowanie do poszczególnego wzorca (name i age)
{name, age} = {"Eldir", 10}
name
age
# olanie jednej z wartości
{ _, age } = {"Elidr", 10}
# rozbicie tuple na pomniejsze
{date, time} = :calendar.local_time
{year, month, _} = date
date
year
month
# złe dopasowanie
{date, time} = 10
** (MatchError) no match of right hand side value: 10
Stałe – trochę bardziej zaawansowane dopasowania
Teraz, dość ciekawa sytuacja jest wtedy kiedy zaczniemy wykorzystywać stałe, tak zwane atomy. Dokładnie jest to dobrą i normalną praktyką w elixir przy zwracaniu operacji które mogły zakończyć się powodzeniem {:ok, contents} lub błędem {:error, reason}.
Prosty przykład
dog = {:dog, "Eldir", 10}
{:dog, name, age} = dog
name
age
Ale po co to? Mamy funckję która może zwrócić nam różne wartości dla przykładu, funkcja dog_or_person może zwrócić {:dog, name, age} albo {:person, name, age}. Teraz to co my robimy to podając atom ograniczamy wynik który nas interesuje:
{:dog, name, age} = dog_or_person
Niby proste przykład, ale teraz kiedy mamy czytanie z pliku, próbę nawiązania połączenia do serwera itp., to możemy wykonywać rózne operację w zależności od tego czy mamy :ok czy :error – zaraz będą przykłady.
To co warto jeszcze wspomnieć to to, że możemy zagnieżdżać wzorce:
{{year, month, _}, {hour, _, _}} = :calendar.local_time
year
month
hour
Tak samo to działa na listach:
[one, two, three] = [1, 2, 3]
Z tą różnicą iż listy to taki specyficzny twór w Elixir, gdzie mamy head i tail. head to aktualna wartość a tail to wszystkie pozostałe, a więc:
[head | tail] = [1, 2, 3]
Da nam:
head 1 tail [2,3]
Jak widzicie, dopasowanie wzorców jest prawie wszędzie :)
Przy mapach zaś możemy napisać na przykład:
%{age: age} = ${name: "Eldir", age: 10}
age
10
Co daje nam opcjonalne wartości które możemy olać bez ich określenia za pomocą _. Albo w ogóle pracując z kodem, który może ulec zmianie (i pewnie ulegnie) – wtedy jeżeli tuple by zmienił liczbę parametrów albo ich kolejność, wszystkie dopasowania musiałyby być zmienione. Za pomocą map. nie.
Dopasowanie w funkcjach
To teraz dosłownie to samo co przy opratorze dopasowania = można zastosować do funkcji i przekazywaniu do nich parametrów. Wróćmy do przykładu dodawania z poprzednich wpisów:
defmodule Euclid do
def add({a, b}) do
a + b
end
end
Teraz możemy podać dowolny tuple dwu elementowy do funkcji. To spowoduje dopasowanie wzorca. Czyli podanie na przykład {10} skończy się wyjątkiem. Ale ok, co mi to daje? Pamiętacie atomy? To teraz patrzcie:
defmodule Euclid do
def op({:add, a, b}) do
a + b
end
def op({:pow, a, b}) do
:math.pow(a,b)
end
def op(whaaat) do
{:error, "whaaaaaaat"}
end
end
Euclid.op({:add, 10, 10})
Euclid.op({:pow, 2, 3})
Euclid.op({:daa, 2, 3})
WOW. Daje nam to pewne przeciążenie funkcji. Fajne? Dla mnie super. Teraz już wiecie jak można zastosować {:ok, whatever}, {:error, reason} ?
A zobaczcie jeszcze na to:
op = &Euclid.op/1
op.({:add, 1, 10})
Fajne? No właśnie, zaczyna być z tego super fajna zabawa. & to operator przypisania (capture, ale po polsku pojmanie jakoś mi nie pasuje) funkcji.
Strażnicy
Czasami proste wzorce nie wystarczają i musimy zrobić coś więcej – przy dzieleniu przez 0 nie dopóścic do operacji na przykład. Można to zrobić za pomocą strażników (guards) których dodajemy do funkcji przez słowo kluczowe when:
defmodule Euclid do
def div(a, b) when b > 0 do
a / b
end
def div(a, b) do
{:error, "Pamiętaj cholero nigdy nie dziel przez zero"}
end
end
Proste? Proste, bardzo fajne i przyjemne. Oczywiście możemy to bardziej skomplikować:
defmodule Euclid do
def div(a, b) when b > 0 and a > 10 do
a / b
end
def div(a, b) when is_number(b) do
{:error, "Pamiętaj cholero nigdy nie dziel przez zero"}
end
end
To samo możemy napisać z wykorzsytaniem lambdy. Podstawowa wyglądała by tak:
div = fn(a,b) -> a/b end div.(2,2)
Z ochroniarzami:
div = fn
a, b when b > 0 -> a/b
a, b -> {:error, "Pamiętaj cholero nigdy nie dziel przez zero"}
end
Jaki piękny zapis! :)
Podsumowanie
Nie pokazałem wszystkiego, to miało tylko pokazać wam, że elixir to ogólnie jeden wielki pattern matching :) Już we własnym zakresie proponuje dowiedzieć się o wzorcu ^, który umożliwia nam oczekiwać pewną wartość w danym miejscu.
Warto jednak wiedzieć jak wygląda dopasowanie wzorcowe w Elixir i kiedy ono zachodzi oraz jakie z tego możemy czerpać korzyści. Jak widzicie, wzorce są nieodłączną częścią Elixir zaczynają od operatora = kończąc na zaawansowanych wzorcach polimorfizmu funkcji ze strażnikami.
Więc wiedząc już jak działają wzorce i jak można napisać lambdę, małe zadanie :) kto napiszę ciąg Fibonacciego ? :) Czysta frajda co nie? :)
A za tydzień sam nie wiem :) ciągle mnie te moduły po głowie chodzą i operator pipeline |>. Zobaczymy! :)















[…] najlepiej robić od szczegółu do ogółu – tak jak w MVC i routes. Dodatkowo, tak jak przy pattern matching, istnieje możliwość dodawania strażników (jeżeli strażnik wywala błąd, dany wzorzec zwraca […]
Comments are closed.