Przejdź do treści

Moja przygoda z monadami w JavaScript

Kilka lat temu mój kolega z pracy, developer pythona, który to uwielbiał czytać książki techniczne rozwijając przy tym swój wachlarz umiejętności zapytał mnie, osoby która zajmowała się JavaScript, jak to w “moim” języku wyglądają monady.

Baner promujący artykuł

Flame

Pierwsze co pomyślałem, to że znowu zaczyna się flame - czyli kolejna kłótnia, nad wyższością swojego języka programowania. Nie lubię brać udziału w takich dyskusjach, ponieważ zwykle poza zwiększeniem poziomu stresu niczego wartościowego nie doświadczymy.

Niestety zawiodłem, ponieważ nie znałem odpowiedzi na jego pytania.

Wtedy nie wiedziałem czym się charakteryzuje programowanie funkcyjne, tym samym coś takiego jak monady w JavaScript dla mnie nie istniały.

Generalnie po kilkudziesięciu minutach researchu dowiedziałem się, że monady to takie “idiotoodporne” funkcje. Gama metod biblioteki jQuery jest dobrym przykładem, które to nigdy (a raczej rzadko kiedy) rzucą wyjątku. Z takim myśleniem zostałem przez kolejne 2-3 lata.

Po 2 latach bycia niedoinformowanym…

Pewnego dnia mój kolega Michał Kawalec, zaprosił mnie na Monadic Warsaw. Mowa tu dokładnie o 6-tej edycji. Monadic to organizacja, której przyświeca cel rozmawiania o programowaniu funkcyjnym. Postanowiłem, że z miła chęcią się odwdzięczę i opowiem o monadach w JavaScript - doczytam, przygotuję się i wyjdę na scenę.

Poczytałem, przestudiowałem.
Zrobiłem slajdy https://piecioshka.github.io/slides-monads-in-javascript/.
Jestem przygotowany - do dzieła zatem!

Wychodzę na scenę

Przyszedłem na spotkanie. Pełna sala.

Świetnie jest widzieć tylu ludzi, którzy poza swoimi codziennymi obowiązkami przychodzą na spotkania i się rozwijają.

Po wysłuchaniu pierwszej prezentacji poprowadzonej przez Bartosza Milewskiego, który to wyjaśniał teorię kategorii, dowiedziałem się zdecydowanie więcej o monadach niż przypuszczałem.

Dowiedziałem się, że monada to konstruktor typu M z dwiema operacjami: unit, która opakowuje zwykłą wartość w M, oraz bind, która przekazuje opakowaną wartość do funkcji zwracającej kolejne M. Wynik bind to znów M, a nie M zagnieżdżone w M, dzięki czemu operacje można łączyć w łańcuch.

jQuery to nie jest monada

Biblioteka jQuery, o której wcześniej pisałem nie jest więc przykładem na realizację monad w JavaScript.

Poniżej jeden z slajdów z mojej prezentacji:

jQuery to nie monada

Źródła slajdów dostępne tu: https://github.com/piecioshka/slides-monads-in-javascript

Podczas swojego researchu przed prezentacją i kompletowaniem źródeł, z których korzystałem przy tworzeniu slajdów natknąłem się na post https://www.quora.com/Is-jQuery-a-monad.

W pierwszym komentarzu mój poprzednik na scenie wyjaśnił, że wszyscy, którzy uważają jQuery za monadę, żyją w błędzie.

jQuery nie jest monadą, ponieważ funkcja $() sama spłaszcza zagnieżdżenia:

$($($('body'))) jest tym samym co $('body').

Przez takie zachowanie nie da się zapisać unit i bind dla jQuery tak, żeby spełniały prawa monad opisane niżej.

Podczas mojego researchu dowiedziałem się o 3 aksjomatach, które muszą zostać spełnione, aby można było coś nazwać monadą.

3 prawa monad

Przetłumaczyłem opis znajdujący się w projekcie https://github.com/piecioshka/my-monad#the-monadic-laws na język polski:

  • monada jest wraperem na inny typ
  • wszystkie funkcje monad muszą posiadać funkcję do wrapowania samych siebie o inne typy danych
  • wszystkie monady muszą być zdolne aby zasilać wartością, które wrapują, inne funkcje, dopóki one nie zwrócą kolejnej monady

Te punkty opisują jednak budowę monady, a nie jej prawa. Właściwe prawa to trzy równości, które muszą spełniać operacje unit i bind:

  • lewa tożsamość: unit(a).bind(f) daje to samo co f(a)
  • prawa tożsamość: m.bind(unit) daje to samo co m
  • łączność: m.bind(f).bind(g) daje to samo co m.bind(x => f(x).bind(g))

W czym monady pomagają?

Monady mają pomóc w programowaniu i to robią.

Najpopularniejszym przykładem czegoś bardzo podobnego do monady jest obiekt Promise w JavaScript. Ścisłą monadą nie jest, bo then sam spłaszcza zagnieżdżone obietnice i łączy w sobie dwie operacje (map i bind). To dzięki niemu piszemy kod asynchronicznych w sposób bardzo czytelny. Przykład:

function makeSomething(value) {
    return new Promise((resolve, reject) => {
        setTimeout(() => resolve(value), 1000);
    });
}

Promise.resolve(3)
    .then(makeSomething)
    .then((value) => {
        console.debug(value);
    });

Podsumowanie

Temat monad jest bardzo szeroki. Nie opisałem jak samemu stworzyć implementację chociażby Identity Monad, ze względu na to, że istnieje już wiele miejsc gdzie można takie implementację zobaczyć.

Na swoim profilu na GitHubie stworzyłem projekt, z implementacją monad w JavaScript. Zapraszam do odwiedzenia przykładowych testów: https://github.com/piecioshka/my-monad

Aby jeszcze bardziej zapoznać się monadami i ich różnymi typami zapraszam do 11 slajdu mojej prezentacji na Monadic Warsaw https://piecioshka.github.io/slides-monads-in-javascript/#11. Jest tutaj lista podstawowych typów monad jakie występują w przyrodzie.

Bibliografia

Na zakończenie warto zapoznać się ze źródłami, z których czerpałem wiedzę:

Cya! 👋