我刚刚从typeclassopedia中阅读了有关Monad
和Applicative
之间的区别的内容。我可以理解join
中没有Applicative
。但是以下描述对我来说似乎很模糊,我无法弄 list 子计算/操作的“结果”到底是什么意思。因此,如果我在Maybe
中输入一个值,使之成为monad,那么这种“计算”的结果是什么?
是否有一个具体的示例说明“没有能力使用先前计算的输出来决定下一步要运行的计算”,而哪个Applicative没有?
最佳答案
我最喜欢的示例是“纯适用的任何一种”。我们将从分析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
视为“ future ”,那么(>>=)
只要能够运行“stepper” (a -> m b)
,就可以产生过去的 future 。这个“步进器”要求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!
再次注意,如果我们将左参数视为“过去”,将右参数视为“ future ”,则
(<*>)
与(>>=)
相比是特殊的,因为它允许并行“打开” future 和过去,而不必一定要来自“过去”的结果,以便计算“ future ”。这直接意味着我们可以使用纯粹的
Applicative
Either
收集错误,如果链中存在任何Right
,则可以忽略Left
。> Right (+1) <*> Left [1] <*> Left [2]
> Left [1,2]
因此,让我们颠倒这个直觉。我们纯粹使用
Either
不能做什么?好吧,由于它的运行取决于在运行过去之前检查 future ,因此我们必须能够确定 future 的结构而不依赖于过去的值(value)。换句话说,我们不能写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
确切地体现了根据参数计算中嵌入的值进行结果计算的想法。关于haskell - Haskell中Monad和Applicative之间的区别,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/23342184/