Applicative 和 Monad 都提供了将多个副作用1 值“组合”成单个副作用值的方法。
用于组合的 Applicative 接口只允许您组合有效值,以便生成的有效值根据一些“固定”配方组合所有效果。
用于组合的 Monad 接口允许您以这样一种方式组合有效值,即组合值的效果取决于原始有效值在实际解析时的作用。
例如,State Integer monad/applicative 的值取决于(并影响)某些Integer 状态。 State Integer t 值仅在存在该状态时才具有具体值。
一个函数接受两个State Integer Char 值(称它们为a 和b)并返回一个State Integer Char 值并且只使用State Integer 的Applicative 接口必须产生一个“有状态”的值无论Integer 状态值是什么,也无论输入产生什么Char 值,总是相同的。例如,它可以通过a 和b 将状态线程化,以某种方式组合它们的Char 值。或者它可能通过b 然后a 威胁国家。或者它可以只选择a 或只选择b。或者它可以完全忽略两者,而不会对当前的Integer 状态产生任何影响,而只是对pure 一些字符值产生影响。或者它可以以任何固定顺序运行它们中的任何一个或两个,并且它可以包含它知道的任何其他State Integer t 值。但无论它做什么,它总是这样做,不管当前的Integer 状态,或者它设法获得的任何State Integer t 值产生的任何值。
一个接受相同输入但能够为State Integer 使用monad 接口的函数可以做的远不止这些。它可以运行a 或b,具体取决于当前Integer 状态是正还是负。它可以运行a,然后如果生成的Char 是一个ascii 数字字符,它可以将数字转换为数字并多次运行b。以此类推。
所以是的,计算如下:
do
print' "hello"
print' "world"
对于print' 返回的任何内容,都可以仅使用 Applicative 接口来实现。如果 Monad 和 Applicative 都有一个 do-notation,那么您接近于正确的区别在于 monadic do 将允许 x <- ...,而 applicative do 则不允许。它比这更微妙一些。 this 也适用于 Applicative:
do x <- ...
y <- ...
pure $ f x y
Applicative 不能做的是 检查 x 和 y 来决定 f 调用它们(或者对 f x y 的结果做任何事情,而不仅仅是 @ 987654358@它。
但是,Writer w 作为 monad 和作为应用程序之间没有区别,您并不完全正确。 Writer w 的单子接口确实不允许 value 依赖于效果(“日志”),因此必须始终可以重写任何使用定义的 Writer w monadic features 到只使用 applicative features 并始终产生相同值的一种方法2。但是 monadic 接口允许 效果 依赖于 values,而 applicative 接口则不允许,因此您不能总是忠实地重现 Writer w 的效果仅使用应用程序接口。
看这个(有点傻的)示例程序:
import Control.Applicative
import Control.Monad.Writer
divM :: Writer [String] Int -> Writer [String] Int -> Writer [String] Int
divM numer denom
= do d <- denom
if d == 0
then do tell ["divide by zero"]
return 0
else do n <- numer
return $ n `div` d
divA :: Writer [String] Int -> Writer [String] Int -> Writer [String] Int
divA numer denom = divIfNotZero <$> numer <*> denom
where
divIfNotZero n d = if d == 0 then 0 else n `div` d
noisy :: Show a => a -> Writer [String] a
noisy x = tell [(show x)] >> return x
然后在 GHCi 中加载:
*Main> runWriter $ noisy 6 `divM` noisy 3
(2,["3","6"])
*Main> runWriter $ noisy 6 `divM` noisy 0
(0,["0","divide by zero"])
*Main> runWriter $ undefined `divM` noisy 0
(0,["0","divide by zero"])
*Main> runWriter $ noisy 6 `divA` noisy 3
(2,["6","3"])
*Main> runWriter $ noisy 6 `divA` noisy 0
(0,["6","0"])
*Main> runWriter $ undefined `divA` noisy 0
(0,*** Exception: Prelude.undefined
*Main> runWriter $ (tell ["undefined"] *> pure undefined) `divA` noisy 0
(0,["undefined","0"])
注意divM,numer的效果是否包含在numer `divM` denom中取决于denom的值(tell ["divide by zero"]的效果是否包含)。在应用程序接口所能做的最好的情况下,numer 的效果总是包含在numerdivAdenom 中,即使惰性求值意味着numer 产生的值 永远不会检查。当分母为零时,不可能在日志中添加“除以 0”。
1 我不喜欢像 monad 和 applicatives 的定义那样认为“组合有效值”,但这是一个 示例 em> 你可以用它们做什么。
2 无论如何,当不涉及底部时;你应该可以从我的例子中看出为什么底部会搞乱等价。