it-swarm.com.ru

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

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

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

101
missingfaktor

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

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

108
pigworker

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

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

71
Conal

Если у вас есть аппликативные 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.

38
Rotsor

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

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

17
Landei

Решения распределительного закона 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

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

7
user278559