Co to jest monada w Scali?
W matematyce monada to monoid w kategorii endofunktorów.
Brzmi dość abstrakcyjnie jeśli nie jesteś matematykiem, prawda? Być może jesteś już po lekturze 3 artykułów o działaniu monad, analizie kilku prezentacji wokół ich zastosowania oraz obejrzeniu zarówno krótkich jak i długich tutoriali na YouTube i… masz pojęcie czym są monady, jednak nadal zastanawiasz się nad konkretnym przykładem ich realizacji w kodzie.
Jeśli utożsamiasz się z tym opisem, czytaj dalej. Dziś przedstawię ci metaforę, dzięki której może lepiej wyobrazisz sobie monady. A kto wie, może nawet zapragniesz programować funkcyjnie? 😁 Jedno jest pewne - poznasz prosty przepis na to, jak stworzyć monadę!
Co to jest monada?
Monada to pojęcie matematyczne zaciągnięte wprost z matematycznej teorii kategorii. Języki funkcyjne, takie jak Scala czy Haskell, często korzystają z matematycznych abstrakcji, żeby lepiej modelować problemy, które za ich pomocą rozwiązujemy. Dlatego na przykładzie takich języków najlepiej będzie wytłumaczyć monadę.
Tak w prosty sposób wyjaśnia monadę Jacek Kunicki, nasz Senior Scala Engineer:
W programowaniu funkcyjnym monada to mechanizm umożliwiający wykonywanie obliczeń (reprezentowanych przez funkcje) w sposób sekwencyjny.
Podobnym na pierwszy rzut oka mechanizmem jest funktor (umożliwiający wykonywanie operacji map), jednak pomiędzy monadą a funktorem jest zasadnicza różnica - za chwilę dowiesz się jaka.
Monadę można również traktować jako opakowanie na wartości innych typów. W języku programowania Scala monada F
będąca opakowaniem na jakiś typ A
jest typu F[A]
.
Na monadę składa się:
- funkcja
A => F[A]
, nazywanapure
lubreturn
(rzadziejunit
), pozwalająca opakować dowolną wartość typuA
w monadę, - funkcja
F[A] => (A => F[B]) => F[B]
, nazywanaflatMap
(m. in. w Scali) lubbind
(np. w Haskellu), która umożliwia składanie funkcji zwracających monadę w ciąg sekwencyjnych wywołań.
Definicja interfejsu monady w Scali wyglądałaby następująco:
trait Monad[F[_]]:
def pure[T](t: T): F[T]
def flatMap[T, U](a: F[T])(f: T => F[U]): F[U]
Wróćmy teraz do porównania z funktorem.
Funkcja map
w funktorze F
ma sygnaturę: F[A] => (A => B) => F[B]
. Zauważ, że różnica względem flatMap
występuje w funkcji “wewnętrznej”, zamieniającej wartości typu A
na wartości typu B
. W przypadku map funkcja ta zwraca wartość typu B
, a więc bez opakowania. W przypadku flatMap
typem zwracanym jest F[B]
, a więc wartość typu B
opakowana w monadę F
.
Załóżmy, że mamy pewną funkcję f: A => F[B]
. Gdybyśmy wywołali funkcję F[A].map(f)
, to w wyniku otrzymalibyśmy wartość typu F[F[B]]
, a więc zagnieżdżone opakowanie. Z kolei wywołując F[A].flatMap(f)
, otrzymamy wartość typu F[B]
, czyli bez zagnieżdżonego opakowania - mimo że zaczynaliśmy od wartości w opakowaniu i użyliśmy funkcji zwracającej opakowaną wartość.
To właśnie dzięki takiemu zachowaniu monady umożliwiają składanie wielu funkcji zwracających tę samą monadę w ciąg sekwencyjnych wywołań. Metoda flatMap
odpowiada za wywołanie funkcji zmieniającej wartość oraz za “spłaszczanie” zagnieżdżonych opakowań pojawiających się w toku kolejnych obliczeń.
Jeśli chcesz wdrożyć się jeszcze konkretniej w temat monad w Scali, blog “Monoid in the Category of Endofunctors” na pewno Cię zainteresuje.
Polecam też krótki tutorial naszego CTO, który rozwija temat zastosowania Monad w Scali.
A tymczasem, by wyobrazić sobie monadę jeszcze lepiej, idźmy o krok dalej...
Czym właściwie jest monada? Nie tylko w programowaniu!
W filozofii monada to najbardziej podstawowa lub oryginalna substancja. To jak placek do kebaba dla programisty(-ki). Przyznaj się - kiedy jadłeś(-aś) go ostatnio?
W placek (monadę) zawijamy (opakowujemy) wkład wejściowy: mięso lub/i warzywa (wartości dowolnego typu), otrzymując w ten sposób pysznego kebaba.
Już dziś możesz spróbować przepisu na Placek de la Monada, programując w domu.
Placek de la Monada
- Do miski wsyp 2,5 szklanki mąki i szczyptę soli.
- Dodaj 5 łyżek oliwy i niepełną szklankę gorącej wody.
- Wygniataj.
- Przykryj ścierką, odstaw na pół godziny.
- Podziel ciasto na 5-6 części.
- Wałkuj na cienkie placki.
- Rozgrzej dobrze patelnię.
- Smaż placki na suchej patelni przez około minutę z każdej strony.
- Zbuduj stos z usmażonych placków, przykryj stos pokrywką i poczekaj aż ostygną.
- Pobieraj placki de la Monada ze stosu w kolejności LIFO.
Co dalej? Więcej mięsa (dosłownie!) znajdziesz w Functional Programming Cookbook! Kliknij w poniższy obrazek, by przejść na stronę, gdzie możesz go pobrać.