Если сравнить типы
(<*>) :: 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
потому что нет интерференции между слоями.
Соответственно, хорошо понимать, когда вам действительно нужна дополнительная мощность Monad
s, а когда можно обойтись жесткой структурой вычислений, которая Applicative
поддерживает.
Заметьте, кстати, что, хотя составление монад сложно, это может быть больше, чем вам нужно. Тип m (n v)
указывает вычисление с m
-effects, затем вычисление с n
-effects до v
-value, где m
-effects заканчиваются до начала n
-effects (отсюда необходимость swap
). Если вы просто хотите чередовать m
-effects с n
-effects, то композиция - это слишком много, чтобы просить!