【发布时间】:2019-08-19 06:25:32
【问题描述】:
我正在从“Learn you a Haskell for Great Good”教程中学习 Haskell,我已经完成了 writer monads 的部分内容。这是我想不通的例子。
import Control.Monad.Writer
logNumber :: Int -> Writer [String] Int
logNumber x = writer (x, ["Got number: " ++ show x])
multWithLog :: Writer [String] Int
multWithLog = do
a <- logNumber 3
b <- logNumber 5
return (a*b) -- shouldn't return (3*5) result in (15,[]) ?
ghci> runWriter $ multWithLog
(15,["Got number: 3","Got number: 5"]) -- how did that happen?
我正在尝试了解 do 块返回的 Writer w a monad 中的 monoidic 值 w 是如何改变的。本教程没有详细说明mappending 是如何发生的。
Writer 的类型声明和 Writer 作为 monad 的实例声明由教程给出
newtype Writer w a = Writer { runWriter :: (a, w) }
instance (Monoid w) => Monad (Writer w) where
return x = Writer (x, mempty)
(Writer (x,v)) >>= f = let (Writer (y, v')) = f x in Writer (y, v `mappend` v')
如果return x 根据实例声明导致Writer (x, mempty) 并且对于monoid [a] 的mempty 是[],则不应return (a*b),相当于return (3*5),评估为(15,[]) ?
ghci> return (15) :: Writer [String] Int
WriterT (Identity (15,[]))
我把上面的命令给了 ghci,它返回一个 WriterT 类型的值,元组包含一个空列表,正如预期的那样。
multWithLog :: Writer [String] Int
multWithLog = logNumber 3 >>= (\a ->
logNumber 5 >>= (\b ->
return (a*b)))
我已经改用绑定运算符重写了do 块。上面的代码给出了与教程中的原始代码相同的结果。
我的印象是>>= 只从logNumber 3 的结果中提取Int 3 并将其提供给(\a -> logNumber 5 ...etc.),然后logNumber 对不同的值(@ 987654348@) 等等。这些操作是如何导致 Writer monad 的 [String] 部分被改变的?
【问题讨论】:
-
我想你可能对
return在 Haskell 中的含义感到困惑——这很常见,所以不要为此感到难过。在大多数命令式语言中,函数定义底部的return x意味着函数的结果是x。但是 Haskell 中的return只是一个函数,multWithLog的实际值是通过评估整个do块获得的表达式,而不仅仅是return的最后一行。特别是do块的连续行由>>=的应用程序连接,正是它在“幕后”附加了日志值。 -
“我的印象是 >>= 只提取 Int 3 ...”。那就是你错了。只有 3 作为 argument 传递给下一个函数,但
m >>= f不会简单地返回f返回的任何内容。它使用m和f的返回值创建一个new Writer 值。
标签: haskell monads monoids do-notation writer-monad