Jeśli porównamy typy
(<*>) :: Applicative a => a (s -> t) -> a s -> a t
(>>=) :: Monad m => m s -> (s -> m t) -> m t
otrzymujemy wskazówkę, co oddziela te dwa pojęcia. To (s -> m t)
w typie (>>=)
pokazuje, że wartość w s
może określać zachowanie obliczenia w m t
. Monady pozwalają na interferencję między warstwami wartości i obliczeń. (<*>)
Operator nie umożliwia takich zakłóceń: Działanie i argumentów obliczenia nie zależy od wartości. To naprawdę gryzie. Porównać
miffy :: Monad m => m Bool -> m x -> m x -> m x
miffy mb mt mf = do
b <- mb
if b then mt else mf
który wykorzystuje wynik jakiegoś efektu do wyboru między dwoma obliczeniami (np. wystrzelenie rakiet i podpisanie rozejmu), podczas gdy
iffy :: Applicative a => a Bool -> a x -> a x -> a x
iffy ab at af = pure cond <*> ab <*> at <*> af where
cond b t f = if b then t else f
która używa wartości ab
do wyboru między wartościami dwóch obliczeń at
i af
, po przeprowadzeniu obu, być może z tragicznym skutkiem.
Wersja monadyczna polega zasadniczo na dodatkowej mocy (>>=)
wyboru obliczeń z wartości, a to może być ważne. Jednak wspieranie tej mocy utrudnia komponowanie monad. Jeśli spróbujemy zbudować „podwójne wiązanie”
(>>>>==) :: (Monad m, Monad n) => m (n s) -> (s -> m (n t)) -> m (n t)
mns >>>>== f = mns >>-{-m-} \ ns -> let nmnt = ns >>= (return . f) in ???
dotarliśmy tak daleko, ale teraz wszystkie nasze warstwy są pomieszane. Mamy n (m (n t))
, więc musimy pozbyć się zewnętrznego n
. Jak mówi Alexandre C, możemy to zrobić, jeśli mamy odpowiedni
swap :: n (m t) -> m (n t)
permutować do n
wewnątrz i join
do drugiego n
.
Słabsze „podwójne zastosowanie” jest znacznie łatwiejsze do zdefiniowania
(<<**>>) :: (Applicative a, Applicative b) => a (b (s -> t)) -> a (b s) -> a (b t)
abf <<**>> abs = pure (<*>) <*> abf <*> abs
ponieważ nie ma interferencji między warstwami.
W związku z tym dobrze jest rozpoznać, kiedy naprawdę potrzebujesz dodatkowej mocy Monad
s i kiedy możesz uciec ze sztywną strukturą obliczeniową, która Applicative
obsługuje.
Zwróć uwagę, że chociaż komponowanie monad jest trudne, może to być więcej niż potrzebujesz. Typ m (n v)
wskazuje na obliczanie z m
-efektami, a następnie obliczanie z n
-efekty do -wartości v
, gdzie m
-efekty kończą się przed n
rozpoczęciem -efekty (stąd potrzeba swap
). Jeśli chcesz po prostu przeplatać m
-efekty z n
-efektami, to o skład może zbyt wiele prosić!