2013-04-30 59 views
6

一般主题:虽然我发现将monad堆叠在一起的想法非常有吸引力,但我在绘制代码的执行方式方面遇到很多麻烦,以及运行这些图层的适当命令是什么。下面是一个堆栈的例子:Writer,State,State和Error,没有特定的顺序(或者是否存在?)。您如何推断monadT堆栈中函数的执行顺序?

----------------------- 
-- Utility Functions -- 
----------------------- 

type Memory = Map String Int 
type Counter = Int 
type Log  = String 

tick :: (MonadState Counter m) => m() 
tick = modify (+1) 

record :: (MonadWriter Log m) => Log -> m() 
record msg = tell $ msg ++ "; " 

------------------ 
-- MonadT Stack -- 
------------------ 

mStack :: (MonadTrans t, MonadState Memory m, MonadState Counter (t m), MonadError ErrMsg (t m), MonadWriter Log (t m)) => t m Int 
mStack = do 
    tick 
    m <- lift get 
    let x = fromJust (M.lookup "x" m) in x 
    record "accessed memory" 
    case True of 
     True -> return 100 
     False -> throwError "false" 

请注意mStack,是否有错误被抛出与否无关的功能的任何其他部分。

现在非常想输出看起来像这样:

(Right 100, 1, "accessed memory", fromList [...]) 

或一般:

(output of errorT, output of stateT Counter, output of writerT, output of StateT Memory) 

但我无法得到它的工作。具体地讲,我试图运行堆栈仿佛错误是在最外层上:

mem1 = M.fromList [("x",10),("y",5)] 
runIdentity $ runWriterT (runStateT (runStateT (runErrorT mStack) 0) mem1) "" 

但正在此错误消息:

Couldn't match type `Int' with `Map [Char] Int' 

上述实例之外,在一般情况下,当我我打电话:

runMonadT_1 (runMonadT_2 expr param2) param1

是与01的功能先运行,然后将输出传送到与monadT_1相关的功能?换句话说,与代码在上面的函数mStack中看起来一样,执行的顺序完全取决于monadT的运行顺序(除了由lift引入的任何刚性结构)?

回答

6

你会得到更翔实的类型的错误,如果你曾试图使用一个明确的单子转换堆栈键入您的计算:

mStack :: ErrorT String (StateT (Map String Int) (StateT Int Writer)) Int 

假如你这样做,ghc会错误类型更早捕获。其原因是,你在最顶层使用以下两条命令中mStack

modify (+1) -- i.e. from `tick` 
... 
yourMap <- lift get 

如果你要这给一个明确的堆栈,那么你会赶上的错误:既modifylift get会瞄准他们遇到的第一层StateT层,碰巧是相同的StateT层。

modifyErrorT层开始,直到它碰到外StateT层向下前进,并得出结论认为外StateT必须使用一个Int状态。 get从外层StateT层开始,注意到它已经在StateT层中,并且完全忽略了内层StateT层,因此它得出结论外层StateT层必须存储Map

ghc然后说:“什么给?这层不能同时存储IntMap!“,这说明了你得到的类型错误。但是,因为你使用类型类而不是具体的monad变量堆栈,所以ghc不可能知道这是直到你指定一个具体层叠等待类型的错误

解决方案很简单:。只需添加另一个liftget,现在将针对内StateT层像你预期

我个人更喜欢以避免mtl完全可以使用transformers函数库,并且一直使用具体的monad变压器堆栈。因为你必须准确地使用lift来确定你想要的图层,但是它会减少头痛。