Enr*_*lis 8 monads haskell rewriting do-notation writer-monad
问题在底部以粗体显示。
LYAH 给出了这个使用monaddo符号的例子Writer
import Control.Monad.Writer
logNumber :: Int -> Writer [String] Int
logNumber x = writer (x, ["number " ++ show x])
multWithLog :: Writer [String] Int
multWithLog = do
a <- logNumber 3
b <- logNumber 5
return (x*y)
Run Code Online (Sandbox Code Playgroud)
定义可以在没有do符号的情况下重写:
multWithLog = logNumber 3 >>= (\x ->
logNumber 5 >>= (\y ->
return (x*y)))
Run Code Online (Sandbox Code Playgroud)
到现在为止还挺好。
之后,书中介绍了tell,并编辑了这样的定义multWithLog:
multWithLog = do
a <- logNumber 3
b <- logNumber 5
tell ["something"]
return (x*y)
Run Code Online (Sandbox Code Playgroud)
再次可以重写为:
multWithLog = logNumber 3 >>= (\x ->
logNumber 5 >>= (\y ->
tell ["something"] >>
return (x*y)))
Run Code Online (Sandbox Code Playgroud)
然后这本书提出了一个对我来说似乎不清楚的观点,如果不是不准确的话:
return (a*b)最后一行很重要,因为do表达式中最后一行的结果是整个 do 表达式的结果。如果我们把它tell作为最后一行,()就会是这个do表达式的结果。我们会失去乘法的结果。但是,日志将是相同的。
因此,我的第一个疑问来了:如果tell结果为(),那么代码不应该,甚至不编译,因为()不能匹配预期的类型Int,也不能匹配除()自身以外的任何其他类型;那么作者想告诉我们什么?为了使这种非基于意见,Haskell 有什么变化,因为这本书是写的,这使得上面引用的陈述不清楚/不准确?
等价的重写是进一步
multWithLog = logNumber 3 >>= (\ x ->
logNumber 5 >>= (\ y ->
tell ["something"] >>= (\ () -> -- () NB
return (x*y) >>= (\ result ->
return result ))))
Run Code Online (Sandbox Code Playgroud)
并且这是()那个tell ["something"]“收益”。显然,洗牌
multWithLog2 = logNumber 3 >>= (\ x ->
logNumber 5 >>= (\ y ->
return (x*y) >>= (\ result ->
tell ["something"] >>= (\ () ->
return () ))))
Run Code Online (Sandbox Code Playgroud)
确实会有类型Writer [String] (),所以如果签名要指定Writer [String] Int,它确实不会编译。
没有类型签名问题,“日志”即收集的[String]列表对于两种变体都是相同的,因为return不会改变收集的输出“日志”。