8

问题在底部以粗体显示。

LYAH 给出了将do符号与Writermonad一起使用的示例

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 中是否发生了一些变化,这使得上述引用的陈述不清楚/不准确?

4

1 回答 1

6

等效的重写是进一步

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

就是“返回” ()tell ["something"]显然,洗牌

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

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

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

于 2020-06-03T20:27:29.350 回答