问题在底部以粗体显示。
LYAH给出了将do
符号与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)
其中可以不使用do
标记来重写定义:
multWithLog = logNumber 3 >>= (\x ->
logNumber 5 >>= (\y ->
return (x*y)))
到目前为止一切顺利。
此后,书介绍了tell
,并像这样编辑multWithLog
的定义:
multWithLog = do
a <- logNumber 3
b <- logNumber 5
tell ["something"]
return (x*y)
再次可以改写为:
multWithLog = logNumber 3 >>= (\x ->
logNumber 5 >>= (\y ->
tell ["something"] >>
return (x*y)))
然后这本书提出了一个我不太清楚的观点,即使不是很准确:
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 ))))
和that是()
“返回”的tell ["something"]
。显然,改组
multWithLog2 = logNumber 3 >>= (\x ->
logNumber 5 >>= (\y ->
return (x*y) >>= (\result ->
tell ["something"] >>= (\() ->
return () ))))
确实具有类型Writer [String] ()
,因此,如果签名指定为Writer [String] Int
,则它的确不会编译。
没有类型签名问题,“ log”,即,所收集的[String]
列表对于两个变体都是相同的,因为return
不会更改所收集的输出“ log”。