You would have gotten a more informative type error if you had tried to type your computation using an explicit monad transformer stack:
mStack :: ErrorT String (StateT (Map String Int) (StateT Int Writer)) Int
Had you done that, ghc
would have caught the type error earlier. The reason is that you use the following two commands within mStack
at the top-most level:
modify (+1) -- i.e. from `tick`
...
yourMap <- lift get
If you were to give this an explicit stack, then you'd catch the mistake: both modify
and lift get
are going to target the first StateT
layer they encounter, which happens to be the same StateT
layer.
modify
begins from the ErrorT
layer and proceeds downward until it hits the outer StateT
layer, and concludes that the outer StateT
must be using an Int
state. get
begins from the outer StateT
layer, notices that it's already in a StateT
layer and ignores the inner StateT
layer entirely, so it concludes that the outer StateT
layer must be storing a Map
.
ghc
then says "What gives? This layer can't be storing both an Int
and a Map
!", which explains the type error you got. However, because you used type classes instead of a concrete monad transformer stack, there was no way that ghc
could know that this was a type error in waiting until you specified a concrete stack.
The solution is simple: just add another lift
to your get
and it will now target the inner StateT
layer like you intended.
I personally prefer to avoid mtl
classes entirely and always work with a concrete monad transformer stack using the transformers
library alone. It's more verbose because you have to be precise about which layer you want using lift
, but it causes fewer headaches down the road.