Если мы сравним типы
(<*>) :: 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
.
слабее'double-apply' намного проще определить
(<<**>>) :: (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
-эффектами, затем вычисления с n
-эффектами до значения v
, где m
-эффекты заканчиваются до начала n
-эффектов (отсюда необходимостьдля swap
).Если вы просто хотите чередовать m
-эффекты с n
-эффектами, то, возможно, композиции слишком много, чтобы спрашивать!