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

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

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


person missingfaktor    schedule 12.08.2011    source источник
comment
Откуда у вас это заявление? Может быть полезно увидеть некоторый контекст.   -  person fuz    schedule 12.08.2011
comment
@FUZxxl: Я неоднократно слышал это от многих разных людей, в последнее время из debasishg в твиттере.   -  person missingfaktor    schedule 12.08.2011
comment
По поводу второго вопроса. Не так много общих структур, которые были бы аппликативными, но не монадными. Аппликатив множественных ошибок в статье Конора Макбрайда и Росса Патерсона - один. Есть некоторые специфические структуры, которые являются аппликативными, а не монадными, где их можно оптимизировать, если они просто аппликативны: комбинаторы синтаксического анализатора Doaiste Swierstra являются одними, а тип Chalkboard Active - другим.   -  person stephen tetley    schedule 12.08.2011
comment
@stephen tetley: Обратите внимание, что многие такие Applicative на самом деле представляют собой целое семейство Monad, а именно по одному для каждой возможной формы структуры. ZipList - это не Monad, а ZipList фиксированной длины. Reader - удобный специальный (или общий?) Случай, когда размер структуры фиксируется как мощность типа среды.   -  person C. A. McCann    schedule 12.08.2011
comment
@ C.A.McCann Все эти быстрые аппликативы (независимо от того, усекают ли они или дополняют) ограничиваются монадами, если вы исправляете форму таким образом, чтобы с точностью до изоморфизма составляла Reader монаду. Как только вы исправляете форму контейнера, он эффективно кодирует функцию с позиций, как памятка. Питер Хэнкок называет такие функторы напериановыми, поскольку они подчиняются законам логарифмов.   -  person pigworker    schedule 12.08.2011
comment
@stephen tetley: другие примеры включают аппликатив константного моноида (который является композицией монад, но не монадой) и аппликатив единичной задержки (который лучше не допускает соединения).   -  person pigworker    schedule 12.08.2011


Ответы (7)


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

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

person pigworker    schedule 12.08.2011
comment
В качестве сомнительного примера вы заявляете, что он использует значение ab для выбора между значениями двух вычислений at и af, выполнив оба вычисления, возможно, с трагическим эффектом. Разве от этого вас не защищает ленивая природа Haskell? Если у меня есть list = (\ btf - ›if b then t else f): [], а затем выполните оператор: list‹ * ›pure True‹ * ›pure hello‹ * ›pure (ошибка плохая) .... I получить привет, и ошибка никогда не возникает. Этот код далеко не так безопасен и не контролируется, как монада, но пост, похоже, предполагает, что аппликативы вызывают строгую оценку. В целом отличный пост! Спасибо! - person shj; 29.02.2012
comment
Вы по-прежнему получаете эффекты обоих, но в чистом виде (плохая ошибка) их нет. Если, с другой стороны, вы попробуете iffy (pure True) (pure hello) (error bad), вы получите ошибку, которую miffy избегает. Более того, если вы попробуете что-то вроде iffy (pure True) (pure 0) [1,2], вы получите [0,0] вместо [0]. У аппликаторов есть своего рода строгость в том, что они создают фиксированные последовательности вычислений, но, как вы заметили, значения, полученные в результате этих вычислений, по-прежнему лениво комбинируются. - person pigworker; 01.03.2012
comment
Верно ли, что для любых монад m и n вы всегда можете написать преобразователь монад mt и работать в n (m t), используя mt n t? Значит, вы всегда можете составлять монады, это просто сложнее, используя трансформаторы? - person ron; 19.06.2012
comment
Такие преобразователи часто существуют, но, насколько мне известно, нет канонического способа их создания. Часто есть настоящий выбор, как разрешить чередующиеся эффекты от разных монад, классическим примером являются исключения и состояния. Должно ли исключение изменить состояние отката или нет? У обоих вариантов есть свое место. Сказав это, есть бесплатная монада, которая выражает произвольное чередование. data Free f x = Ret x | Do (f (Free f x)), затем data (:+:) f g x = Inl (f x) | Tnr (g x), и подумайте о Free (m :+: n). Это задерживает выбор способа выполнения чередования. - person pigworker; 19.06.2012
comment
@pigworker По поводу ленивых / строгих споров. Я думаю, что с помощью аппликативов вы не можете контролировать эффект от внутри вычислений, но эффект-слой вполне может решить не оценивать более поздние значения. Для (прикладных) синтаксических анализаторов это означает, что если синтаксический анализатор выходит из строя раньше, последующие синтаксические анализаторы не оцениваются / не применяются к входным данным. Для Maybe это означает, что раннее Nothing подавит оценку a более позднего / последующего Just a. Это правильно? - person ziggystar; 15.10.2015
comment
@ziggystar Да. Чего не может случиться, так это того, что значения из предыдущих вычислений определяют выбор последующих вычислений. Другое дело, насколько ленивым или строгим может быть выполнение вычислений. - person pigworker; 15.10.2015
comment
Я думал, что понимаю Applicatives, но этот пост избавил меня от моей глупости. - person Arek' Fu; 15.09.2016
comment
@pigworker, как у error "bad" нет никаких эффектов? Я в тупике. Не могли бы вы определить эффекты? - person Arek' Fu; 15.09.2016
comment
@ Arek'Fu, ты пренебрегал "чистым". Это чисто (ошибка плохая), что не имеет никакого эффекта, или что-то чистое в этом отношении. - person ivan vadovič; 06.08.2018

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

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

person Conal    schedule 15.08.2011
comment
Кроме того, любые два аппликатива составляют полностью механически, тогда как монада, образованная композицией двух монад, специфична для этой композиции. - person Apocalisp; 16.08.2011
comment
Более того, монады компонуются другими способами, продукт двух монад - это монада, и только совместные произведения нуждаются в каком-то распределительном законе. - person Edward KMETT; 16.08.2011
comment
С включенным комментарием @Apocalisp это лучший и самый лаконичный ответ. - person Paul Draper; 18.07.2015

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

person Rotsor    schedule 12.08.2011
comment
... так что избегайте монад, когда подойдет функция? - person andrew cooke; 12.08.2011
comment
@andrew, если вы имели в виду функтор, то да, функторы проще и должны использоваться, когда их достаточно. Учтите, что это не всегда. Например, IO без Monad было бы очень сложно запрограммировать. :) - person Rotsor; 12.08.2011

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

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

person Landei    schedule 12.08.2011
comment
Tl; dr для нетерпеливых читателей: вы можете составлять монады, если (f?) Вы можете обеспечить естественное преобразование swap : N M a -> M N a - person Alexandre C.; 12.08.2011
comment
@Alexandre C .: Просто если, я подозреваю. Не все преобразователи монад описываются прямой композицией функторов. Например, ContT r m a не равно ни m (Cont r a), ни Cont r (m a), а StateT s m a примерно равно Reader s (m (Writer s a)). - person C. A. McCann; 12.08.2011
comment
@C. А. Макканн: Кажется, я не могу перейти от (монады M, монады N, монады MN, монады NM) к (существует своп: MN - ›NM natural). Так что давайте пока придерживаемся if (возможно, ответ находится в статье, я должен признаться, что нашел его быстро) - person Alexandre C.; 12.08.2011
comment
@Alexandre C .: Просто указать, что композиции представляют собой монады, в любом случае может быть недостаточно - вам также нужен способ связать две части с целым. Существование swap подразумевает, что композиция позволяет им каким-то образом сотрудничать. Также обратите внимание, что sequence - это особый случай подкачки для некоторых монад. На самом деле это flip. - person C. A. McCann; 12.08.2011
comment
Чтобы написать swap :: N (M x) -> M (N x), мне кажется, что вы можете использовать returns (соответственно fmapped), чтобы вставить M спереди и N сзади, начиная с N (M x) -> M (N (M (N x))), а затем использовать join композита, чтобы получить M (N x). - person pigworker; 12.08.2011
comment
@C. А. Макканн: Я понимаю вашу точку зрения, но не понимаю, почему flip является частным случаем swap. Однако sequence имеет для меня смысл. - person Alexandre C.; 12.08.2011
comment
@Alexandre C .: Думайте об этом как о flip :: (->) a ((->) b c) -> (->) b ((->) a c). Другими словами, swap используется на двух монадах чтения. - person C. A. McCann; 12.08.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) or by N(LM) -> (LM)N

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

person user278559    schedule 16.08.2011
comment
Вероятно, это отличный ответ, но он свист пролетел над моей головой. - person Dan Burton; 16.08.2011
comment
Это потому, что, используя термин Applicative и тег haskell, это вопрос о haskell, но с ответом в другой нотации. - person codeshot; 23.12.2017

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

Более того, невозможно соединить две монады вообще ни композиционно, ни каким-либо другим способом. Не существует известного алгоритма или процедуры, объединяющей любые две монады M, N в более крупную законную монаду T, чтобы вы могли вводить M ~> T и N ~> T морфизмами монад и удовлетворять разумным законам невырожденности (например, чтобы гарантировать, что T не просто тип юнита, который отбрасывает все эффекты от M и N).

Можно определить подходящий T для конкретных M и N, например M = Maybe, N = State s и так далее. Но неизвестно, как определить T, который работал бы параметрически в монадах M и N. Ни композиция функторов, ни более сложные конструкции не работают должным образом.

Один из способов комбинирования монад M и N - сначала определить совместный продукт C a = Either (M a) (N a). Этот C будет функтором, но, в общем, не монадой. Затем строят свободную монаду (Free C) на функторе C. В результате получилась монада, способная представлять комбинированные эффекты M и N. Однако это гораздо более крупная монада, которая также может представлять другие эффекты; это намного больше, чем просто комбинация эффектов M и N. Кроме того, свободную монаду нужно будет запустить или интерпретировать, чтобы извлечь какие-либо результаты (а законы монад гарантируются только после запуска). Будет штраф во время выполнения, а также штраф за размер памяти, потому что свободная монада потенциально будет создавать очень большие структуры в памяти перед запуском. Если эти недостатки несущественны, то можно использовать бесплатную монаду.

Другой способ объединения монад - взять преобразователь одной монады и применить его к другой монаде. Но не существует алгоритмического способа взять определение монады (например, тип и код в Haskell) и создать тип и код соответствующего преобразователя.

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

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

Есть даже некоторые монады, у которых есть два неэквивалентных преобразователя; один определяется обычным образом, а другой - специально. Простым примером является монада идентичности Id a = a; у него есть обычный трансформатор IdT m = m (составной) и нерегулярный специальный: IdT2 m a = forall r. (a -> m r) -> m r (монада кодовой плотности на m).

Более сложный пример - монада селектора: Sel q a = (a -> q) -> a. Здесь q - фиксированный тип, а a - главный параметр типа монады Sel q. Эта монада имеет два преобразователя: SelT1 m a = (m a -> q) -> m a (внутренний) и SelT2 m a = (a -> m q) -> m a (специальный).

Полная информация представлена ​​в главе 14 книги «Наука функционального программирования». https://github.com/winitzki/sofp или https://leanpub.com/sofp/.

person winitzki    schedule 19.05.2021

Вот код, создающий композицию монад с помощью закона распределения. Обратите внимание, что существуют законы распределения от любой монады к монадам Maybe, Either, Writer и []. С другой стороны, вы не найдете таких (общих) законов распределения на Reader и State. Для этого вам понадобятся монадные трансформаторы.

 {-# LANGUAGE FlexibleInstances #-}
 
 module ComposeMonads where
 import Control.Monad
 import Control.Monad.Writer.Lazy
 
 newtype Compose m1 m2 a = Compose { run :: m1 (m2 a) }
 
 instance (Functor f1, Functor f2) => Functor (Compose f1 f2) where
   fmap f = Compose . fmap (fmap f) . run
 
 class (Monad m1, Monad m2) => DistributiveLaw m1 m2 where
   dist :: m2 (m1 a) -> m1 (m2 a)
 
 instance (Monad m1,Monad m2, DistributiveLaw m1 m2)
           => Applicative (Compose m1 m2) where
     pure = return
     (<*>) = ap
 
 instance (Monad m1, Monad m2, DistributiveLaw m1 m2)
           => Monad (Compose m1 m2) where
   return = Compose . return . return
   Compose m1m2a >>= g =
     Compose $ do m2a <- m1m2a -- in monad m1
                  m2m2b <- dist $ do a <- m2a  -- in monad m2
                                     let Compose m1m2b = g a
                                     return m1m2b
                                  -- do ... ::  m2 (m1 (m2 b))
                           -- dist ... :: m1 (m2 (m2 b))          
                  return $ join m2m2b -- in monad m2
 
 instance Monad m => DistributiveLaw m Maybe where
   dist Nothing = return Nothing
   dist (Just m) = fmap Just m
 
 instance Monad m => DistributiveLaw m (Either s) where
   dist (Left s) = return $ Left s
   dist (Right m) = fmap Right m
 
 instance Monad m => DistributiveLaw m [] where
   dist = sequence
 
 instance (Monad m, Monoid w) => DistributiveLaw m (Writer w) where
   dist m = let (m1,w) = runWriter m
            in do a <- m1
                  return $ writer (a,w)
 
 liftOuter :: (Monad m1, Monad m2, DistributiveLaw m1 m2) =>
                    m1 a -> Compose m1 m2 a
 liftOuter = Compose . fmap return
 
 liftInner :: (Monad m1, Monad m2, DistributiveLaw m1 m2) =>
                    m2 a -> Compose m1 m2 a
 liftInner = Compose . return
 
 
   
 
person tillmo    schedule 14.01.2021