Если сравнить типы
(<*>) :: Applicative a => a (s -> t) -> a s -> a t
(>>=) :: Monad m => m s -> (s -> m t) -> m t
мы понимаем, что разделяет эти два понятия. То, что (s -> m t)в типе (>>=)показывает, что значение в sможет определять поведение вычисления в m t. Монады допускают взаимодействие между слоями значений и вычислений. (<*>)Оператор не допускает таких помех: функция и аргумент вычисления не зависят от значений. Это действительно кусается. Сравнить
miffy :: Monad m => m Bool -> m x -> m x -> m x
miffy mb mt mf = do
b <- mb
if b then mt else mf
который использует результат некоторого эффекта для выбора между двумя вычислениями (например, запуск ракет и подписание перемирия), тогда как
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
который использует значение abдля выбора между значениями двух вычислений atи af, выполнив оба, возможно, к трагическим последствиям.
Монадическая версия в основном полагается на дополнительную (>>=)возможность выбора вычисления из значения, и это может быть важно. Однако поддержка этой силы затрудняет сочинение монад. Если мы попытаемся построить двойную привязку
(>>>>==) :: (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 ???
мы зашли так далеко, но теперь наши слои перемешаны. У нас есть n (m (n t)), поэтому нам нужно избавиться от внешнего n. Как говорит Александр С, мы можем это сделать, если у нас есть подходящий
swap :: n (m t) -> m (n t)
переставить nвнутрь и joinего на другое n.
Более слабое «двойное применение» гораздо легче определить
(<<**>>) :: (Applicative a, Applicative b) => a (b (s -> t)) -> a (b s) -> a (b t)
abf <<**>> abs = pure (<*>) <*> abf <*> abs
потому что нет интерференции между слоями.
Соответственно, хорошо понимать, когда вам действительно нужна дополнительная мощность Monads, а когда можно обойтись жесткой структурой вычислений, которая Applicativeподдерживает.
Заметьте, кстати, что, хотя составление монад сложно, это может быть больше, чем вам нужно. Тип m (n v)указывает вычисление с m-effects, затем вычисление с n-effects до v-value, где m-effects заканчиваются до начала n-effects (отсюда необходимость swap). Если вы просто хотите чередовать m-effects с n-effects, то композиция - это слишком много, чтобы просить!