简体   繁体   English

Haskell中Monad和Applicative的区别

[英]Difference between Monad and Applicative in Haskell

I just read the following from typeclassopedia about the difference between Monad and Applicative .我刚刚从typeclassopedia 中阅读了以下关于MonadApplicative之间区别的内容。 I can understand that there is no join in Applicative .我可以理解Applicative没有join But the following description looks vague to me and I couldn't figure out what exactly is meant by "the result" of a monadic computation/action.但是下面的描述对我来说看起来很模糊,我无法弄清楚一元计算/动作的“结果”究竟是什么意思。 So, if I put a value into Maybe , which makes a monad, what is the result of this "computation"?那么,如果我将一个值放入Maybe ,它构成了一个 monad,这个“计算”的结果是什么?

Let's look more closely at the type of (>>=).让我们更仔细地看看 (>>=) 的类型。 The basic intuition is that it combines two computations into one larger computation.基本的直觉是它将两个计算组合成一个更大的计算。 The first argument, ma, is the first computation.第一个参数 ma 是第一个计算。 However, it would be boring if the second argument were just an mb;但是,如果第二个参数只是一个 mb 就很无聊了; then there would be no way for the computations to interact with one another (actually, this is exactly the situation with Applicative).那么计算将无法相互交互(实际上,这正是 Applicative 的情况)。 So, the second argument to (>>=) has type a -> mb: a function of this type, given a result of the first computation, can produce a second computation to be run.因此,(>>=) 的第二个参数的类型为 a -> mb:这种类型的函数,给定第一次计算的结果,可以产生要运行的第二次计算。 ... Intuitively, it is this ability to use the output from previous computations to decide what computations to run next that makes Monad more powerful than Applicative. ... 直观地说,正是这种使用先前计算的输出来决定接下来要运行的计算的能力,使 Monad 比 Applicative 更强大。 The structure of an Applicative computation is fixed, whereas the structure of a Monad computation can change based on intermediate results. Applicative 计算的结构是固定的,而 Monad 计算的结构可以根据中间结果而改变。

Is there a concrete example illustrating "ability to use the output from previous computations to decide what computations to run next", which Applicative does not have?是否有一个具体的例子来说明“使用先前计算的输出来决定接下来要运行的计算的能力”,而 Applicative 没有?

My favorite example is the "purely applicative Either". 我最喜欢的例子是“纯粹适用于任何一个”。 We'll start by analyzing the base Monad instance for Either 我们首先分析Either的基础Monad实例

instance Monad (Either e) where
  return = Right
  Left e  >>= _ = Left e
  Right a >>= f = f a

This instance embeds a very natural short-circuiting notion: we proceed from left to right and once a single computation "fails" into the Left then all the rest do as well. 这个例子嵌入了一个非常自然的短路概念:我们从左到右进行,一旦单个计算“失败”进入Left那么所有其余的也会这样做。 There's also the natural Applicative instance that any Monad has 任何Monad都有自然的Applicative实例

instance Applicative (Either e) where
  pure  = return
  (<*>) = ap

where ap is nothing more than left-to-right sequencing before a return : 其中ap只是在return之前从左到右排序:

ap :: Monad m => m (a -> b) -> m a -> m b
ap mf ma = do 
  f <- mf
  a <- ma
  return (f a)

Now the trouble with this Either instance comes to light when you'd like to collect error messages which occur anywhere in a computation and somehow produce a summary of errors. 现在,当您想要收集计算中出现的错误消息并以某种方式产生错误摘要时,这个Either实例的问题就会浮出水面。 This flies in the face of short-circuiting. 面对短路,这种情况很严重。 It also flies in the face of the type of (>>=) 它也面对(>>=)的类型

(>>=) :: m a -> (a -> m b) -> m b

If we think of ma as "the past" and mb as "the future" then (>>=) produces the future from the past so long as it can run the "stepper" (a -> mb) . 如果我们认为ma是“过去”而mb是“未来”那么(>>=)会产生过去的未来,只要它可以运行“步进” (a -> mb) This "stepper" demands that the value of a really exists in the future... and this is impossible for Either . 这个“步进器”要求a的价值确实存在于未来......对于Either这是不可能的。 Therefore (>>=) demands short-circuiting. 因此(>>=) 要求短路。

So instead we'll implement an Applicative instance which cannot have a corresponding Monad . 因此,我们将实现一个不能拥有相应MonadApplicative实例。

instance Monoid e => Applicative (Either e) where
  pure = Right

Now the implementation of (<*>) is the special part worth considering carefully. 现在(<*>)是值得仔细考虑的特殊部分。 It performs some amount of "short-circuiting" in its first 3 cases, but does something interesting in the fourth. 它在前三种情况下执行了一些“短路”,但在第四种情况下做了一些有趣的事情。

  Right f <*> Right a = Right (f a)     -- neutral
  Left  e <*> Right _ = Left e          -- short-circuit
  Right _ <*> Left  e = Left e          -- short-circuit
  Left e1 <*> Left e2 = Left (e1 <> e2) -- combine!

Notice again that if we think of the left argument as "the past" and the right argument as "the future" then (<*>) is special compared to (>>=) as it's allowed to "open up" the future and the past in parallel instead of necessarily needing results from "the past" in order to compute "the future". 再次注意,如果我们将左参数视为“过去”而将右参数视为“未来”,那么(<*>)(>>=)相比是特殊的,因为它允许“打开”未来和过去并行而不是必然需要“过去”的结果来计算“未来”。

This means, directly, that we can use our purely Applicative Either to collect errors, ignoring Right s if any Left s exist in the chain 这直接意味着我们可以使用我们纯粹的Applicative Either收集错误,如果链中存在任何Left ,则忽略Right s

> Right (+1) <*> Left [1] <*> Left [2]
> Left [1,2]

So let's flip this intuition on its head. 让我们把这个直觉放在头上。 What can we not do with a purely applicative Either ? 什么不能用纯粹适用的Either什么? Well, since its operation depends upon examining the future prior to running the past, we must be able to determine the structure of the future without depending upon values in the past. 那么,由于其运作取决于在运行过去之前检查未来,我们必须能够在不依赖于过去的价值的情况下确定未来的结构。 In other words, we cannot write 换句话说,我们不能写

ifA :: Applicative f => f Bool -> f a -> f a -> f a

which satisfies the following equations 满足以下等式

ifA (pure True)  t e == t
ifA (pure False) t e == e

while we can write ifM 虽然我们可以写ifM

ifM :: Monad m => m Bool -> m a -> m a -> m a
ifM mbool th el = do
  bool <- mbool
  if bool then th else el

such that 这样的

ifM (return True)  t e == t
ifM (return False) t e == e

This impossibility arises because ifA embodies exactly the idea of the result computation depending upon the values embedded in the argument computations. 这种不可能性的产生是因为ifA完全体现了结果计算的思想,这取决于参数计算中嵌入的值。

Just 1 describes a "computation", whose "result" is 1. Nothing describes a computation which produces no results. Just 1描述了一个“计算”,其“结果”是1. Nothing描述产生任何结果的计算。

The difference between a Monad and an Applicative is that in the Monad there's a choice. Monad和Applicative之间的区别在于Monad中有一个选择。 The key distinction of Monads is the ability to choose between different paths in computation (not just break out early). Monads的关键区别在于能够在计算中选择不同的路径(不仅仅是提前爆发)。 Depending on a value produced by a previous step in computation, the rest of computation structure can change. 根据前一步计算产生的值,其余的计算结构可以改变。

Here's what this means. 这就是这意味着什么。 In the monadic chain 在monadic链中

return 42            >>= (\x ->
if x == 1
   then
        return (x+1) 
   else 
        return (x-1) >>= (\y -> 
        return (1/y)     ))

the if chooses what computation to construct. if选择构建什么计算。

In case of Applicative, in 如果是Applicative,请参阅

pure (1/) <*> ( pure (+(-1)) <*> pure 1 )

all the functions work "inside" computations, there's no chance to break up a chain. 所有函数都在“内部”计算中工作,没有机会打破链条。 Each function just transforms a value it's fed. 每个函数只是转换它所输入的值。 The "shape" of the computation structure is entirely "on the outside" from the functions' point of view. 从功能的角度来看,计算结构的“形状”完全“在外部”。

A function could return a special value to indicate failure, but it can't cause next steps in the computation to be skipped. 函数可以返回一个特殊值来指示失败,但它不能导致跳过计算中的后续步骤。 They all will have to process the special value in a special way too. 他们都必须以特殊的方式处理特殊价值。 The shape of the computation can not be changed according to received value. 计算的形状不能根据接收的值改变。

With monads, the functions themselves construct computations to their choosing. 对于monad,函数本身构造了他们选择的计算。

Here is my take on @J. 这是我对@J的看法。 Abrahamson's example as to why ifA cannot use the value inside eg (pure True) . Abrahamson的例子,为什么ifA不能使用内部的值,例如(pure True) In essence, it still boils down to the absence of the join function from Monad in Applicative , which unifies the two different perspectives given in typeclassopedia to explain the difference between Monad and Applicative . 从本质上讲,它仍然可以归结为Applicative没有Monadjoin函数,它统一了类词库中给出的两个不同的视角来解释MonadApplicative之间的区别。

So using @J. 所以使用@J。 Abrahamson's example of purely applicative Either : 纯粹应用性的亚伯拉罕的例子Either

instance Monoid e => Applicative (Either e) where
  pure = Right

  Right f <*> Right a = Right (f a)     -- neutral
  Left  e <*> Right _ = Left e          -- short-circuit
  Right _ <*> Left  e = Left e          -- short-circuit
  Left e1 <*> Left e2 = Left (e1 <> e2) -- combine!

(which has similar short-circuiting effect to the Either Monad ), and the ifA function (具有与Either Monad类似的短路效果)和ifA功能

ifA :: Applicative f => f Bool -> f a -> f a -> f a

What if we try to achieve the mentioned equations: 如果我们试图实现上述方程怎么办?

ifA (pure True)  t e == t
ifA (pure False) t e == e

?

Well, as already pointed out, ultimately, the content of (pure True) , cannot be used by a later computation. 好吧,正如已经指出的那样,最终, (pure True)的内容不能用于以后的计算。 But technically speaking, this isn't right. 但从技术上讲,这是不对的。 We can use the content of (pure True) since a Monad is also a Functor with fmap . 我们可以使用(pure True)的内容,因为Monad也是fmapFunctor We can do: 我们可以做的:

ifA' b t e = fmap (\x -> if x then t else e) b

The problem is with the return type of ifA' , which is f (fa) . 问题在于ifA'的返回类型,即f (fa) In Applicative , there is no way of collapsing two nested Applicative S into one. Applicative ,没有办法将两个嵌套的Applicative S折叠成一个。 But this collapsing function is precisely what join in Monad performs. 但是这种崩溃功能恰恰是Monad join功能。 So, 所以,

ifA = join . ifA' 

will satisfy the equations for ifA , if we can implement join appropriately. 如果我们可以适当地实现join ,将满足ifA的方程式。 What Applicative is missing here is exactly the join function. 这里缺少什么Applicativejoin功能。 In other words, we can somehow use the result from the previous result in Applicative . 换句话说,我们可以某种方式使用Applicative先前结果的结果。 But doing so in an Applicative framework will involve augmenting the type of the return value to a nested applicative value, which we have no means to bring back to a single-level applicative value. 但是在Applicative框架中这样做会涉及将返回值的类型扩展为嵌套的应用值,我们无法将其返回到单级应用值。 This will be a serious problem because, eg, we cannot compose functions using Applicative S appropriately. 这将是一个严重的问题,因为,例如,我们不能适当地使用Applicative S组成函数。 Using join fixes the issue, but the very introduction of join promotes the Applicative to a Monad . 使用join修复了这个问题,但是join介绍将Applicative提升为Monad

The key of the difference can be observed in the type of ap vs type of =<< . 可以在ap =<< type =<<的类型中观察到差异的关键。

ap :: m (a->b) -> (m a->m b)
=<< :: (a->m b) -> (m a->m b)

In both cases there is ma , but only in the second case ma can decide whether the function (a->mb) gets applied. 在两种情况下都有ma ,但只有在第二种情况下, ma才能决定是否应用函数(a->mb) In its turn, the function (a->mb) can "decide" whether the function bound next gets applied - by producing such mb that does not "contain" b (like [] , Nothing or Left ). 反过来,函数(a->mb)可以“决定”下一个绑定的函数是否被应用 - 通过生成不包含“ b ”的mb (如[]NothingLeft )。

In Applicative there is no way for functions "inside" m (a->b) to make such "decisions" - they always produce a value of type b . Applicative ,“内部” m (a->b)函数无法做出这样的“决定” - 它们总是产生类型b的值。

f 1 = Nothing -- here f "decides" to produce Nothing
f x = Just x

Just 1 >>= f >>= g -- g doesn't get applied, because f decided so.

In Applicative this is not possible, so can't show a example. Applicative这是不可能的,所以不能显示一个例子。 The closest is: 最接近的是:

f 1 = 0
f x = x

g <$> f <$> Just 1 -- oh well, this will produce Just 0, but can't stop g
                   -- from getting applied

But the following description looks vague to me and I couldn't figure out what exactly is meant by "the result" of a monadic computation/action. 但是下面的描述看起来含糊不清,我无法弄清楚monadic计算/动作的“结果”究竟是什么意思。

Well, that vagueness is somewhat deliberate, because what "the result" is of a monadic computation is something that depends on each type. 嗯,这种模糊性是有些刻意的,因为“结果”是monadic计算的东西取决于每种类型。 The best answer is a bit tautological: the "result" (or results , since there can be more than one) is whatever value(s) the instance's implementation of (>>=) :: Monad m => ma -> (a -> mb) -> mb invokes the function argument with. 最好的答案有点同义反复:“结果”(或结果 ,因为可能不止一个)是实例的实现(>>=) :: Monad m => ma -> (a -> mb) -> mb )的任何值(>>=) :: Monad m => ma -> (a -> mb) -> mb调用函数参数。

So, if I put a value into Maybe , which makes a monad, what is the result of this "computation"? 那么,如果我将一个值放入Maybe ,这就是monad,这个“计算”的结果是什么?

The Maybe monad looks like this: Maybe monad看起来像这样:

instance Monad Maybe where
    return = Just
    Nothing >>= _ = Nothing
    Just a >>= k = k a

The only thing in here that qualifies as a "result" is the a in the second equation for >>= , because it's the only thing that ever gets "fed" to the second argument of >>= . 在这里有资格作为一个“结果”的唯一的事情是a在第二个方程>>= ,因为它是有史以来被“喂”给的第二个参数的唯一的事>>=

Other answers have gone into depth about the ifA vs. ifM difference, so I thought I'd highlight another significant difference: applicatives compose, monads don't . 其他答案已经进入到的深度ifAifM差,所以我想我应该强调的另一个显著差异:applicatives组成,单子没有 With Monad s, if you want to make a Monad that combines the effects of two existing ones, you have to rewrite one of them as a monad transformer. 对于Monad s,如果你想制作一个结合了两个现有效果的Monad ,你必须将其中一个重写为monad变换器。 In contrast, if you have two Applicatives you can easily make a more complex one out of them, as shown below. 相反,如果您有两个Applicatives您可以轻松地制作一个更复杂的Applicatives ,如下所示。 (Code is copypasted from transformers .) (代码是由transformers复制的。)

-- | The composition of two functors.
newtype Compose f g a = Compose { getCompose :: f (g a) }

-- | The composition of two functors is also a functor.
instance (Functor f, Functor g) => Functor (Compose f g) where
    fmap f (Compose x) = Compose (fmap (fmap f) x)

-- | The composition of two applicatives is also an applicative.
instance (Applicative f, Applicative g) => Applicative (Compose f g) where
    pure x = Compose (pure (pure x))
    Compose f <*> Compose x = Compose ((<*>) <$> f <*> x)


-- | The product of two functors.
data Product f g a = Pair (f a) (g a)

-- | The product of two functors is also a functor.
instance (Functor f, Functor g) => Functor (Product f g) where
    fmap f (Pair x y) = Pair (fmap f x) (fmap f y)

-- | The product of two applicatives is also an applicative.
instance (Applicative f, Applicative g) => Applicative (Product f g) where
    pure x = Pair (pure x) (pure x)
    Pair f g <*> Pair x y = Pair (f <*> x) (g <*> y)


-- | The sum of a functor @f@ with the 'Identity' functor
data Lift f a = Pure a | Other (f a)

-- | The sum of two functors is always a functor.
instance (Functor f) => Functor (Lift f) where
    fmap f (Pure x) = Pure (f x)
    fmap f (Other y) = Other (fmap f y)

-- | The sum of any applicative with 'Identity' is also an applicative 
instance (Applicative f) => Applicative (Lift f) where
    pure = Pure
    Pure f <*> Pure x = Pure (f x)
    Pure f <*> Other y = Other (f <$> y)
    Other f <*> Pure x = Other (($ x) <$> f)
    Other f <*> Other y = Other (f <*> y)

Now, if we add in the Constant functor/applicative: 现在,如果我们添加Constant functor / applicative:

newtype Constant a b = Constant { getConstant :: a }

instance Functor (Constant a) where
    fmap f (Constant x) = Constant x

instance (Monoid a) => Applicative (Constant a) where
    pure _ = Constant mempty
    Constant x <*> Constant y = Constant (x `mappend` y)

...we can assemble the "applicative Either " from the other responses out of Lift and Constant : ......我们可以从LiftConstant的其他响应中组合“applicative Either ”:

type Error e a = Lift (Constant e) a

As @Will Ness explains in his answer, the key difference is that with Monads there's a choice between different executions paths at every step.正如@Will Ness 在他的回答中所解释的那样,关键的区别在于 Monads 在每一步都可以在不同的执行路径之间进行选择。 Let's make this potential choice syntactically visible by implementing a function for sequencing four times.让我们通过实现一个用于四次排序的函数来使这个潜在的选择在语法上可见。 Once for applicative f , and then for monad m :一次用于应用f ,然后用于 monad m

seq4A :: Applicative f => f a -> f [a]
seq4A f =
    f <**> (
    f <**> (
    f <**> (
    f <&> (\a1 a2 a3 a4 -> 
        [a1, a2, a3, a4]))))

seq4M :: Monad m => m a -> m [a]
seq4M m =
    m >>= (\a1 ->
    m >>= (\a2 ->
    m >>= (\a3 ->
    m >>= (\a4 -> 
        return [a1, a2, a3, a4]))))

The seq4M function has the values resulting from the monadic action available at every step and could thus make a choice at every step. seq4M函数具有在每一步可用的seq4M操作产生的值,因此可以在每一步做出选择。 On the other hand the seq4A function only has the values available at the very end.另一方面, seq4A函数只有最后可用的值。

I would like to share my view on this "iffy miffy" thing, as I understand this everything inside the context get applied, so for example: 我想分享我对这个“iffy miffy”事物的看法,因为我理解上下文中的所有内容都得到了应用,例如:

iffy :: Applicative f => f Bool -> f a -> f a -> f a
iffy fb ft fe = cond <$> fb <*> ft <*> fe   where
            cond b t e = if b then t else e

case 1>> iffy (Just True) (Just “True”) Nothing ->> Nothing

upps should be Just "True" ... but upps应该是“真实的”......但是

 case 2>> iffy (Just False) (Just “True”) (Just "False") ->> Just "False" 

(the "good" choice is made inside the context) I explained this to myself this way, just before the end of the computation in case >>1 we get something like that in the "chain" : (“好的”选择是在上下文中做出的)我用这种方式解释了这一点,就在计算结束之前>> 1我们在“链”中得到类似的东西:

Just (Cond True "True") <*> something [something being "accidentaly" Nothing]

which according by definition of Applicative is evaluated as: 根据Applicative的定义,评估为:

fmap (Cond True "True") something 

which when "something" is Nothing becomes a Nothing according to Functor constraint (fmap over Nothing gives Nothing). 根据Functor约束(fothing over Nothing no Nothing),当“something” Nothing时,Nothing变为Nothing。 And it is not possible to define a Functor with "fmap f Nothing = something" end of story. 并且无法使用“fmap f Nothing = something”故事结尾定义Functor。

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

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