如何处理Applicative的副作用?

mb14

我到处都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之间的区别ApplicativeMonad允许x <- ...Applicative不允许。

然后,Writer monad只是一个应用程序吗?

Ben

Applicative和Monad都提供了将多个副作用1“组合”为单个副作用值的方法。

用于合并的Applicative接口仅使您可以合并有效值,以使所得的有效值根据某个“固定”配方合并其所有效果。

Monad用于合并的界面使您可以以有效方式合并有效值的方式,使合并后的值的效果取决于实际有效值在实际解析时所执行的操作。

例如,State Integer单子/应用程序的值取决于(并影响)某些Integer状态。State Integer t值仅在存在该状态时才具有具体值。

这需要两个函数State Integer Char的值(叫他们ab),使我们回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不能做的就是检查 xy确定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”。numerdenomnumer


1我不喜欢将monad和appadatives的定义视为“结合有效的值” ,但这只是您可以使用它们示例

2无论如何,不​​涉及底层问题;您应该可以从我的示例中看到为什么bottom可以弄乱等效性。

本文收集自互联网,转载请注明来源。

如有侵权,请联系[email protected] 删除。

编辑于
0

我来说两句

0条评论
登录后参与评论

相关文章