Si nous comparons les types
(<*>) :: Applicative a => a (s -> t) -> a s -> a t
(>>=) :: Monad m => m s -> (s -> m t) -> m t
nous avons une idée de ce qui sépare les deux concepts. Cela (s -> m t)
dans le type de (>>=)
montre qu'une valeur dans s
peut déterminer le comportement d'un calcul dans m t
. Les monades permettent des interférences entre la valeur et les couches de calcul. L' (<*>)
opérateur n'autorise pas de telles interférences: les calculs de fonction et d'argument ne dépendent pas de valeurs. Cela mord vraiment. Comparer
miffy :: Monad m => m Bool -> m x -> m x -> m x
miffy mb mt mf = do
b <- mb
if b then mt else mf
qui utilise le résultat d'un certain effet pour décider entre deux calculs (par exemple le lancement de missiles et la signature d'un armistice), alors que
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
qui utilise la valeur de ab
pour choisir entre les valeurs de deux calculs at
et af
, après avoir effectué les deux, peut-être à un effet tragique.
La version monadique repose essentiellement sur la puissance supplémentaire de (>>=)
choisir un calcul à partir d'une valeur, et cela peut être important. Cependant, soutenir ce pouvoir rend les monades difficiles à composer. Si nous essayons de construire une double liaison
(>>>>==) :: (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 ???
nous arrivons jusqu'ici, mais maintenant nos couches sont toutes mélangées. Nous avons un n (m (n t))
, donc nous devons nous débarrasser de l'extérieur n
. Comme le dit Alexandre C, nous pouvons le faire si nous avons un
swap :: n (m t) -> m (n t)
permuter l' n
intérieur et join
lui à l'autre n
.
La `` double application '' plus faible est beaucoup plus facile à définir
(<<**>>) :: (Applicative a, Applicative b) => a (b (s -> t)) -> a (b s) -> a (b t)
abf <<**>> abs = pure (<*>) <*> abf <*> abs
car il n'y a pas d'interférence entre les couches.
En conséquence, il est bon de reconnaître quand vous avez vraiment besoin de la puissance supplémentaire de Monad
s et quand vous pouvez vous en sortir avec la structure de calcul rigide qui Applicative
prend en charge.
Notez, en passant, que bien que composer des monades soit difficile, cela peut être plus que ce dont vous avez besoin. Le type m (n v)
indique le calcul avec m
-effects, puis le calcul avec n
-effects à une v
-value, où les m
-effects se terminent avant le n
début des -effects (d'où la nécessité de swap
). Si vous voulez juste entrelacer m
-effets avec n
-effects, alors la composition est peut-être trop demander!