繁体   English   中英

Haskell中Monad和Applicative的区别

[英]Difference between Monad and Applicative in Haskell

我刚刚从typeclassopedia 中阅读了以下关于MonadApplicative之间区别的内容。 我可以理解Applicative没有join 但是下面的描述对我来说看起来很模糊,我无法弄清楚一元计算/动作的“结果”究竟是什么意思。 那么,如果我将一个值放入Maybe ,它构成了一个 monad,这个“计算”的结果是什么?

让我们更仔细地看看 (>>=) 的类型。 基本的直觉是它将两个计算组合成一个更大的计算。 第一个参数 ma 是第一个计算。 但是,如果第二个参数只是一个 mb 就很无聊了; 那么计算将无法相互交互(实际上,这正是 Applicative 的情况)。 因此,(>>=) 的第二个参数的类型为 a -> mb:这种类型的函数,给定第一次计算的结果,可以产生要运行的第二次计算。 ... 直观地说,正是这种使用先前计算的输出来决定接下来要运行的计算的能力,使 Monad 比 Applicative 更强大。 Applicative 计算的结构是固定的,而 Monad 计算的结构可以根据中间结果而改变。

是否有一个具体的例子来说明“使用先前计算的输出来决定接下来要运行的计算的能力”,而 Applicative 没有?

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

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

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

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

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

因此,我们将实现一个不能拥有相应MonadApplicative实例。

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!

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

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

> 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完全体现了结果计算的思想,这取决于参数计算中嵌入的值。

Just 1描述了一个“计算”,其“结果”是1. 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 )

所有函数都在“内部”计算中工作,没有机会打破链条。 每个函数只是转换它所输入的值。 从功能的角度来看,计算结构的“形状”完全“在外部”。

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

对于monad,函数本身构造了他们选择的计算。

这是我对@J的看法。 Abrahamson的例子,为什么ifA不能使用内部的值,例如(pure True) 从本质上讲,它仍然可以归结为Applicative没有Monadjoin函数,它统一了类词库中给出的两个不同的视角来解释MonadApplicative之间的区别。

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

(具有与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也是fmapFunctor 我们可以做的:

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

问题在于ifA'的返回类型,即f (fa) Applicative ,没有办法将两个嵌套的Applicative S折叠成一个。 但是这种崩溃功能恰恰是Monad join功能。 所以,

ifA = join . ifA' 

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

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

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

在两种情况下都有ma ,但只有在第二种情况下, ma才能决定是否应用函数(a->mb) 反过来,函数(a->mb)可以“决定”下一个绑定的函数是否被应用 - 通过生成不包含“ b ”的mb (如[]NothingLeft )。

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这是不可能的,所以不能显示一个例子。 最接近的是:

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

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

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

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

Maybe monad看起来像这样:

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

在这里有资格作为一个“结果”的唯一的事情是a在第二个方程>>= ,因为它是有史以来被“喂”给的第二个参数的唯一的事>>=

其他答案已经进入到的深度ifAifM差,所以我想我应该强调的另一个显著差异:applicatives组成,单子没有 对于Monad s,如果你想制作一个结合了两个现有效果的Monad ,你必须将其中一个重写为monad变换器。 相反,如果您有两个Applicatives您可以轻松地制作一个更复杂的Applicatives ,如下所示。 (代码是由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)

现在,如果我们添加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)

......我们可以从LiftConstant的其他响应中组合“applicative Either ”:

type Error e a = Lift (Constant e) a

正如@Will Ness 在他的回答中所解释的那样,关键的区别在于 Monads 在每一步都可以在不同的执行路径之间进行选择。 让我们通过实现一个用于四次排序的函数来使这个潜在的选择在语法上可见。 一次用于应用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]))))

seq4M函数具有在每一步可用的seq4M操作产生的值,因此可以在每一步做出选择。 另一方面, seq4A函数只有最后可用的值。

我想分享我对这个“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应该是“真实的”......但是

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

(“好的”选择是在上下文中做出的)我用这种方式解释了这一点,就在计算结束之前>> 1我们在“链”中得到类似的东西:

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

根据Applicative的定义,评估为:

fmap (Cond True "True") something 

根据Functor约束(fothing over Nothing no Nothing),当“something” Nothing时,Nothing变为Nothing。 并且无法使用“fmap f Nothing = something”故事结尾定义Functor。

暂无
暂无

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

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