LYAH中关于作家单子的问题。 (如何追加到日志中?)

时间:2019-03-28 13:01:20

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

我正在从“学习Haskell的伟大成就”教程中学习Haskell,现在进入writer monads。这是我不知道的例子。

var fs = require('fs');

function find(name,  cb){
        fs.readFile('./db.json', 'utf8', function(err, data){
                if(err) return cb(err)
                data =  JSON.parse(data);
                cb(null, data[name]);
        });
}

find('student', function(err, data){
        console.log(data);
})

我正在尝试了解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? 块返回的w单子变量中的单调值Writer w a是如何改变的。本教程未详细介绍如何进行do

mappend的类型声明和Writer作为monad的实例声明由本教程给出

Writer

如果根据实例声明,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)的{​​{1}}为mempty,则不应该[a],哪个等于[],哪个等于return (a*b)

return (3*5)

我将上面的命令交给了ghci,它返回了(15,[])类型的值,该元组包含了一个空列表。

ghci> return (15) :: Writer [String] Int
WriterT (Identity (15,[]))

我改用绑定运算符重写了WriterT块。上面的代码给出的结果与教程中的原始代码相同。

我的印象是multWithLog :: Writer [String] Int multWithLog = logNumber 3 >>= (\a -> logNumber 5 >>= (\b -> return (a*b))) 仅从do的结果中提取>>= Int并将其交给3,然后{ logNumber 3作用于其他值((\a -> logNumber 5 ...etc.)),依此类推。这些操作如何导致Writer monad的logNumber部分被更改?

2 个答案:

答案 0 :(得分:2)

根据您发布的代码

(Writer (x,v)) >>= f =
   let (Writer (y, v')) = f x in Writer (y, v `mappend` v')

我们可以看到实际上仅通过f参数调用了x。 因此,在logNumber 3 >>= \a -> ...中,变量a确实绑定到3

但是,>>=在调用f之后会执行某些操作,即它将vv'组合在一起。在您的示例中,v是来自[String]的{​​{1}}的{​​{1}}。取而代之的是logNumber 3["Got number: 3"]一起对v'求值,就是\a -> ...

列表的

a=3["Got number: 5"],它将列表连接在一起。因此,我们得到了最终结果。

允许我有点草率,而忽略mappend包装器。我们得到

++

直觉上,我们可以假设作家monad中的值是有效的计算,它返回一个值(如Writer),并且副作用是在字符串列表中附加了一些消息。所有这些消息的日志在monad内部都是不可见的(我们只能追加到日志中),并且只有在使用return (a*b) = (a*b, []) logNumber 5 >>= \b -> return (a*b) = logNumber 5 >>= \b -> (a*b, []) = (5, ["Got number: 5"]) >>= \b -> (a*b, []) = (a*5, ["Got number: 5"] `mappend` []) = (a*5, ["Got number: 5"]) logNumber 3 >>= \a -> logNumber 5 >>= \b -> return (a*b) = logNumber 3 >>= \a -> (a*5, ["Got number: 5"]) = (3, ["Got number: 3"]) >>= \a -> (a*5, ["Got number: 5"]) = (3*5, ["Got number: 3"] `mappend` ["Got number: 5"]) = (15, ["Got number: 3", "Got number: 5"]) 从monadic上下文中退出时,才在最后可用。 >

答案 1 :(得分:0)

这应该解释一下:

> runWriter (return 15) :: (Int, [String])
(15,[])                       -- == runWriter $ writer (15, mempty)

> runWriter (logNumber 3)
(3,["Got number: 3"])         -- == runWriter $ writer (3, ["Got number: 3"])

> runWriter (logNumber 5)
(5,["Got number: 5"])         -- == runWriter $ writer (5, ["Got number: 5"])

> runWriter (logNumber 3 >> logNumber 5)
(5,["Got number: 3","Got number: 5"])   -- == ["Got number: 3"] ++ ["Got number: 5"]

> runWriter (logNumber 3 >>         logNumber 5 >>         return 15        )
(15,["Got number: 3","Got number: 5"])  -- == ["Got number: 3"] ++ ["Got number: 5"] ++ []

> runWriter (logNumber 3 >>= (\_ -> logNumber 5 >>= (\_ -> return 15    ) ) )
(15,["Got number: 3","Got number: 5"])

> runWriter (logNumber 3 >>= (\i -> logNumber 5 >>= (\j -> return (i*j) ) ) )
(15,["Got number: 3","Got number: 5"])

最后一行的单子表达式等同于multWithLog的{​​{1}}块。

注意lambda函数的嵌套:lambda函数

do

位于laemda函数

                                                    (\j -> return (i*j) )

这就是为什么 (\i -> logNumber 5 >>= (\j -> return (i*j) ) ) 中的i引用 outer lambda函数的参数return (i*j)的原因,该参数是从最外面的单子动作表达式{{ 1}}。

如何?因为根据您在问题中引用的i 的定义,所以我们有

logNumber 3

>>=

每个 runWriter ( Writer (x,v) >>= f ) = runWriter ( let (Writer (y, u)) = f x in Writer (y, v `mappend` u) ) = let (Writer (y, u)) = f x in runWriter ( Writer (y, v `mappend` u) ) = let (Writer (y, u)) = f x in (y, v `mappend` u) 动作中的“单值”不“变”。每个动作将其“单态值”贡献到 runWriter ( logNumber 5 >>= (\j -> return j) ) = -------- f ----- runWriter ( writer (5, ["Got number: 5"]) >>= (\j -> writer (j, mempty)) ) = -- x ------- v ------- -------- f --------------- let Writer (y, u) = ( (\j -> writer (j, mempty)) 5 ) -------- f --------------- x in (y, ["Got number: 5"] `mappend` u) = ------- v ------- let (y, u) = (5, mempty) in (y, ["Got number: 5"] `mappend` u) = (5, ["Got number: 5"] `mappend` mempty) 块的Writer型组合计算的整体“单值”中,该计算由其Writer构建do进行的类型子计算,由{{1中的 语义 元组的1}}字段代表操作(Writer 实现 )。

同样,通过组合每个元组的单等值部分(即它们的mappending字段)来组合此总值。单面组合为Writer,它是通过snd类型的计算在后台为我们完成的。

对于列表,Writer,而对于列表snd

您的问题,然后:

  •   

    mappend等于Writer吗?

    它应该而且确实如此,就像我们在答案开头看到的那样。

  •   

    mappend [a] [b] == [a] ++ [b] == [a,b]mappend [a,b] [] == [a,b] ++ [] == [a,b]包装器

    没关系。两者都是同构的,因为return (a*b)是无操作的。 (15,[])是Writer monad的实现的一部分;本书中给出的内容更简单,更易于理解。

  •   

    这些操作如何导致Writer单子的Writer部分被更改?

    未由特定作家使用的特定Monoid的WriterT进行更改,但进行了组合;作为 monadic组合的一部分,即monadic绑定的Identity定义;作为Monads的泛化函数调用协议,而Writer Monad的泛化是在幕后收集Monoid值,因此,除了用户函数在露天进行工作外,还可以将其附加在阴影中:

    WriterT

拥抱[String]-符号,是您的朋友。它可以帮助我们抽象