6

一般主题:虽然我发现将 monad 堆叠在一起的想法非常吸引人,但我很难想象代码是如何执行的,以及运行层的适当顺序是什么。下面是堆栈的一个示例:Writer、State、State 和 Error,没有特定的顺序(或者是否存在?)。

-----------------------
-- 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 )

但我无法让它工作。具体来说,我尝试运行堆栈,就好像 Error 在最外层一样:

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,

monadT_2运行相关的功能是否首先运行,然后将输出通过管道传输到与相关的功能monadT_1?所以换句话说,就像上面函数中的代码一样mStack,执行顺序是否完全取决于 monadT 的运行顺序(除了由 引入的结构中的任何刚性lift)?

4

1 回答 1

6

如果您尝试使用显式 monad 转换器堆栈键入计算,您会得到更多信息类型错误:

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然后说“什么给出?这一层不能同时存储一个Int和一个Map!”,这解释了你得到的类型错误。但是,因为您使用类型类而不是具体的 monad 转换器堆栈,所以ghc在等待指定具体堆栈之前无法知道这是一个类型错误。

解决方案很简单:只需将另一个添加lift到您的get,它现在将StateT像您预期的那样针对内层。

我个人更喜欢完全避免使用mtl类,并且总是单独使用transformers库来使用具体的 monad 转换器堆栈。它更加冗长,因为您必须准确地确定要使用的层lift,但它会减少以后的麻烦。

于 2013-04-30T00:32:32.133 回答