LYAH - 理解链接 Writer monad 时关于 "tell" 的评论

LYAH - Understanding comment about "tell" when chaining Writer monads

问题在底部以粗体显示。

LYAH 给出了这个使用 do 符号和 Writer monad

的例子
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)))

然后这本书提出了一个我觉得不清楚的观点,如果不是不准确的话:

It's important that return (a*b) is the last line, because the result of the last line in a do expression is the result of the whole do expression. Had we put tell as the last line, () would have been the result of this do expression. We'd lose the result of the multiplication. However, the log would be the same.

因此,我的第一个疑问来了:如果 tell 导致 (),那么代码不应该编译,甚至不会编译,因为 () 无法匹配预期的类型Int,也不是 () 本身以外的任何其他类型;那么作者想告诉我们什么呢?为了使这个不基于观点, 在 Haskell 中做了一些改变,因为这本书是写的,上面引用的陈述 unclear/inaccurate?

等价的重写更进一步

multWithLog = logNumber 3        >>= (\ x ->
              logNumber 5        >>= (\ y ->
              tell ["something"] >>= (\ () ->     -- () NB
              return (x*y)       >>= (\ result ->
              return result ))))

那个()那个tell ["something"]“returns”。显然,洗牌

multWithLog2 = logNumber 3        >>= (\ x ->
               logNumber 5        >>= (\ y ->
               return (x*y)       >>= (\ result ->
               tell ["something"] >>= (\ () ->
               return () ))))

确实有类型Writer [String] (),所以如果签名指定Writer [String] Int,它确实不会编译。

没有类型签名问题,“日志”即收集的 [String] 列表对于两个变体都是相同的,因为 return 不会改变收集的输出“日志”。