【问题标题】:LYAH - Understanding comment about "tell" when chaining Writer monadsLYAH - 在链接 Writer monad 时理解关于“tell”的评论
【发布时间】:2020-09-22 16:40:12
【问题描述】:

问题在底部以粗体显示。

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)))

然后这本书提出了一个对我来说似乎不清楚,如果不是不准确的话:

return (a*b) 是最后一行很重要,因为 do 表达式中最后一行的结果是整个 do 表达式的结果。如果我们将 tell 作为最后一行,则 () 将是这个 do 表达式的结果。我们会丢失乘法的结果。但是,日志将是相同的。

因此,我的第一个疑问来了:如果tell 结果为(),那么代码不应该甚至不能编译,因为() 不能匹配预期的类型Int,也不匹配任何其他类型除了() 本身;那么作者想告诉我们什么?为了使这个不基于意见,自从本书编写以来,Haskell 发生了一些变化,导致上述引用的陈述不清楚/不准确?

【问题讨论】:

  • 如果你删除return (x*y) 行,那么这确实不会编译,因为那时() 将是结果。我真的看不出你引用的段落有什么问题?
  • 一方面它以日志将是相同的结尾。哪个日志,如果代码甚至没有编译?
  • 好吧,作者可能假设您将签名更改为Writer [String] () :)。在这种情况下,结果将为(),但仍会将["something"] 写入日志。
  • 这正是作者的重点。最后一行很重要,否则返回值(以及此处的类型)是不同的。
  • 如果有人读到这篇文章想玩这个 LYAH 例子,看看它是否还能编译,试试 LYAH 的 Jupyter 改编版(第 13 章)github.com/jamesdbrock/learn-you-a-haskell-notebook

标签: haskell monads do-notation rewriting writer-monad


【解决方案1】:

等价的重写是进一步

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,它确实不会编译。

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

【讨论】:

    猜你喜欢
    • 2016-02-26
    • 2019-08-19
    • 2012-02-02
    • 2014-07-27
    • 1970-01-01
    • 2012-08-02
    • 1970-01-01
    • 2011-05-27
    • 2018-02-08
    相关资源
    最近更新 更多