Si comparamos los tipos
(<*>) :: Applicative a => a (s -> t) -> a s -> a t
(>>=) :: Monad m => m s -> (s -> m t) -> m t
tenemos una pista de lo que separa los dos conceptos. Que (s -> m t)en el tipo de (>>=)muestra que un valor en spuede determinar el comportamiento de un cálculo en m t. Las mónadas permiten la interferencia entre las capas de valor y de cálculo. El (<*>)operador no permite tal interferencia: los cálculos de la función y el argumento no dependen de los valores. Esto realmente muerde. Comparar
miffy :: Monad m => m Bool -> m x -> m x -> m x
miffy mb mt mf = do
b <- mb
if b then mt else mf
que utiliza el resultado de algún efecto para decidir entre dos cálculos (por ejemplo, lanzar misiles y firmar un armisticio), mientras 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
que utiliza el valor de abpara elegir entre los valores de dos cálculos aty af, habiendo realizado ambos, quizás con efecto trágico.
La versión monádica se basa esencialmente en el poder adicional de (>>=)elegir un cálculo a partir de un valor, y eso puede ser importante. Sin embargo, apoyar ese poder hace que las mónadas sean difíciles de componer. Si intentamos construir 'doble vínculo'
(>>>>==) :: (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 ???
llegamos hasta aquí, pero ahora nuestras capas están revueltas. Tenemos un n (m (n t)), así que tenemos que deshacernos del exterior n. Como dice Alexandre C, podemos hacer eso si tenemos un
swap :: n (m t) -> m (n t)
permutar el ninterior y joinel otro n.
La 'doble aplicación' más débil es mucho más fácil de definir
(<<**>>) :: (Applicative a, Applicative b) => a (b (s -> t)) -> a (b s) -> a (b t)
abf <<**>> abs = pure (<*>) <*> abf <*> abs
porque no hay interferencia entre las capas.
En consecuencia, es bueno reconocer cuándo realmente necesita la potencia adicional de Monads, y cuándo puede salirse con la suya con la rígida estructura de cálculo que Applicativeadmite.
Tenga en cuenta, por cierto, que aunque componer mónadas es difícil, puede ser más de lo que necesita. El tipo m (n v)indica computación con m-effects, luego computación con n-effects a un v-valor, donde los m-effects terminan antes de que ncomiencen los -effects (de ahí la necesidad de swap). Si solo desea intercalar m-efectos con n-efectos, entonces la composición es quizás demasiado pedir.