W zeszłym tygodniu w elixir zajęliśmy się GenServer, przez co wspomniałem o behaviours i o makrze use. Dziś pora się temu przyjrzeć. Zacznę o behaviours i zobaczymy ile czasu mi na to zejdzie :( święta się zbliżają i z godziny na to mam jedynie 20 minut. Jak się uda to jeszcze dzisiaj będzie o makrze use bo to tak naprawdę działa najlepiej jako całość ;)
Bahaviours to naprawdę interfejs – ciutkę bardziej zaawansowany, ale wciąż interfejs. Niestety przy wykorzystaniu behaviours trzeba przestrzegać pewnych zasad które normalnie możemy olać w trakcie pisania aplikacji w elixir – przez co jest trochę nowości.
Deklaracja
Behaviours deklarujemy za pomocą makra @callback, wygląda to mniej więcej tak:
@callback say_hi() :: any
Gdzie:
@callback– to dyrektywa wymagana, przed każdą metodą, którą chcemy by była traktowana jako metoda w interfejsie – czyli taka, która trzeba zaimplementować.::– to dość ciekawy operator (nawet nie wiem jak to nazwać), ogólnie znaczy on “danego typu” ale ma możliwość on być trochę bardziej zaawansowany na przykład może wykorzystywać pattern matching.any– to dowolny typ danych
Teraz, na przykład możemy zrobić tak by nasza metoda w interfejsie pozwała zwrócić jedynie określony set
@callback say_hi_again() ::
{ :yes, term :: String} |
{ :no }
To mówi nam, że metoda say_hi_again może zwrócić dwie różne wartości:
{ :yes, "some_string" }
{ :no }
Jednak nie ma to znaczenia – jest to bardziej informacja dla kogoś zewnątrz. Elixir tego nie wychwyci jak błąd – nie ma on dobrej walidacji.
Jeżeli chcemy dodać więcej możliwości zwracanych wartości to dodajmy je po operatorze | – nawet nie wiem jak on się tutaj nazywa. Ale ogólnie wygląda on na takie lub w tym konkretnym przypadku.
Deklaracja typów nie tylko tyczy się tego co zwracamy, ale także tego co przekazujemy jako parametr:
@callback say_hello_to(person :: String) :: any
Gdzie jedynie person musi być ciągiem znaków. Jeżeli nie chcemy podawać typu parametru to wtedy musimy zastosować trick, nasz parametr musi spełnić oczekiwany wzorzec:
@callback say_hello_to(age, person :: String) ::
any when age: 10
Na sam koniec, możemy określić które z metod nie muszą być zaimplementowane, możemy to zrobić za pomocą dyrektywy @optional_callbacks. I tak dla przykładu:
@optional_callbacks say_hello_to: 2
Spowoduje, że nasz przykład say_hello_to(age, person) będzie opcjonalny do zaimplementowania.
Implementacja
Mając zadeklarowane behaviours pora je zaimplementować. W tym celu wykorzystujemy dyrektywę @behaviour:
defmodule MyBehaviourImpl do @behaviour Hello end
Teraz musimy zaimplementować wszystkie te metody, które nie były opcjonalne. Nie zaimplementowanie, będzie równe ze zwróceniem warningu w iex:
warning: undefined behaviour function say_hi_again/0 (for behaviour Hello)
Zaś błędna deklaracja implementacji metody, spowoduje błąd kompilacji:
** (CompileError) test.exs:28: cannot invoke local ::/2 inside match, called as: age :: String (stdlib) lists.erl:1353: :lists.mapfoldl/3
Trzeba go jednak popełnić albo intencjonalnie albo kompletnie przez przypadek.
Podsumowanie
Bechaviours nie są takie trudne jak się wydają. Dość prosto się je piszę i jest to dość zrozumiałe. Prosta konstrukcja, przejrzysty kod. Trochę bardziej explicit niż implicit, ale może nawet i dobrze. Przynajmniej deklarujemy je i implementujemy intencjonalnie a nie przez przypadek ;) To co jednak mi się nie podoba to walidacja tych behaviours. Niby jak interfejsy ale nie aż tak bardzo.
Z chęcią bym zobaczył walidację poprawności zwracanych danych (ciężkie, wiem), ale inaczej nie wiem czemu muszę podawać typ danych.
Nie udało mi się już czasowo poruszyć use i dlaczego to idzie ładnie w parze z behaviours ale co się odwlecze to nie uciecze :)















[…] To samo co tutaj wykorzystujemy w @spec możemy wykorzystać w @callback (tutaj więcej info). […]
Comments are closed.