Se compararmos os tipos
(<*>) :: Applicative a => a (s -> t) -> a s -> a t
(>>=) :: Monad m => m s -> (s -> m t) -> m t
temos uma pista do que separa os dois conceitos. Isso (s -> m t)no tipo de (>>=)mostra que um valor em spode determinar o comportamento de um cálculo em m t. Mônadas permitem interferência entre as camadas de valor e computação. O (<*>)operador não permite essa interferência: os cálculos da função e do argumento não dependem de valores. Isso realmente incomoda. 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 usa o resultado de algum efeito para decidir entre dois cálculos (por exemplo, lançar mísseis e assinar um armistício), enquanto
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 usa o valor de abpara escolher entre os valores de dois cálculos ate af, tendo realizado ambos, talvez com efeito trágico.
A versão monádica depende essencialmente do poder extra de (>>=)escolher um cálculo a partir de um valor, e isso pode ser importante. No entanto, apoiar esse poder torna as mônadas difíceis de compor. Se tentarmos construir 'double-bind'
(>>>>==) :: (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 ???
chegamos até aqui, mas agora nossas camadas estão todas misturadas. Temos um n (m (n t)), então precisamos nos livrar do exterior n. Como diz Alexandre C, podemos fazer isso se tivermos um adequado
swap :: n (m t) -> m (n t)
permutar o ninterior e joino outro n.
O mais fraco 'dupla aplicação' é muito mais fácil de definir
(<<**>>) :: (Applicative a, Applicative b) => a (b (s -> t)) -> a (b s) -> a (b t)
abf <<**>> abs = pure (<*>) <*> abf <*> abs
porque não há interferência entre as camadas.
Da mesma forma, é bom reconhecer quando você realmente precisa da potência extra de Monads e quando pode se safar com a estrutura de computação rígida que o Applicativesuporta.
Observe, a propósito, que embora compor mônadas seja difícil, pode ser mais do que você precisa. O tipo m (n v)indica computação com m-effects e, em seguida, computação com n-effects para um -value v, em que m-effects termina antes de n-effects iniciar (daí a necessidade de swap). Se você deseja apenas intercalar m-effects com n-effects, então composição talvez seja pedir demais!