Заявки сочиняют, монады не делают

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

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

Ответ 1

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

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

Монадическая версия в основном полагается на дополнительную мощность (>>=), чтобы выбрать вычисление из значения, и это может быть важно. Однако поддержка этой власти делает монады трудными для сочинения. Если мы попытаемся построить & lsquo; double-bind & rsquo;

(>>>>==) :: (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.

Более слабое "двойное применение & rsquo; намного легче определить

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

Ответ 2

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

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

Ответ 3

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

С другой стороны, если у вас есть монады M1 и M2 то data M3 a = M3 (M1 (M2 a)) типа 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.

Ответ 4

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

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

Ответ 5

Решение дистрибутивного закона   l: MN → NM достаточно

чтобы гарантировать монадичность 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

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