你如何推理monadT堆栈中函数的执行顺序?

chi*_*ro2 6 monads haskell monad-transformers

一般主题:虽然我发现将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"
Run Code Online (Sandbox Code Playgroud)

请注意mStack,是否抛出错误与函数的任何其他部分无关.

理想情况下,我希望输出看起来像这样:

( Right 100, 1, "accessed memory", fromList [...])
Run Code Online (Sandbox Code Playgroud)

或者一般来说:

( output of errorT, output of stateT Counter, output of writerT, output of StateT Memory )
Run Code Online (Sandbox Code Playgroud)

但我无法让它发挥作用.具体来说,我尝试运行堆栈,就像最外层的错误一样:

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

但我收到此错误消息:

  Couldn't match type `Int' with `Map [Char] Int'
Run Code Online (Sandbox Code Playgroud)

除了上面的例子,一般来说,当我打电话时:

runMonadT_1 ( runMonadT_2 expr param2 ) param1,

是先monadT_2运行相关的函数,然后将输出通过管道输入到与之相关的函数中monadT_1?换句话说,正如代码在上面的函数mStack中所看到的那样,执行的顺序完全取决于monadT的运行顺序(除了引入的结构中的任何刚性lift)?

Gab*_*lez 6

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

mStack :: ErrorT String (StateT (Map String Int) (StateT Int Writer)) Int
Run Code Online (Sandbox Code Playgroud)

如果你这样做了,ghc就会早点发现类型错误.原因是您mStack在最顶层使用以下两个命令:

modify (+1)  -- i.e. from `tick`
...
yourMap <- lift get
Run Code Online (Sandbox Code Playgroud)

如果你是这给出一个明确的堆栈,那么你会赶上的错误:既modifylift get将要针对第一StateT他们遇到层,这恰好是同StateT层.

modifyErrorT图层开始并向下进行直到它到达外层StateT,并断定外部StateT必须使用Int状态. get从外层开始StateT,注意到它已经在一个StateT层中并StateT完全忽略了内层,因此它得出结论外层StateT必须存储一个Map.

ghc然后说"什么给出?这个图层不能同时存储一个Int和一个Map!",这解释了你得到的类型错误.但是,因为您使用的是类型而不是具体的monad转换器堆栈,所以ghc在您指定具体堆栈之前,无法知道这是一个类型错误.

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

我个人更喜欢mtl完全避免课程,并且总是使用transformers单独的库来使用具体的monad变换器堆栈.它更加冗长,因为你必须准确地确定你想要使用哪一层lift,但它会导致更少的麻烦.