How do you talk about the execution order of functions on the monadT stack?

General topic: Although I think that the idea of ​​putting monads together is very attractive, I have problems with the image of how the code is executed, and what are the corresponding orders for launching the layers. The following is one example stack: Writer, State, State, and Error, in a specific order (or is there?).

----------------------- -- 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 (tm), MonadError ErrMsg (tm), MonadWriter Log (tm) ) => tm 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" 

Note that in mStack , whether an error occurs or not has nothing to do with any other part of the function.

Now, ideally, I want the result to look like this:

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

or even:

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

But I can’t make it work. In particular, I tried to launch the stack as if the error was on the outermost layer:

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

But I get this error message:

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

The above instance aside, in general, when I call:

runMonadT_1 ( runMonadT_2 expr param2 ) param1 ,

functions related to monadT_2 , then this output is passed to functions related to monadT_1 ? In other words, how is it necessary, since the code looks like in the above mStack function, the order of execution depends entirely on the order in which monadT is executed (except for any rigidity in the structure introduced by lift )?

+6
source share
1 answer

You would have a more informative type error if you tried to enter your calculations using the explicit monad transformer stack:

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

If you did, ghc catch a type error earlier. The reason is that you use the following two commands in mStack at the highest level:

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

If you were to provide this explicit stack, you will catch an error: both modify and lift get will target the first StateT layer they encounter, which is the same StateT .

modify begins with the ErrorT layer and continues down until it reaches the outer StateT layer, and completes that the outer StateT must use state Int . get starts with the outer layer of StateT , notices that it is already in the StateT layer and completely ignores the inner StateT layer, so it concludes that the outer layer of StateT should store the Map .

ghc then says: “What gives? This layer cannot store both Int and Map !”, which explains the type error you received. However, since you used class types instead of a specific monad transformer stack, there was no way that ghc could know that it was a type error while waiting until you specify a specific stack.

The solution is simple: just add another lift to your get , and now it will target the inner StateT layer, as you expected.

I personally prefer to completely avoid mtl classes and always work with a specific monad transformer stack using only the transformers library. This is more detailed because you need to be precise as to which layer you want to use lift , but it causes less headaches along the way.

+6
source

Source: https://habr.com/ru/post/943929/


All Articles