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 znaczeniureturn
, które w F# jest słowem kluczowym(>>=) x f
- bind-
map f x
- dlax
będącego Functorem, aplikujemy funkcjęf
do wartości w środkux
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>
- gdzief
to pewien Functor- Implementacja
Monad
iFunctor
dla wbudowanych typówOption
,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.