我看到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
不允许。{/ p>
然后,是作家monad,只是一个应用程序?
答案 0 :(得分:14)
>>
的应用等效项为*>
,因此您可以
ghci> :m Control.Applicative
ghci> print 5 *> print 7
5
7
import Control.Applicative
data Company = Company {name :: String, size :: Int}
deriving Show
getCompany :: IO Company
getCompany = Company <$> getLine <*> readLn
适用于输入:
ghci> getCompany >>= print
BigginsLtd
3
Company {name = "BigginsLtd", size = 3}
请注意,由于我们正在使用Applicative for IO,我们无论如何都在IO monad中,所以如果我们愿意,可以使用>>=
。 Applicative给你的好处是语法很好。
我最喜欢的是解析,所以我可以做
data Statement = Expr Expression | If Condition Statement Statement
parseStatement = Expr <$> parseExpression <|>
If <$> (string "if" *> parseCondition)
<*> (string "then" *> parseStatement)
<*> (string "else" *> parseStatement)
Applicative和Monad之间的区别在于Monad有>>=
,可让您根据自己的价值选择使用哪种副作用。
使用Monad:
don't_reformat_hard_drive :: Bool -> IO ()
don't_reformat_hard_drive yes = if yes then putStr "OK I didn't"
else putStr "oops!" >> System.IO.reformat "C:/"
maybeReformat :: IO ()
maybeReformat = WinXP.Dialogs.ask "Don't reformat hard drive?"
>>= don't_reformat_hard_drive
(没有System.IO.reformat
或WinXP.Dialogs.ask
。这只是我觉得有趣的一个例子。)
使用Applicative:
response :: Bool -> () -> String
response yes () = if yes then "OK I didn't" else "oops!"
probablyReformat = response <$> WinXP.Dialogs.ask "Don't reformat hard drive?"
<*> System.IO.reformat "C:\"
遗憾的是,使用Applicative我无法检查布尔值以确定是否重新格式化 - 副作用顺序是在编译时确定,在Applicative中,并且硬盘驱动器将< em>总是用这段代码重新格式化。我需要Monad的绑定(>>=
)才能停止重新格式化。
.........your hard drive C: has been successfully reformatted.
"OK I didn't"
答案 1 :(得分:5)
Applicative和Monad都提供了将多个副作用 1 值“组合”成单个副作用值的方法。
用于组合的Applicative界面让您可以组合有效的值,以便产生的有效值根据某些“固定”配方组合所有效果。
用于组合的Monad接口允许您组合有效值,使组合值的效果取决于原始有效值在实际解析时的作用。
例如,State Integer
monad / applicative的值取决于(并影响)某些Integer
状态。 State Integer t
值仅在存在该状态时具有具体值。
一个函数,它接受两个State Integer Char
值(称为a
和b
)并返回State Integer Char
值并仅使用{{1}的Applicative接口无论State Integer
状态值是什么,无论输入产生什么Integer
值,都必须生成一个“有状态”始终相同的值。例如,它可以通过Char
然后a
来处理状态,并以某种方式组合它们的b
值。或者它可以通过Char
然后b
来威胁状态。或者它只能选择a
或仅a
。或者它可以完全忽略它们,不对它们的当前b
状态采取任何一种效果,而只采用Integer
一些char值。或者它可以以任何固定的顺序运行其中任何一个或两个任意固定的次数,并且它可以包含它知道的任何其他pure
值。但无论它做什么,始终都会这样做,无论当前的State Integer t
状态如何,或者它设法获得的任何Integer
值产生的任何值。
使用相同输入但能够使用State Integer t
的monad接口的函数可以做更多的事情。它可以运行State Integer
或a
,具体取决于当前b
状态是正还是负。它可以运行Integer
,如果生成的a
是一个ascii数字字符,它可以将数字转换为数字并多次运行Char
。等等。
是的,计算如下:
b
是否可以使用Applicative接口实现任何do
print' "hello"
print' "world"
返回。你接近纠正了Monad和Applicative之间的区别,如果两者都有一个do-notation,那monadic会允许print'
,而申请则不会。虽然它比这更微妙; 此也适用于Applicative:
x <- ...
申请人无法做的是检查 do x <- ...
y <- ...
pure $ f x y
和x
来决定y
要对他们进行调用(或对{的结果做任何事情{1}} {而不仅仅是f
。
你并不完全正确,f x y
作为monad和appative之间没有区别。确实pure
的monadic接口不允许值产生依赖于效果(“log”),因此必须始终可以重写任何{{1}使用monadic功能定义仅使用应用功能并始终产生相同值 2 的功能。但monadic接口允许效果依赖于值,而应用程序界面却没有,所以你不能总是忠实地再现{{1}的效果仅使用应用程序界面。
看到这个(有些愚蠢)的示例程序:
Writer w
然后加载GHCi:
Writer w
请注意Writer w
的问题,Writer w
的{{1}}效果是否包含在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
中取决于*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"])
的值({1}的效果是否也是如此})。尽管应用界面可以做到最好,divM
的效果总是包含在numer
divA numer `divM` denom
中,即使懒惰评估意味着值产生了永远不会检查denom
。当分母为零时,不可能在日志中添加“除以0”。
1 我不喜欢将“有效值组合”视为monad和applicatives的定义,但它是示例你可以用它们做什么。
2 无论如何都不涉及底部;你应该能够从我的例子中看到为什么底部会弄乱等价物。
答案 2 :(得分:4)
在您的示例中,正在使用列表的Applicative
实例。这里的“效果”是非确定性:返回多个可能的值。
列表的Applicative
实例计算各个列表参数中元素的可能组合。
然而,它不能做的是使其中一个列表依赖于前一个列表中包含的值。您需要Monad
实例。
例如,考虑代码:
foo :: [Int]
foo = do
r <- [2,7]
if (even r)
then [2,5,6]
else [9,234,343]
这里我们生成的列表的值取决于计算中较早出现的列表([2,7]
)。你不能用Applicative
。
类比
我希望以下类比不是太迟钝,但Applicative
就像铁路一样。
在Applicative
中,效果构建了功能应用的机车将行进的铁路。所有这些效果都是在建造铁路时发生的,而不是在机车移动时。机车不能以任何方式改变它的路线。
A Monad
就像在铁路上有一个仍在建设中的火车头。乘客实际上可以向前方几米处的建筑工作人员大喊,告诉他们“我喜欢这边的风景,请将轨道向右倾斜一点”或“你可以停止铺设轨道,我就在这里出去”。当然,对于这条奇怪的铁路,公司无法提供时间表或固定的停靠列表,可以在登上火车前进行检查。
这就是为什么Applicative
可以被视为“广义函数应用”的一种形式,但Monad
不能。{/ p>