Monady w F#

Podczas przygotowywania mojego warsztatu na GirlzCamp postanowiłem napisać sobie kilka monad w F#, w tym spróbować napisać transformator monad (co w ogólności mi się nie udało). Podczas moich zmagań natrafiłem na bibliotekę FSharpPlus, która jest mega!!

Przede wszystkim, jednym z większych problemów z jakimi się borykałem to sprawa operatora bind >>=. W Haskellu, dzięki klasom typów, można używać tego operatora do wszystkich monad, które instancjonują klasę Monad. W F# jest to nieco trudniejsze.

Okazuje się, że F# ma dwa typy generycznych argumentów typów. Najczęściej używany to 'T (z apostrofem), czyli typy ogólnie generyczne, zachowujące się tak jak generyki .NETowe. Ale jest jeszcze drugi typ - ^T (z daszkiem), który określa generyczny typ, rozwiązywany w trakcie kompilacji. Może być użyty tylko dla funkcji, które są inline i podczas kompilacji taka funkcja jest zastępowana na wywołanie konretnej funkcji, być może specyficznej dla danego typu.

Odpowiednie użycie funkcji inline i tych argumentów typowych rozwiązywanych w trackie kompilacji, pozwala na utworzenie generycznego operatora >>=, którego można używać dla każdej monady, pod warunkiem, że jej typ monadyczny zawiera

static member (>>=) : '``Monad<'T>`` -> ('T -> '``Monad<'U>``) -> '``Monad<'U>``

Także okazuje się, że F# pozwala na więcej niż by się na pierwszy rzut oka wydawało, ale składnia do tego jest strasznie nieintuicyjna.

W bibliotece FSharpPlus znajdziemy definicje kilku poręcznych funkcji oraz gotowych monad.

  • result x - funkcja o znaczeniu return, które w F# jest słowem kluczowym
  • (>>=) x f - bind
  • map f x - dla x będącego Functorem, aplikujemy funkcję f do wartości w środku x np:

    let h = map ((+) 2) (Some 3) //h = Some 5

  • monad { } - środowisko do pracy z każdą monadą
  • (<*>) : f<'a -> 'b> -> f<'a> -> f<'b> - gdzie f to pewien Functor
  • Implementacja Monad i Functor dla wbudowanych typów Option, Result, Choice
  • Monady State, Writer, Reader, Cont oraz ich transformatory

Więcej inormacji znajdziecie w repozytorium na GitHubie, aczkolwiek, ze względu na to, że spora część tej biblioteki to nieziemskie obejścia, jej kod jest mało czytelny.

Nie mniej jednak jesteśmy w stanie napisać coś takiego (przykład poniżej). Korzystanie z biblioteki wymaga dużej ilości adnotacji typów, żeby kompilator nie narzekał, więc nie jestem pewny czy będę ją wykorzystywał w projektach. Mam nadzieję, że dyskusja o wprowadzeniu klas typów do F# posunie się niedługo do przodu i zobaczymy używalne rozwiązanie.