如何处理Applicative的副作用? [英] How to handle side effect with Applicative?

查看:85
本文介绍了如何处理Applicative的副作用?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我到处可以看到 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 是一个弱单子,所以你可以处理副作用(就像使用状态单元一样),但是你不能重用先前的结果副作用。

这是否意味着>> 可以写入应用和诸如

  do 
print'hello
print ''world'

有意义(用 print':: a - > Applicative something )(带有适当的do-applicative扩展名)。

在其他世界中, Monad Applicative 就是 Monad 允许 x < - ... 但是 Applicative 没有。然后,Writer monad,只是一个应用?

解决方案

Applicative和Monad都提供了组合多个副作用 1 值转化为单数e b-b

适用于组合的接口只是让您结合有效的值,使得最终的有效值根据某些固定配方组合所有效果。

用于组合的Monad界面可让您组合有效值,以便组合值的效果取决于原始有效值在实际解析时的效果。例如,状态整数 monad / applicative的值取决于(并影响)一些整数状态。 状态整数t 值仅在该状态存在时具有具体值。



一个函数, code> State Integer Char 值(称它们为 a b )和给我们一个 State Integer Char 值,并且只使用 State Integer 的Applicative接口必须产生一个值,其状态性始终是相同的,无论 Integer 状态值是多少,并且不管输入产生的是什么 Char 值。例如,它可以通过 a 然后 b 来对状态进行线程化,结合它们的 Char 值。或者它可能通过 b 然后 a 来威胁州。或者它可以只选择 a 或者只选择 b 。或者它可以完全忽略它们,不会对当前的 Integer 状态产生任何影响,并且只是纯粹的一些char值。或者它可以以任何固定次序运行任一或两者任意固定次数,并且可以包含它所知道的任何其他 State Integer t 值。但无论它做什么,无论当前的 Integer 状态如何,始终都会这样做,或者任何状态整数t 值,它管理着它的手。



一个采用相同输入但能够使用monad接口的函数对于 State Integer 可以做得比这更多。它可以运行 a b ,具体取决于当前整数状态是正面或负面。它可以运行 a ,然后如果得到的 Char 是一个ascii数字字符,它可以将数字转换为数字,多次运行 b 。等等。



所以是的,计算如下:

pre $
print'hello
print'world

只需使用Applicative接口就可以实现任何 print'返回。如果两者都有一个符号,Monad和Applicative之间的区别就是一元函数会允许 x < - ... ,而应用程序do不会。虽然这比这更微妙一些; 也适用于Applicative:

  do x < -  ... 
y < - ...
pure $ fxy

Applicative不能做的是 inspect x y 来决定 f 来调用它们(或者对 fxy 的结果做任何事情,而不仅仅是 pure 它。 p>

然而,作为一个单元和一个应用程序,你不太正确: Writer w 没有区别。单元接口 Writer w 不允许取决于效果(日志),所以它必须始终可以将任何使用monadic特性定义的 Writer w 重写为仅使用应用特性的函数,并且总是产生相同的值 2 。但是,monadic接口允许效果依赖于,而应用接口不会,所以y ou不能总是忠实地使用仅适用的界面来复制 Writer w 的效果。



请参阅有点傻)示例程序:

pre $ c $ import 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 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< $>数字< *> denom
where
divIfNotZero n d = if if == 0 then 0 else n`div` d


noisy :: Show a =>> a - > Writer [String] a
noisy x = tell [(show x)]>>返回x

然后用GHCi加载:

  *主> runWriter $ noisy 6`divM`嘈杂3 
(2,[3,6])
* Main> runWriter $ noisy 6`divM` noisy 0
(0,[0,divide by zero))
* Main> runWriter $ undefined`divM`嘈杂0
(0,[0,除以零])

* Main> runWriter $ noisy 6`divA`嘈杂3
(2,[6,3])
* Main> runWriter $ noisy 6`divA`嘈杂0
(0,[6,0])
* Main> runWriter $ undefined`divA`嘈杂0
(0,***例外:Prelude.undefined
* Main> runWriter $(tell [undefined] *>纯粹未定义)`divA` noise 0
(0,[undefined,0])

code> divM ,无论是 numer 的效果都包含在 numer里`divM` denom 取决于 denom 的值(如果的结果告诉[除以零] )。应用接口可以做得最好, numer 的效果总是包含在 numer divA <$ c中$ c> denom ,即使懒惰评估应该表示由 numer 产生的永远不会被检查。






1

不可以在分数为零时帮助添加除以0 / sup>我不喜欢将有效值组合成为单元和应用程序的定义,但它是一个示例le 你可以用它们做什么。



2 无论如何,你应该能够从我的例子中看到为什么bottom可能会弄乱等价。


I see everywhere that Applicative can handle side effects, but all the simple examples I've seen are just combining stuff together like:

> (,,) <$> [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")]

Which is cool but I can't see how that links to side effects. My understanding is that Applicative is a weak monad and so you can handle side effects (as you would do with a State monad) but you can't reuse the result of the previous side effect.

Does that mean that >> could be written for an Applicative and things like

do
  print' "hello"
  print' "world"

would make sense (with print' :: a -> Applicative something) (with the appropriate do-applicative extension).

In other world, is the difference between Monad and Applicative is that Monad allows x <- ... but Applicative doesn't.

Then, is the Writer monad, just an applicative?

解决方案

Applicative and Monad both provide ways of "combining" multiple side-effectful1 values into a single side-effectful value.

The Applicative interface for combining just lets you combine effectful values such that the resulting effectful value combines all their effects according to some "fixed" recipe.

The Monad interface for combining lets you combine effectful values in such a way that the effects of the combined value depends on what the original effectful values do when they're actually resolved.

For example, the State Integer monad/applicative is of values that depend upon (and affect) some Integer state. State Integer t values only have a concrete value in the presence of that state.

A function that takes two State Integer Char values (call them a and b) and gives us back a State Integer Char value and only uses the Applicative interface of State Integer must produce a value whose "statefulness" is always the same, regardless of what the Integer state value is and regardless of what Char values the inputs yield. For example, it could thread the state through a and then b, combining their Char values somehow. Or it could threat the state through b and then a. Or it could pick only a or only b. Or it could ignore both entirely, not taking either of their effects on the current Integer state, and just pure some char value. Or it could run either or both of them any fixed number of times in any fixed order, and it could incorporate any other State Integer t values it knows about. But whatever it does, it always does that, regardless of the current Integer state, or any values produced by any of the State Integer t values it manages to get its hands on.

A function that took the same inputs but was able to use the monad interface for State Integer can do much more than that. It can run a or b depending on whether the current Integer state is positive or negative. It can run a, then if the resulting Char is an ascii digit character it can turn the digit into a number and run b that many times. And so on.

So yes, a computation like:

do
  print' "hello"
  print' "world"

Is one that could be implemented using only the Applicative interface to whatever print' returns. You are close to correct that the difference between Monad and Applicative if both had a do-notation would be that monadic do would allow x <- ..., while applicative do would not. It's a bit more subtle than that though; this would work with Applicative too:

do  x <- ...
    y <- ...
    pure $ f x y

What Applicative can't do is inspect x and y to decide what f to call on them (or do anything with the result of f x y other than just pure it.

You are not quite correct that there's no difference between Writer w as a monad and as an applicative, however. It's true that the monadic interface of Writer w doesn't allow the value yielded to depend on the effects (the "log"), so it must always be possible to rewrite any Writer w defined using monadic features to one that only uses applicative features and always yields the same value2. But the monadic interface allows the effects to depend on the values, which the applicative interface doesn't, so you can't always faithfully reproduce the effects of a Writer w using only the applicative interface.

See this (somewhat silly) example program:

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

Then with that loaded in 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"])

Note how with divM, whether numer's effects are included in numer `divM` denom depends on the value of denom (as does whether the effect of tell ["divide by zero"]). With the best the applicative interface can do, the effects of numer are always included in numerdivAdenom, even when lazy evaluation should mean that the value yielded by numer is never inspected. And it's not possible to helpfully add "divide by 0" to the log when the denominator is zero.


1 I don't like to think of "combining effectful values" as the definition of that monads and applicatives do, but it's an example of what you can do with them.

2 When bottoms aren't involved, anyway; you should be able to see from my example why bottom can mess up the equivalence.

这篇关于如何处理Applicative的副作用?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆