简体   繁体   English

应用程序组成,单子不组成

[英]Applicatives compose, monads don't

Applicatives compose, monads don't.应用程序组成,单子不组成。

What does the above statement mean?上面的说法是什么意思? And when is one preferable to other?什么时候一个比另一个更可取?

If we compare the types如果我们比较类型

(<*>) :: Applicative a => a (s -> t) -> a s -> a t
(>>=) :: Monad m =>       m s -> (s -> m t) -> m t

we get a clue to what separates the two concepts.我们得到了区分这两个概念的线索。 That (s -> mt) in the type of (>>=) shows that a value in s can determine the behaviour of a computation in mt . (>>=)类型中的(s -> mt)表明s中的值可以确定mt中计算的行为。 Monads allow interference between the value and computation layers.单子允许值层和计算层之间的干扰。 The (<*>) operator allows no such interference: the function and argument computations don't depend on values. (<*>)运算符不允许这样的干扰:function 和参数计算不依赖于值。 This really bites.这真是咬人。 Compare相比

miffy :: Monad m => m Bool -> m x -> m x -> m x
miffy mb mt mf = do
  b <- mb
  if b then mt else mf

which uses the result of some effect to decide between two computations (eg launching missiles and signing an armistice), whereas它使用某种效果的结果来决定两种计算(例如发射导弹和签署停战协议),而

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

which uses the value of ab to choose between the values of two computations at and af , having carried out both, perhaps to tragic effect.它使用ab的值ataf的两个计算值之间进行选择,同时执行了这两个计算,可能会产生悲剧性的效果。

The monadic version relies essentially on the extra power of (>>=) to choose a computation from a value, and that can be important. monadic 版本本质上依赖于(>>=)的额外功能来从值中选择计算,这很重要。 However, supporting that power makes monads hard to compose.然而,支持这种权力使得单子难以组合。 If we try to build 'double-bind'如果我们尝试构建“双重绑定”

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

we get this far, but now our layers are all jumbled up.我们已经走到了这一步,但现在我们的图层都混乱了。 We have an n (m (nt)) , so we need to get rid of the outer n .我们有一个n (m (nt)) ,所以我们需要去掉外部的n As Alexandre C says, we can do that if we have a suitable正如 Alexandre C 所说,如果我们有合适的

swap :: n (m t) -> m (n t)

to permute the n inwards and join it to the other n .向内排列n并将其join到另一个n

The weaker 'double-apply' is much easier to define较弱的“双重应用”更容易定义

(<<**>>) :: (Applicative a, Applicative b) => a (b (s -> t)) -> a (b s) -> a (b t)
abf <<**>> abs = pure (<*>) <*> abf <*> abs

because there is no interference between the layers.因为层与层之间没有干扰。

Correspondingly, it's good to recognize when you really need the extra power of Monad s, and when you can get away with the rigid computation structure that Applicative supports.相应地,当您真正需要Monad的额外功能以及何时可以摆脱Applicative支持的刚性计算结构时,这是很好的。

Note, by the way, that although composing monads is difficult, it might be more than you need.顺便提一下,尽管编写 monad 很困难,但它可能比你需要的要多。 The type m (nv) indicates computing with m -effects, then computing with n -effects to a v -value, where the m -effects finish before the n -effects start (hence the need for swap ).类型m (nv)表示使用m -effects 计算,然后使用n -effects 计算到v -value,其中m -effects 在n -effects 开始之前完成(因此需要swap )。 If you just want to interleave m -effects with n -effects, then composition is perhaps too much to ask!如果您只想将m -effects 与n -effects 交错,那么组合可能太难了!

Applicatives compose, monads don't.应用程序组成,单子不组成。

Monads do compose, but the result might not be a monad.单子确实可以组合,但结果可能不是单子。 In contrast, the composition of two applicatives is necessarily an applicative.相反,两个应用程序的组合必然是一个应用程序。 I suspect the intention of the original statement was that "Applicativeness composes, while monadness doesn't."我怀疑原始陈述的意图是“应用性构成,而单子性不构成”。 Rephrased, " Applicative is closed under composition, and Monad is not."改写为,“ Applicative在作文下是封闭的,而Monad不是。”

If you have applicatives A1 and A2 , then the type data A3 a = A3 (A1 (A2 a)) is also applicative (you can write such an instance in a generic way).如果您有应用程序A1A2 ,那么类型data A3 a = A3 (A1 (A2 a))也是适用的(您可以以通用方式编写这样的实例)。

On the other hand, if you have monads M1 and M2 then the type data M3 a = M3 (M1 (M2 a)) is not necessarily a monad (there is no sensible generic implementation for >>= or join for the composition).另一方面,如果您有单子M1M2 ,则类型data M3 a = M3 (M1 (M2 a))不一定是单子( >>=或组合的join没有合理的通用实现)。

One example can be the type [Int -> a] (here we compose a type constructor [] with (->) Int , both of which are monads).一个例子可以是类型[Int -> a] (这里我们用(->) Int组合了一个类型构造函数[] ,它们都是单子)。 You can easily write您可以轻松编写

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

And that generalizes to any applicative:这可以推广到任何应用程序:

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

But there is no sensible definition of但是没有合理的定义

join :: [Int -> [Int -> a]] -> [Int -> a]

If you're unconvinced of this, consider this expression:如果您不相信这一点,请考虑以下表达式:

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

The length of the returned list must be set in stone before an integer is ever provided, but the correct length of it depends on the integer that's provided.在提供 integer 之前,必须确定返回列表的长度,但它的正确长度取决于提供的 integer。 Thus, no correct join function can exist for this type.因此,对于这种类型,不存在正确的join function。

Unfortunately, our real goal, composition of monads, is rather more difficult.不幸的是,我们真正的目标,单子的组合,是相当困难的。 .. In fact, we can actually prove that, in a certain sense, there is no way to construct a join function with the type above using only the operations of the two monads (see the appendix for an outline of the proof). .. 事实上,我们实际上可以证明,在某种意义上,仅使用两个 monad 的操作是无法构造具有上述类型的连接 function 的(参见附录中的证明大纲)。 It follows that the only way that we might hope to form a composition is if there are some additional constructions linking the two components.因此,我们可能希望形成一个组合的唯一方法是,如果有一些额外的结构连接这两个组件。

Composing monads, http://web.cecs.pdx.edu/~mpj/pubs/RR-1004.pdf组成单子, http://web.cecs.pdx.edu/~mpj/pubs/RR-1004.pdf

The distributive law solution l: MN -> NM is enough分配律解 l: MN -> NM 就够了

to guarantee monadicity of NM.以保证 NM 的一元性。 To see this you need a unit and a mult.要看到这一点,您需要一个单元和一个 mult。 i'll focus on the mult (the unit is unit_N unitM)我将专注于 mult(单位是 unit_N unitM)

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

This does not guarantee that MN is a monad.这并不能保证 MN 是一个单子。

The crucial observation however, comes into play when you have distributive law solutions然而,当你有分配律解决方案时,关键的观察就会发挥作用

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

thus, LM, LN and MN are monads.因此,LM、LN 和 MN 是单子。 The question arises as to whether LMN is a monad (either by问题在于 LMN 是否是一个单子(通过

(MN)L -> L(MN) or by N(LM) -> (LM)N (MN)L -> L(MN) 或按 N(LM) -> (LM)N

We have enough structure to make these maps.我们有足够的结构来制作这些地图。 However, as Eugenia Cheng observes , we need a hexagonal condition (that amounts to a presentation of the Yang-Baxter equation) to guarantee monadicity of either construction.然而,正如Eugenia Cheng 所观察到的,我们需要一个六边形条件(相当于 Yang-Baxter 方程的表示)来保证任一结构的一元性。 In fact, with the hexagonal condition, the two different monads coincide.事实上,在六边形条件下,两个不同的单子是重合的。

Any two applicative functors can be composed and yield another applicative functor.任何两个应用函子都可以组合并产生另一个应用函子。 But this does not work with monads.但这不适用于单子。 A composition of two monads is not always a monad.两个单子的组合并不总是单子。 For example, a composition of State and List monads (in any order) is not a monad.例如, StateList monads(以任何顺序)的组合不是 monad。

Moreover, one cannot combine two monads in general, whether by composition or by any other method.此外,一般来说,无论是通过组合还是通过任何其他方法,都不能组合两个单子。 There is no known algorithm or procedure that combines any two monads M , N into a larger, lawful monad T so that you can inject M ~> T and N ~> T by monad morphisms and satisfy reasonable non-degeneracy laws (eg, to guarantee that T is not just a unit type that discards all effects from M and N ).没有已知的算法或程序可以将任意两个单子MN组合成一个更大、合法的单子T以便您可以通过单子态射注入M ~> TN ~> T并满足合理的非退化定律(例如,保证T不只是一个单位类型,它丢弃了MN的所有影响)。

It is possible to define a suitable T for specific M and N , for example of M = Maybe and N = State s and so on.可以为特定的MN定义合适的T ,例如M = MaybeN = State s等等。 But it is unknown how to define T that would work parametrically in the monads M and N .但目前尚不清楚如何定义T以在单子MN中参数化地工作。 Neither functor composition, nor more complicated constructions work adequately.函子组合和更复杂的结构都不能充分发挥作用。

One way of combining monads M and N is first, to define the co-product C a = Either (M a) (N a) .组合单子MN的一种方法是首先定义联积C a = Either (M a) (N a) This C will be a functor but, in general, not a monad.这个C将是一个仿函数,但通常不是单子。 Then one constructs a free monad ( Free C ) on the functor C .然后在仿函数C上构造一个自由单子( Free C )。 The result is a monad that is able to represent effects of M and N combined.结果是一个能够表示MN组合效果的 monad。 However, it is a much larger monad that can also represent other effects;然而,它是一个更大的单子,也可以代表其他效果; it is much larger than just a combination of effects of M and N .它比MN的效果组合要大得多。 Also, the free monad will need to be "run" or "interpreted" in order to extract any results (and the monad laws are guaranteed only after "running").此外,为了提取任何结果,需要“运行”或“解释”免费的 monad(并且只有在“运行”之后才能保证 monad 法则)。 There will be a run-time penalty as well as memory size penalty because the free monad will potentially build very large structures in memory before it is "run".将会有运行时损失以及 memory 大小损失,因为自由 monad 在“运行”之前可能会在 memory 中构建非常大的结构。 If these drawbacks are not significant, the free monad is the way to go.如果这些缺点不显着,那么免费的 monad 就是通往 go 的方法。

Another way of combining monads is to take one monad's transformer and apply it to the other monad.组合 monad 的另一种方法是获取一个 monad 的转换器并将其应用于另一个 monad。 But there is no algorithmic way of taking a definition of a monad (eg, type and code in Haskell) and producing the type and code of the corresponding transformer.但是没有算法方法来定义一个 monad(例如,Haskell 中的类型和代码)并产生相应转换器的类型和代码。

There are at least 4 different classes of monads whose transformers are constructed in completely different but regular ways (composed-inside, composed-outside, adjunction-based monad, product monad).至少有 4 种不同类别的 monad,它们的转换器以完全不同但规则的方式构造(内部组合、外部组合、基于附加的 monad、product monad)。 A few other monads do not belong to any of these "regular" classes and have transformers defined "ad hoc" in some way.其他一些 monad 不属于这些“常规”类中的任何一个,并且以某种方式将转换器定义为“临时”。

Distributive laws exist only for composed monads.分配律只存在于组合的单子中。 It is misleading to think that any two monads M , N for which one can define some function M (N a) -> N (M a) will compose.认为可以定义一些 function M (N a) -> N (M a)任何两个单子M , N将组成是误导性的。 In addition to defining a function with this type signature, one needs to prove that certain laws hold.除了使用此类型签名定义 function 之外,还需要证明某些定律成立。 In many cases, these laws do not hold.在许多情况下,这些法律并不成立。

There are even some monads that have two inequivalent transformers;甚至有些单子有两个不等价的转换器; one defined in a "regular" way and one "ad hoc".一个以“常规”方式定义,一个以“临时”方式定义。 A simple example is the identity monad Id a = a ;一个简单的例子是身份单子Id a = a ; it has the regular transformer IdT m = m ("composed") and the irregular "ad hoc" one: IdT2 ma = forall r. (a -> mr) -> m r它具有常规变压器IdT m = m (“组合”)和不规则“临时”变压器: IdT2 ma = forall r. (a -> mr) -> m r IdT2 ma = forall r. (a -> mr) -> m r (the codensity monad on m ). IdT2 ma = forall r. (a -> mr) -> m rm上的共密度单子)。

A more complicated example is the "selector monad": Sel qa = (a -> q) -> a .一个更复杂的例子是“选择器单子”: Sel qa = (a -> q) -> a Here q is a fixed type and a is the main type parameter of the monad Sel q .这里q是一个固定类型, a是 monad Sel q的主要类型参数。 This monad has two transformers: SelT1 ma = (ma -> q) -> ma (composed-inside) and SelT2 ma = (a -> mq) -> ma (ad hoc).这个 monad 有两个转换器: SelT1 ma = (ma -> q) -> ma (composed-inside) 和SelT2 ma = (a -> mq) -> ma (ad hoc)。

Full details are worked out in Chapter 14 of the book "The Science of Functional Programming".完整的细节在“函数式编程的科学”一书的第 14 章中制定。 https://github.com/winitzki/sofp or https://leanpub.com/sofp/ https://github.com/winitzki/sofphttps://leanpub.com/sofp/

Here is some code making monad composition via a distributive law work.这是一些通过分配律工作的代码制作单子组合。 Note that there are distributive laws from any monad to the monads Maybe , Either , Writer and [] .请注意,从任何单子到单子MaybeEitherWriter[]都有分配律。 On the other hand, you won't find such (general) distributive laws into Reader and State .另一方面,您不会在ReaderState中找到这样的(一般)分配规律。 For these, you will need monad transformers.对于这些,您将需要 monad 转换器。

 {-# 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
 
 
   
 

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM