Monad Epiphany

I’ve just had an epiphany about Haskell monad transformers. Consider the following nested monad transformers:

type ErrorStateIO = ErrorT String (StateT Int IO) Int

I’d always thought that IO was the innermost monad, and some people erroneously describe this as the “IO monad wrapped in the StateT monad transformer which is in turn wrapped in the ErrorT monad transformer.”

It’s actually the reverse. IO is the outermost monad, and this makes sense because in an implementation with ErrorStateIO as the return type, one has to use use “lift” to talk to the next outermost layer. This can be demonstrated by the following code.

evalStateT (runErrorT (foo 2)) 2

That’s how we call these nested monads. Note that evalStateT is again outermost of the monad transformers. (IO is implicitly outermost as a part of StateT.) The return type of evalStateT is IO (Either String Int).

I’m new to Haskell and never realized this. But it makes enormous sense. I’m getting better than ever at using monads and monad transformers, but I don’t fully understand their internals. I’m hoping for another epiphany.