我到处都Applicative
可以看到副作用,但是我看到的所有简单示例都只是将内容组合在一起,例如:
> (,,) <$> [1,2] <*> ["a", "b", "c"] <*> ["foo", "bar"]
[(1,"a","foo"),(1,"a","bar"),(1,"b","foo"),(1,"b","bar"),
(1,"c","foo"),(1,"c","bar"),(2,"a","foo"),(2,"a","bar"),
(2,"b","foo"),(2,"b","bar"),(2,"c","foo"),(2,"c","bar")]
这很酷,但我看不到它与副作用的联系。我的理解是,这Applicative
是一个较弱的monad,因此您可以处理副作用(就像对State monad所做的那样),但是您不能重用以前的副作用的结果。
这是否意味着>>
可以为Applicative
和这样的东西写
do
print' "hello"
print' "world"
(带有print' :: a -> Applicative something
)(具有适当的do-applicative扩展名)将是有意义的。
在另一个世界中,Monad
和之间的区别Applicative
是Monad
允许x <- ...
但Applicative
不允许。
然后,Writer monad只是一个应用程序吗?
Applicative和Monad都提供了将多个副作用1值“组合”为单个副作用值的方法。
用于合并的Applicative接口仅使您可以合并有效值,以使所得的有效值根据某个“固定”配方合并其所有效果。
Monad用于合并的界面使您可以以有效方式合并有效值的方式,使合并后的值的效果取决于实际有效值在实际解析时所执行的操作。
例如,State Integer
单子/应用程序的值取决于(并影响)某些Integer
状态。State Integer t
值仅在存在该状态时才具有具体值。
这需要两个函数State Integer Char
的值(叫他们a
和b
),使我们回State Integer Char
值,仅使用的应用型接口State Integer
必须出示其“有状态”始终是相同的值,无论什么Integer
状态值,也不管什么Char
重视输入收益。例如,它可以通过a
然后b
将状态穿线,然后Char
以某种方式组合其值。或者可以通过威胁的状态b
,然后a
。或者它只能选择a
或仅选择b
。或者,它可能会完全忽略两者,而不会对当前Integer
状态产生任何影响,而只是pure
一些字符值。或者,它可以以任何固定的顺序运行任何一个或两个固定的次数,并且可以合并State Integer t
它知道的任何其他值。但是无论执行什么操作,它始终会执行此操作,而不管当前Integer
状态如何,或者State Integer t
它设法实现的任何值所产生的任何值。
接受相同输入但能够使用monad接口的State Integer
功能所能做的远不止于此。它可以运行a
还是b
取决于当前Integer
状态是正还是负。它可以运行a
,然后如果结果Char
是一个ascii数字字符,则可以将数字转换为数字并运行b
多次。等等。
因此,是这样的计算:
do
print' "hello"
print' "world"
是可以仅使用Applicative接口实现任何print'
返回值的一种方法。您几乎可以纠正:如果Monad和Applicative都具有do标记,则两者之间的差异将是monadic do允许x <- ...
,而applicative do则不允许。但是,这比它要微妙得多。这也适用于Applicative:
do x <- ...
y <- ...
pure $ f x y
Applicative不能做的就是检查 x
并y
确定f
要调用它们的内容(或执行任何f x y
其他操作,而不仅仅是获得结果)pure
。
您不是很正确,但是Writer w
作为monad和作为应用程序之间没有区别。的确,monadic接口Writer w
不允许产生的值取决于效果(“对数”),因此必须始终可以Writer w
将使用monadic功能定义的任何内容重写为仅使用应用功能并始终产生相同的值2。但是monadic界面允许效果依赖于值,而应用程序界面则不依赖于这些值,因此您不能始终忠实地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"]
)。如果应用程序界面可以做到最好,则divAnumer
中始终会包含的影响,即使懒惰的评估应该意味着从不检查by产生的值。当分母为零时,不可能在日志中添加“除以0”。numer
denom
numer
1我不喜欢将monad和appadatives的定义视为“结合有效的值” ,但这只是您可以使用它们的示例。
2无论如何,不涉及底层问题;您应该可以从我的示例中看到为什么bottom可以弄乱等效性。
本文收集自互联网,转载请注明来源。
如有侵权,请联系[email protected] 删除。
我来说两句