Аппликативные сочинения, монады нет - PullRequest
101 голосов
/ 12 августа 2011

Аппликаторы составляют, монады - нет.

Что означает приведенное выше утверждение?А когда один предпочтительнее другого?

Ответы [ 5 ]

108 голосов
/ 12 августа 2011

Если мы сравним типы

(<*>) :: 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 -эффектами, то, возможно, композиции слишком много, чтобы спрашивать!

71 голосов
/ 16 августа 2011

Аппликаторы составляют, монады нет.

Монады делают , но результат может быть не монадойНапротив, композиция из двух аппликаций обязательно является аппликативной.Я подозреваю, что первоначальное утверждение заключалось в том, что «Аппликативность составляет, а монадность - нет».Перефразируя, «Applicative закрыта под композицией, а Monad - нет».

38 голосов
/ 12 августа 2011

Если у вас есть аппликативы A1 и A2, то тип data A3 a = A3 (A1 (A2 a)) также является аппликативным (вы можете написать такой экземпляр в общем виде). ​​

С другой стороны, если у вас есть монады M1 и M2, то тип data M3 a = M3 (M1 (M2 a)) не обязательно является монадой (не существует разумной универсальной реализации для >>= или join для композиции).

Одним из примеров может быть тип [Int -> a] (здесь мы создаем конструктор типа [] с (->) Int, оба из которых являются монадами). Вы можете легко написать

app :: [Int -> (a -> b)] -> [Int -> a] -> [Int -> b]
app f x = (<*>) <$> f <*> x

И это обобщает для любого аппликативного:

app :: (Applicative f, Applicative f1) => f (f1 (a -> b)) -> f (f1 a) -> f (f1 b)

Но толкового определения

нет
join :: [Int -> [Int -> a]] -> [Int -> a]

Если вы не уверены в этом, подумайте о следующем выражении:

join [\x -> replicate x (const ())]

Длина возвращаемого списка должна быть задана в камне, прежде чем будет предоставлено целое число, но его правильная длина зависит от целого числа, которое предоставляется. Таким образом, для этого типа не может быть правильной функции join.

17 голосов
/ 12 августа 2011

К сожалению, наша настоящая цель, составление монад, гораздо более сложно. .. На самом деле, мы может на самом деле доказать, что в определенном смысле нет никакого способа построить функцию соединения с типом выше, используя только операции двух монад (см. приложение для описания доказательство). Отсюда следует, что единственный способ, которым мы могли бы надеяться сформировать композиция, если есть некоторые дополнительные конструкции, связывающие два компонента.

Составляющие монады, http://web.cecs.pdx.edu/~mpj/pubs/RR-1004.pdf

7 голосов
/ 16 августа 2011

Распределительное правовое решение l: MN -> NM достаточно

для гарантии монадности ЯМ. Чтобы увидеть это вам нужен юнит и мульт. я сосредоточусь на мульт (единица измерения unit_N unitM)

NMNM - l -> NNMM - mult_N mult_M -> NM

Это не гарантирует, что MN является монадой.

Важное замечание, однако, вступает в игру, когда у вас есть решения с распределенным правом

l1 : ML -> LM
l2 : NL -> LN
l3 : NM -> MN

Таким образом, LM, LN и MN являются монадами. Возникает вопрос, является ли LMN монадой (либо

(MN) L -> L (MN) или N (LM) -> (LM) N

У нас достаточно структур, чтобы сделать эти карты. Однако, поскольку Евгения Ченг наблюдает , нам необходимо гексагональное условие (которое представляет собой представление уравнения Янга-Бакстера), чтобы гарантировать монадность любой конструкции. Фактически, с гексагональным условием две разные монады совпадают.

...