当我浏览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的方法,以便他们可以一起工作?
答案 0 :(得分:17)
您正在寻找monad transformers。基本思想是定义类似WriterT
的类型,它采用另一个monad并将其与Writer
组合,创建一个新类型(如WriterT log (State s)
)。
注意:有一种惯例,即变压器类型以大写T
结尾。因此Maybe
和Writer
是正常的monad,MaybeT
和WriterT
是它们的变换器等价物。
核心思想非常简单:对于一堆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堆栈的基础上。在您的情况下,WriterT
和StateT
都存在,可以用于您的计划。
答案 1 :(得分:12)
Tikhon Jelvis给monad变形金刚一个很好的答案。但是,也有一个快速的解决方案。
mtl
中的Control.Monad.RWS
模块会导出RWS
monad,它是Reader
,Writer
和State
monad的组合。