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

Jav*_*ran 9 monads haskell

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

manipList = do
    goForward
    goForward
    goBack
Run Code Online (Sandbox Code Playgroud)

同时,我想通过利用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)
Run Code Online (Sandbox Code Playgroud)

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

Tik*_*vis 17

你正在寻找monad变形金刚.基本思想是定义一个类型WriterT,它采用另一个monad并将其与Writer创建一个新类型(如WriterT log (State s))相结合.

注意:有一种惯例,即变压器类型以资本结束T.所以,Maybe并且Writer是正常的单子,MaybeT并且WriterT是他们的变压器等价物.

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

Nothing >>= f = Nothing
Just x >>= f = f x
Run Code Online (Sandbox Code Playgroud)

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

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

Monad变形金刚是一种非常常见的"组合"monad的方式.有一些最常用的monad版本作为变换器,但值得注意的例外是IO它们总是必须位于monad堆栈的基础上.在你的情况,无论是WriterTStateT存在,可用于您的程序.


opq*_*nut 13

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

所述Control.Monad.RWS在模块mtl出口RWS单子,这是的组合Reader,WriterState单子.