Haskell中Monad与Applicative的区别

时间:2014-04-28 13:17:36

标签: haskell monads applicative

我刚从typeclassopedia了解了MonadApplicative之间的区别。我可以理解join中没有Applicative。但是下面的描述看起来含糊不清,我无法弄清楚"结果"的确切含义。一元计算/行动。所以,如果我将一个值放入Maybe,这是一个monad,那么"计算"的结果是什么?

  

让我们更仔细地看一下(>> =)的类型。基本的直觉是   它将两个计算组合成一个更大的计算。该   第一个参数m a是第一个计算。但是,它会   如果第二个论点只是一个m b,那就很无聊;那就没有了   计算彼此交互的方式(实际上,这个   正是适用的情况)。所以,第二个论点   (>> =)具有类型a - > m b:给定结果的这种类型的函数   第一次计算,可以产生第二次运算。   ...直观地说,正是这种能力使用了之前的输出   用于决定接下来运行哪些计算的计算使Monad成为可能   比Applicative更强大。申请人的结构   计算是固定的,而Monad计算的结构可以   根据中间结果改变。

是否有一个具体的例子说明"能否使用先前计算的输出来决定接下来运行的计算"哪个应用程序没有?

6 个答案:

答案 0 :(得分:61)

我最喜欢的例子是“纯粹适用于任何一个”。我们首先分析Either的基础Monad实例

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

这个例子嵌入了一个非常自然的短路概念:我们从左到右进行,一旦单个计算“失败”进入Left,那么所有其他人也会这样做。任何Applicative都有

的自然Monad实例
instance Applicative (Either e) where
  pure  = return
  (<*>) = ap

其中ap只不过是return之前从左到右的排序:

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

现在,当您想要收集计算中出现的错误消息并以某种方式产生错误摘要时,此Either实例会出现问题。面对短路,这种情况很严重。它也面对(>>=)

的类型
(>>=) :: m a -> (a -> m b) -> m b

如果我们将m a视为“过去”而将m b视为“未来”,那么(>>=)会产生过去的未来,只要它可以运行“步进” (a -> m b)。这个“步进者”要求a的价值确实存在于未来...... Either这是不可能的。因此(>>=) 要求短路。

相反,我们将实现一个Applicative实例,该实例不能有相应的Monad

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

现在(<*>)的实施是值得仔细考虑的特殊部分。它在第一个 3 案例中执行了一些“短路”,但在第四个案例中做了一些有趣的事情。

  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!

再次注意,如果我们将左边的参数视为“过去”而将右边的参数视为“未来”,那么(<*>)(>>=)相比是特殊的,因为它允许“打开”未来和过去并行而不是必然需要“过去”的结果来计算“未来”。

这意味着,我们可以直接使用纯Applicative Either来收集错误,如果链中存在任何Right,则忽略Left

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

所以让我们把这个直觉放在头上。我们不能用纯粹适用的Either做什么?那么,由于其运作取决于在运行过去之前检查未来,我们必须能够在不依赖于过去的价值的情况下确定未来的结构。换句话说,我们不能写

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

满足以下等式

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

虽然我们可以写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

这样

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

这种不可能性的产生是因为ifA完全体现了结果计算的思想,取决于参数计算中嵌入的值。

答案 1 :(得分:38)

Just 1描述了&#34;计算&#34;,其结果&#34;是Nothing描述了一个不产生结果的计算。

Monad和Applicative之间的区别在于Monad可以选择。 Monads的关键区别在于能够在计算中选择不同的路径(不仅仅是提前爆发)。根据前一步计算产生的值,其余的计算结构可以改变。

这就是这意味着什么。在monadic链中

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

if选择要构建的计算。

如果是Applicative,请

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

所有功能都起作用&#34;内部&#34;计算,没有机会打破链条。每个函数只是转换它所馈送的值。 &#34;形状&#34;计算结构完全是在外面&#34;从功能&#39;观点看法。

函数可以返回一个特殊值来指示失败,但它不能导致跳过计算中的后续步骤。他们都必须以特殊的方式处理特殊价值。计算的形状不能根据接收的值改变。

使用monad,函数本身可以根据自己的选择构建计算。

答案 2 :(得分:15)

这是我对@J的看法。 Abrahamson关于为什么ifA无法使用内部值的示例(pure True)。从本质上讲,它仍然归结为joinMonad Applicative函数的缺失,Monad统一typeclassopedia中给出的两个不同视角来解释Applicative之间的差异{1}}和Either

所以使用@J。亚伯拉罕森的纯粹适用的例子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!

Either

(与Monad ifA具有类似的短路效果)和ifA :: Applicative f => f Bool -> f a -> f a -> f a 函数

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

如果我们试图实现上述等式,该怎么办:

(pure True)

嗯,正如已经指出的那样,最终,(pure True)的内容不能用于以后的计算。但从技术上讲,这是不对的。我们可以使用Monad的内容,因为Functor也是fmap ifA' b t e = fmap (\x -> if x then t else e) b 。我们可以这样做:

ifA'

问题在于f (f a)的返回类型,即Applicative。在Applicative中,无法将两个嵌套的join S合并为一个。但是这个折叠函数正是MonadifA = join . ifA' 执行的功能。所以,

ifA
如果我们能够恰当地实现join

将满足Applicative的等式。这里缺少的join正是Applicative函数。换句话说,我们可以某种方式使用Applicative中先前结果的结果。但是在Applicative框架中这样做会涉及将返回值的类型扩展为嵌套的应用值,我们无法将其返回到单级应用值。这将是一个严重的问题,因为,例如,我们不能恰当地使用join S来组合函数。使用join修复了该问题,但Applicative的介绍将Monad提升为{{1}}。

答案 3 :(得分:12)

ap类型与=<<的类型可以观察到差异的关键。

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

在这两种情况下都有m a,但仅在第二种情况m a可以决定是否应用函数(a->m b)。反过来,函数(a->m b)可以“决定”接下来绑定的函数是否被应用 - 通过生成不包含m b的{​​{1}}(如b[]Nothing)。

Left中,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. 中这是不可能的,所以无法显示示例。最接近的是:

Applicative

答案 4 :(得分:2)

  

但是下面的描述看起来含糊不清,我无法弄清楚monadic计算/动作的“结果”究竟是什么意思。

嗯,这种模糊性有些刻意,因为“结果”是一元计算的东西取决于每种类型。最好的答案有点重复:“结果”(或结果,因为可能有多个)是实例的(>>=) :: Monad m => m a -> (a -> m b) -> m b实现调用函数参数的任何值用。

  

那么,如果我将一个值放入Maybe,这是一个monad,这个“计算”的结果是什么?

Maybe monad看起来像这样:

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

这里唯一有资格作为“结果”的是a的第二个等式中的>>=,因为它是唯一被“喂”到{的第二个参数的东西{1}}。

其他答案已深入探讨了>>=ifA之间的差异,所以我认为我会强调另一个显着差异:应用程序撰写,monad不 。对于ifM s,如果要创建一个组合了两个现有效果的Monad,则必须将其中一个重写为monad变换器。相反,如果你有两个Monad,你可以轻松地制作一个更复杂的一个,如下所示。 (代码是从transformers复制的。)

Applicatives

现在,如果我们添加-- | 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) 仿函数/ applicative:

Constant

...我们可以从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) Either中的其他回复中汇总“适用Lift”:

Constant

答案 5 :(得分:-1)

我想分享我对“难解之谜”的看法,因为据我了解,上下文中的所有内容都得到了应用,例如:

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应该只是“ True” ...但是

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

(在上下文中选择“好”) 我以这种方式向自己解释了这一点,就在计算结束之前,如果>> 1我们在“链”中得到了类似的内容:

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

根据“应用”的定义,其评估为:

fmap (Cond True "True") something 

当“某物” 时,根据Functor约束,什么都不会变成什么(fmap over Nothing给出了什么)。而且不可能用故事的结尾定义“ fmap f Nothing = something”的函子。