我如何利用haskell中的State和Writer?

时间:2012-09-08 08:41:06

标签: haskell monads

当我浏览LYAH的最后一章并与ListZipper会面时,我给自己做了一个任务,使其成为状态monad,以便源代码看起来更清晰:

manipList = do
    goForward
    goForward
    goBack

同时,我希望通过利用Writer monad保留这个过程的日志,但我不知道如何将这两个Monad组合在一起。

我的解决方案是将[String]保留在状态中,我的源代码是

import Control.Monad
import Control.Monad.State

type ListZipper a = ([a], [a])

-- move focus forward, put previous root into breadcrumbs
goForward :: ListZipper a -> ListZipper a
goForward (x:xs, bs) = (xs, x:bs)

-- move focus back, restore previous root from breadcrumbs
goBack :: ListZipper a -> ListZipper a
goBack (xs, b:bs) = (b:xs, bs)

-- wrap goForward so it becomes a State
goForwardM :: State (ListZipper a) [a]
goForwardM = state stateTrans where
    stateTrans z = (fst newZ, newZ) where
        newZ = goForward z

-- wrap goBack so it becomes a State
goBackM :: State (ListZipper a) [a]
goBackM = state stateTrans where
    stateTrans z = (fst newZ, newZ) where
        newZ = goBack z

-- here I have tried to combine State with something like a Writer
-- so that I kept an extra [String] and add logs to it manually

-- nothing but write out current focus
printLog :: Show a => State (ListZipper a, [String]) [a]
printLog = state $ \(z, logs) -> (fst z, (z, ("print current focus: " ++ (show $ fst z)):logs))

-- wrap goForward and record this move
goForwardLog :: Show a => State (ListZipper a, [String]) [a]
goForwardLog = state stateTrans where
    stateTrans (z, logs) = (fst newZ, (newZ, newLog:logs)) where
        newZ = goForward z
        newLog = "go forward, current focus: " ++ (show $ fst newZ)

-- wrap goBack and record this move
goBackLog :: Show a => State (ListZipper a, [String]) [a]
goBackLog = state stateTrans where
    stateTrans (z, logs) = (fst newZ, (newZ, newLog:logs)) where
        newZ = goBack z
        newLog = "go back, current focus: " ++ (show $ fst newZ)

-- return
listZipper :: [a] -> ListZipper a
listZipper xs = (xs, [])

-- return
stateZipper :: [a] -> (ListZipper a, [String])
stateZipper xs = (listZipper xs, [])

_performTestCase1 = do
    goForwardM
    goForwardM
    goBackM

performTestCase1 =
    putStrLn $ show $ runState _performTestCase1 (listZipper [1..4])

_performTestCase2 = do
    printLog
    goForwardLog
    goForwardLog
    goBackLog
    printLog

performTestCase2 = do
    let (result2, (zipper2, log2)) = runState _performTestCase2 $ stateZipper [1..4]
    putStrLn $ "Result: " ++ (show result2)
    putStrLn $ "Zipper: " ++ (show zipper2)
    putStrLn "Logs are: "
    mapM_ putStrLn (reverse log2)

但问题是我不认为这是一个很好的解决方案,因为我必须手动维护我的日志。是否有任何混合State monad和Writer monad的方法,以便他们可以一起工作?

2 个答案:

答案 0 :(得分:17)

您正在寻找monad transformers。基本思想是定义类似WriterT的类型,它采用另一个monad并将其与Writer组合,创建一个新类型(如WriterT log (State s))。

注意:有一种惯例,即变压器类型以大写T结尾。因此MaybeWriter是正常的monad,MaybeTWriterT是它们的变换器等价物。

核心思想非常简单:对于一堆monad,你可以很容易地想象它们在bind上的行为。最简单的例子是Maybe。回想一下Maybe所做的就是在bind上传播Nothing

Nothing >>= f = Nothing
Just x >>= f = f x

因此,应该很容易想象使用此行为扩展任何 monad。我们所做的只是首先检查Nothing,然后使用旧monad的绑定。 MaybeT类型就是这样做的:它包装一个现有的monad,并为每个bind绑定一个这样的检查。您还必须实现return,实际上将值包装在Just中,然后使用内部monad的return。还有一些管道可以使一切工作,但这是重要的想法。

你可以想象Writer的一个非常相似的行为:首先我们组合任何新的输出,然后我们使用旧的monad的绑定。这基本上是WriterT的行为。还涉及其他一些细节,但基本思想非常简单实用。

Monad变形金刚是一种非常常见的“组合”monad的方式。有最常用monad的版本作为变换器,IO的明显例外,它总是必须位于monad堆栈的基础上。在您的情况下,WriterTStateT都存在,可以用于您的计划。

答案 1 :(得分:12)

Tikhon Jelvis给monad变形金刚一个很好的答案。但是,也有一个快速的解决方案。

mtl中的Control.Monad.RWS模块会导出RWS monad,它是ReaderWriterState monad的组合。