Haskell: <- in do notation for monad



我看到下面的例子来自Writer一节。

    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) 

结果是:

    ghci> runWriter multWithLog  
    (15,["Got number: 3","Got number: 5"]) 

我知道在do表示法中<->将从上下文中提取值。因此,根据Writer的声明,ab应该是格式为(Int, [String])的元组。我觉得abreturn (a*b)中应该是两个整数,否则我们不能做乘法。

我的误解是什么?有人能帮忙吗?多谢。

单子中的主要"工作"是在>>= (bind)函数中产生的。单子是某种计算生成器。所以如果你想知道在具体的Monad中如何进行计算,你需要打开它的>>=返回函数的实现。

。Writer的单子实现:

instance (Monoid w) => Monad (Writer w) where
    return a = Writer (a, mempty)
    m >>= k  = Writer $ let
        (a, w)  = runWriter m
        (b, w') = runWriter (k a)
        in (b, w `mappend` w')

所以它需要另一个作家m,得到它的计算结果a(在你的情况下是Int)和额外的上下文w ([String])通过在它上执行runWriter。然后将结果提供给函数k,该函数返回另一个Writer。在得到结果后,通过应用 mapappend (在list为++的情况下)将上下文组合为

所以字符串的组合发生在w mappend w'。这一切都发生在幕后。

可以看到,>>=只向函数k提供结果a (Int),没有任何上下文([String])。这就是为什么你的代码中没有任何对。

也许如果你重写你的代码没有"do"语法糖,它会更清楚:

import Control.Monad.Writer
logNumber :: Int -> Writer [String] Int 
logNumber x = Writer (x, ["Got number: " ++ show x])  
multWithLog =
    logNumber 3 >>=
    a -> logNumber 5 >>=
    b -> return (a*b)

暂时忽略logNumber的定义,只关心它的签名:

logNumber :: Int -> Writer [String] Int

我们知道Writer s是一个单子(给定Monoid s)。让我们暂时使用一个类型同义词StringWriter来使事情更清楚:

type StringWriter = Writer [String]
logNumber :: Int -> StringWriter Int

请记住,Writer s是一个单子,而StringWriter只是该单子的类型同义词。我们还改变了multWithLog:

的类型
multWithLog :: StringWriter Int  

现在应该清楚了,在StringWriter上下文中包装的值是一个Int,而不是一对。现在,回到你的语句:

我知道在do表示法中<-将从上下文中提取值。

此时,被提取的值的类型应该是Int

你的第一个陈述,ab是元组,是不完全正确的。

如果我们有像

foo = do
  ...
  baz <- bar
  quux
如果bar :: m a

,那么我们可以将baz视为quux中的a类型的值,因为<-(大致)是

foo = do
  ...
  bar >>= baz -> quux

Zeta很好地解释了为什么abInt而不是成对的。

我认为混淆的主要原因是最后一行

return (a*b) 

如果a = 3b = 5,这不应该导致Writer (15, [])吗?是的,但是["Got number: 3","Got number: 5"]从何而来呢?

答案在于>>=的实现。让我们去除do符号的语法糖:

multWithLog = do  
    a <- logNumber 3  
    b <- logNumber 5  
    return (a*b) 

等价于

logNumber 3 >>= (a ->
logNumber 5 >>= (b ->
return (a*b)))

等价于

Writer (3, ["Got number: 3"]) >>= (a ->
Writer (5, ["Got number: 5"]) >>= (b ->
Writer (a*b, [])))

等价于

Writer (3, ["Got number: 3"]) >>= (a ->
Writer (5, ["Got number: 5"]) >>= (b ->
Writer (3*5, [])))

记住,>>= 是一个二进制函数(接受两个参数的函数),返回一个Writer (Int, [String]),即一个Writer,一个Int和一个日志。从函数>>=返回的日志是第一个参数的日志["Got number 3", "Got number 5"]和第二个参数的日志[]的组合。



旁注:如果您查看括号,日志附加的实际顺序将是

["Got number: 3"] ++ (["Got number: 5"] ++ [])

最新更新