Se confrontiamo i tipi
(<*>) :: Applicative a => a (s -> t) -> a s -> a t
(>>=) :: Monad m => m s -> (s -> m t) -> m t
abbiamo un'idea di cosa separa i due concetti. Quello (s -> m t)
nel tipo di (>>=)
mostra che un valore in s
può determinare il comportamento di un calcolo in m t
. Le monadi consentono l'interferenza tra il valore e gli strati di calcolo. L' (<*>)
operatore non consente tale interferenza: i calcoli di funzioni e argomenti non dipendono dai valori. Questo morde davvero. Confrontare
miffy :: Monad m => m Bool -> m x -> m x -> m x
miffy mb mt mf = do
b <- mb
if b then mt else mf
che utilizza il risultato di un effetto per decidere tra due calcoli (ad esempio il lancio di missili e la firma di un armistizio), mentre
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
che usa il valore di ab
per scegliere tra i valori di due calcoli at
e af
, avendoli effettuati entrambi, forse con effetto tragico.
La versione monadica si basa essenzialmente sul potere extra di (>>=)
scegliere un calcolo da un valore, e questo può essere importante. Tuttavia, supportare quel potere rende le monadi difficili da comporre. Se proviamo a costruire un "doppio legame"
(>>>>==) :: (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 ???
siamo arrivati fin qui, ma ora i nostri strati sono tutti confusi. Abbiamo un n (m (n t))
, quindi dobbiamo sbarazzarci dell'esterno n
. Come dice Alexandre C, possiamo farlo se ne abbiamo uno adatto
swap :: n (m t) -> m (n t)
permutare l' n
interno e join
questo verso l'altro n
.
La "doppia applicazione" più debole è molto più facile da definire
(<<**>>) :: (Applicative a, Applicative b) => a (b (s -> t)) -> a (b s) -> a (b t)
abf <<**>> abs = pure (<*>) <*> abf <*> abs
perché non c'è interferenza tra gli strati.
Di conseguenza, è bene riconoscere quando hai davvero bisogno della potenza extra di Monad
s e quando puoi farla franca con la rigida struttura di calcolo che Applicative
supporta.
Nota, a proposito, che sebbene comporre le monadi sia difficile, potrebbe essere più del necessario. Il tipo m (n v)
indica il calcolo con m
-effects, quindi il calcolo con -effects n
a v
-value, dove gli m
-effects terminano prima che n
inizino gli -effects (da qui la necessità di swap
). Se vuoi solo intercalare m
-effetti con n
-effetti, allora la composizione è forse troppo da chiedere!