如何在Haskell中玩Control.Monad.Writer?


96

我是函数编程的新手,并且最近在Learn Has a Haskell中学习,但是当我读完本章时,我陷入了以下程序:

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)

我将这些行保存在.hs文件中,但未能将其导入到我的ghci中,抱怨如下:

more1.hs:4:15:
    Not in scope: data constructor `Writer'
    Perhaps you meant `WriterT' (imported from Control.Monad.Writer)
Failed, modules loaded: none.

我通过“:info”命令检查了类型:

Prelude Control.Monad.Writer> :info Writer
type Writer w = WriterT w Data.Functor.Identity.Identity
               -- Defined in `Control.Monad.Trans.Writer.Lazy'

从我的角度来看,这应该像是“ newtype Writer wa ...”之类的东西,所以我对如何馈送数据构造函数和获取Writer感到困惑。

我猜可能是与版本有关的问题,我的ghci版本是7.4.1


2
对于练习,建议不要导入声明并将其自己写入文件中。
sdcvvc 2012年

5
不久前,我与作者交谈,他确认这本书的在线版本已经过时。PDF上还有更新的版本:[here ]
Electric Coffee

@Electric,我有同样的问题,能否请您提供链接?您上面的链接已损坏。
Bulat M.

Answers:


126

程序包Control.Monad.Writer不导出数据构造函数Writer。我想写LYAH的时候就不一样了。

在ghci中使用MonadWriter类型类

相反,您可以使用writer函数创建作者。例如,在ghci会话中,我可以执行

ghci> import Control.Monad.Writer
ghci> let logNumber x = writer (x, ["Got number: " ++ show x])

现在logNumber是一个创建作者的函数。我可以要求它的类型:

ghci> :t logNumber
logNumber :: (Show a, MonadWriter [String] m) => a -> m a

这告诉我,推断的类型不是返回特定作者的函数,而是实现MonadWriter类型类的任何东西。我现在可以使用它:

ghci> let multWithLog = do { a <- logNumber 3; b <- logNumber 5; return (a*b) }
    :: Writer [String] Int

(输入实际上全部输入在一行上)。在这里,我指定的类型multWithLogWriter [String] Int。现在我可以运行它:

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

您会看到我们记录了所有中间操作。

为什么代码是这样写的?

为什么要费心创建MonadWriter类型类呢?原因是与monad变压器有关。正如您正确认识到的那样,最简单的实现方法Writer是将其作为一对新包装器:

newtype Writer w a = Writer { runWriter :: (a,w) }

您可以为此声明一个monad实例,然后编写函数

tell :: Monoid w => w -> Writer w ()

它只是记录其输入。现在,假设您想要一个具有日志记录功能的monad,但它还需要执行其他操作-说它也可以从环境中读取。您可以将其实现为

type RW r w a = ReaderT r (Writer w a)

现在,因为编写器位于ReaderTmonad转换器内,所以如果您想记录输出,则无法使用tell w(因为该操作仅适用于未包装的编写器),而您必须使用lift $ tell w,它tell通过来“提升” 功能,ReaderT以便可以访问内部作家莫纳德。如果您需要两层变压器(例如您还想添加错误处理),则需要使用lift $ lift $ tell w。这很快变得笨拙。

相反,通过定义类型类,我们可以将编写器周围的任何monad转换器包装器变成编​​写器本身的实例。例如,

instance (Monoid w, MonadWriter w m) => MonadWriter w (ReaderT r m)

也就是说,如果w是一个monoid,并且m是一个MonadWriter w,则ReaderT r m它也是一个MonadWriter w。这意味着我们可以tell直接在转换后的monad上使用该函数,而不必担心通过monad转换器显式地提升它。


31
“我想写LYAH的时候就不一样了。” 对。这与改变mtl会从主要版本1 * 2。*,LYAH和雨水集蓄写后不久。极其不幸的时机导致了初学者之间的困惑。
丹尼尔·菲舍尔

2
我现在使用的是GHC 7.8.3版本,必须导入Control.Monad.Trans.Writer。另外的类型logNumberlogNumber :: (Show a, Monad m) => a -> WriterT [[Char]] m a我。
kmikael 2015年

@kmikael您可能未安装该mtl库(这可能意味着您已经安装了GHC的基本安装,如minGHC,而不是Haskell平台)。从命令提示符下运行cabal updatecabal install mtl,然后再试一次。
克里斯·泰勒

克里斯,我使用的是Haskell平台,并且安装了mtl,尽管我再次安装了它,但现在看来它的工作方式与您的回答相同。我不知道怎么了 谢谢。
kmikael 2015年

该书的印刷副本正确无误。它包括一个段落解释说,writer代替使用Writer,因为后者的价值构造函数,不是由模块导出,而前者是,它可以被用来创建你将与构造函数创建相同的值,但不会不允许模式匹配。
恩里科·玛丽亚·德·安吉利斯

8

提供了一个称为“ writer”的函数来代替“ Writer”构造函数。更改:

logNumber x = Writer (x, ["Got number: " ++ show x])

至:

logNumber x = writer (x, ["Got number: " ++ show x])


6
这对现有答案有什么作用?
dfeuer '16

1

我有一个类似的消息从尝试LYAH “对于少数单子更多”使用在线编辑哈斯克尔repl.it

我从以下位置更改了导入:

import Control.Monad.Writer

至:

import qualified Control.Monad.Trans.Writer.Lazy as W

所以我的代码现在看起来像这样(从Kwang的Haskell Blog那里得到启发):

import Data.Monoid
import qualified Control.Monad.Trans.Writer.Lazy as W


output :: String -> W.Writer [String] ()
output x = W.tell [x]


gcd' :: Int -> Int -> W.Writer [String] Int  
gcd' a b  
    | b == 0 = do  
        output ("Finished with " ++ show a)
        return a  
    | otherwise = do  
        output (show a ++ " mod " ++ show b ++ " = " ++ show (a `mod` b))
        gcd' b (a `mod` b)

main :: IO()
main = mapM_ putStrLn $ snd $ W.runWriter (gcd' 8 3) 

代码目前可在此处运行

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.