提问人:thor 提问时间:4/28/2014 更新时间:12/20/2022 访问量:18945
Haskell 中 Monad 和 Applicative 之间的区别
Difference between Monad and Applicative in Haskell
问:
我刚刚从 typeclassopedia 上阅读了以下关于 和 之间的区别的内容。我能理解没有.但下面的描述对我来说似乎很模糊,我无法弄清楚一元计算/操作的“结果”到底是什么意思。那么,如果我把一个值放进去,形成一个单子,这个“计算”的结果是什么?Monad
Applicative
join
Applicative
Maybe
让我们更仔细地看一下 (>>=) 的类型。基本的直觉是 它将两个计算组合成一个更大的计算。这 第一个参数 M A 是第一个计算。但是,这将是 如果第二个论点只是一个 M B,那就太无聊了;那么就没有了 计算相互交互的方式(实际上,这个 正是 Applicative 的情况)。所以,第二个论点 (>>=) 具有类型 a -> m b:此类型的函数,给定结果为 第一个计算,可以产生第二个计算来运行。 ...直观地说,正是这种能力使用了以前的输出 计算来决定接下来要运行哪些计算,从而使 Monad 比应用更强大。应用的结构 计算是固定的,而 Monad 计算的结构可以 基于中间结果的更改。
有没有一个具体的例子来说明“能够使用先前计算的输出来决定接下来要运行的计算”,而 Applicative 没有?
答:
我最喜欢的例子是“纯粹适用的 Either”。我们将首先分析 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
其中无非是 a 之前的从左到右排序: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!
再次注意,如果我们把左边的论点看作是“过去”,把右边的论点看作是“未来”,那么与它相比,它是特别的,因为它被允许并行地“开放”未来和过去,而不一定需要“过去”的结果来计算“未来”。(<*>)
(>>=)
这直接意味着,我们可以使用 our pure 来收集错误,如果链中存在任何 s,则忽略 sApplicative
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
评论
ifA t c a = g <$> t <*> c <*> a where g b x y = if b then x else y
ifA (Just True) (Just ()) Nothing == Nothing
ifM (Just True) (Just ()) Nothing == Just ()
ifA
ifM
ifM
if' <$> a <*> b <*> c where if' b t e = if b then t else e
Monad
Applicative
Monad
pure = return
). While the second
Applicative
Applicative
Either
差异的关键可以在 的类型和 的类型中观察到。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
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
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
Just 1
描述一个“计算”,其“结果”为 1。 描述不产生任何结果的计算。Nothing
单子和应用之间的区别在于,在单子中有一个选择。Monads的主要区别在于能够在不同的计算路径之间进行选择(而不仅仅是提前突破)。根据计算中前一步生成的值,计算结构的其余部分可能会发生变化。
这就是这意味着什么。在单子链中
return 42 >>= (\x ->
if x == 1
then
return (x+1)
else
return (x-1) >>= (\y ->
return (1/y) ))
选择要构建的计算。if
在应用的情况下,在
pure (1/) <*> ( pure (+(-1)) <*> pure 1 )
所有函数都在“内部”计算中工作,没有机会打破链条。每个函数只是转换它馈送的一个值。从函数的角度来看,计算结构的“形状”完全是“外部”的。
函数可以返回一个特殊值来指示失败,但它不能导致跳过计算中的后续步骤。他们也必须以特殊的方式处理特殊价值。计算的形状不能根据接收到的值而改变。
对于单子,函数本身会根据自己的选择构建计算。
评论
do-block
a <*> b <*> ...
a >>= (\ ... -> b >>= ... )
a
b
以下是我对亚伯拉罕森@J的例子的看法,说明为什么不能使用里面的值,例如。从本质上讲,它仍然归结为 中没有函数 ,它统一了 typeclassopedia 中给出的两种不同观点来解释 和 之间的区别。ifA
(pure True)
join
Monad
Applicative
Monad
Applicative
因此,使用亚伯拉罕森@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
?
好吧,正如已经指出的,最终,的内容不能被后面的计算使用。但从技术上讲,这是不对的。我们可以使用 因为 a 也是 a 的 content with .我们可以做到:(pure True)
(pure True)
Monad
Functor
fmap
ifA' b t e = fmap (\x -> if x then t else e) b
问题出在 的返回类型上,即 。在 中,无法将两个嵌套的 S 折叠为一个。但这种坍塌功能恰恰是 in 所执行的。所以ifA'
f (f a)
Applicative
Applicative
join
Monad
ifA = join . ifA'
将满足 的方程,如果我们能适当地实现。这里缺少的正是功能。换句话说,我们可以以某种方式将前一个结果的结果用于 。但是在框架中这样做将涉及将返回值的类型增加到嵌套的应用值,我们没有办法将其恢复到单级应用值。这将是一个严重的问题,因为,例如,我们无法适当地使用 S 来组合函数。使用解决了这个问题,但引入 将 提升到 .ifA
join
Applicative
join
Applicative
Applicative
Applicative
join
join
Applicative
Monad
评论
join Right (Right a) = Right a; join Right (Left e) = Left e; join Left (Left e) = Left e
join Left (Right a) =? Left (Right a)
join
Result<Result<_,_>,Result<_,_>>
但下面的描述对我来说似乎很模糊,我无法弄清楚一元计算/操作的“结果”到底是什么意思。
嗯,这种模糊性有点故意的,因为一元计算的“结果”取决于每种类型。最好的答案有点自相矛盾:“结果”(或结果,因为可以有多个)是实例的实现调用函数参数的任何值。(>>=) :: Monad m => m a -> (a -> m b) -> m b
那么,如果我把一个值放进去,形成一个单子,这个“计算”的结果是什么?
Maybe
monad 如下所示:Maybe
instance Monad Maybe where
return = Just
Nothing >>= _ = Nothing
Just a >>= k = k a
这里唯一符合“结果”条件的东西是 的第二个等式中的 ,因为它是唯一被“喂”给 的第二个参数的东西。a
>>=
>>=
其他答案已经深入探讨了 vs. 差异,所以我想我应该强调另一个显着的区别:应用者组成,单子不。使用 s,如果你想制作一个结合了两个现有效果的效果,你必须将其中一个重写为 monad 转换器。相反,如果你有两个,你可以很容易地用它们制作一个更复杂的,如下所示。(代码是从转换器
复制粘贴的。ifA
ifM
Monad
Monad
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)
现在,如果我们添加 functor/appplicative: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
type Error e a = Lift (Constant e) a
我想分享我对这个“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
当“某物”是 Nothing 时,根据 Functor 约束,它变成 Nothing(fmap over Nothing gives Nothing)。并且不可能用故事结尾的“fmap f Nothing = something”来定义 Functor。
正如 @Will Ness 在他的回答中解释的那样,关键的区别在于,对于 Monads,每一步都可以在不同的执行路径之间进行选择。让我们通过实现一个四次排序的函数,使这个潜在的选择在语法上可见。首先是应用,然后是单子:f
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
seq4A
评论
Just 1
描述一个“计算”,其“结果”为 1。 描述不产生任何结果的计算。Nothing