Monad transformers II: composition of error and state transformers

The except (error) monad transformer

Composition of effects

Using types, we will have computations of the form

  m_st (Either err a) 

where m_st is a state monad.

Roughly speaking, this type is like having

  s -> (Either err a, s) 

Since the state is hidden inside m_st, it is not affected by whether we return Right a or Left err.

State changes will not be rolled back when there's an exception.
When there is an exception, the state information is there!

Interpreter 3: ExceptT on the inside

code

Interpreter 4: ExceptT on the outside

code

Which one is better? ExceptT outside or inside?

Interpreter 5: Adding I/O

code

Implementing monad transformers

MyStateT: a state monad transformer

Monad transformers: lifting non-proper morphisms

MyExceptT: an error monad transformer

MyReaderT: a reader monad transformer

Interpreter 6: an interpreter with MyStateT, MyExceptT, and MyReaderT

code

To run a computation of type Eval a, we need to run the whole monad stack

Lifting operations I

Lifting operations II

  eval (Catch e1 e2)  =
       let st1  = runSTInt (eval e1)
           st2  = runSTInt (eval e2)
           ... 

So far, we obtained functions st1 and st2 which produce, when given an state, a monadic computation in the next layer of the stack (i.e., a reader monadic computation). In other words, to run st1 and st2, and therefore going deeper into the monad stack, it is necessary to provide a state. Since we have none in scope, we need to break the abstraction of the MyStateT monad transformer in order to introduce a binding for the state.

  eval (Catch e1 e2)  =
       MyStateT $ \s -> let
                           st1  = runSTInt (eval e1)
                           st2  = runSTInt (eval e2)
                           env1 = runEnv (st1 s)
                           env2 = runEnv (st2 s)
                           ... 

At this point, we are at the level of the reader monad in our stack.

Function env1 and env2 produce, when given an environment, a computation in the error layer monad. As before, since we do not have an environment in scope, we need to break the abstraction of the MyReaderT monad transformer to introduce a binding for the environment.

  eval (Catch e1 e2)  =
       MyStateT $ \s -> let
                           st1  = runSTInt (eval e1)
                           st2  = runSTInt (eval e2)
                           env1 = runEnv (st1 s)
                           env2 = runEnv (st2 s)
                       in MyReaderT $ \r -> ... 

With the environment r in scope, env1 r :: MyExceptT Err Identity a and env2 r :: MyExceptT Err Identity a and we can feed function catchError with them.

   eval (Catch e1 e2)  =
        MyStateT $ \s -> let
                            st1  = runSTInt (eval e1)
                            st2  = runSTInt (eval e2)
                            env1 = runEnv (st1 s)
                            env2 = runEnv (st2 s)
                        in MyReaderT $ \r -> catchError (env1 r) (\_err -> env2 r) 

Summary