Note to self on constructing a monad transformer. In a way this follows on from the earlier post Applicatives compose, monads do not. Literate Haskell source for this post is available here: https://github.com/carlohamalainen/playground/tree/master/haskell/transformers.
The Reader Monad
Reader is a data type that encapsulates an environment. The
runReader function takes an environment (a
Reader) and runs it, producing the result of type
Note that with record syntax, the type of
runReader is actually
Reader that increments its environment value, returning the same type:
Reader that converts its environment value into a string:
“Run” the reader using
ghci> runReader reader1 100 101 ghci> runReader reader2 100 "reader2: 100"
There’s nothing magic about
runReader; it is just taking the function out
of the data type. We can do it manually ourselves:
ghci> runReader' reader1 100 101
Reader an instance of
The definition of
>>= is relatively easy to work out using hole driven development.
Or in the more readable
ghci> runReader eg1' 100 "hey, 100"
Note that we use
id to produce a
Reader that just passes its environment argument along as the output. See
readerAsk later for the equivalent situation which uses
 is an instance of
Monad we can also do things like this:
ghci> runReader eg1'' 100 "hey, "
We’d like to use the
Reader in conjunction with other monads, for example running
IO actions but
having access to the reader’s environment. To do this we create a transformer, which we’ll call
r parameter is the reader environment as before,
m is the monad (for example
a is the result type, as before. Again, note the actual type of
It takes a
ReaderT and provides us with a function that takes a reader environment of type
r and produces a monadic value. Following from the
Monad instance declaration for
Reader it is straightforward to write the definition for
ReaderT as the outer monad, we would like to “lift” a monadic computation into the reader. A monadic action doesn’t know anything about the reader environment, so to lift the monadic value we just create a
ReaderT with a function that ignores the environment:
Note the type of
return on the first line of
egLift. In this context,
return :: a -> m a is the equivalent of
id :: a -> a from the earlier
ghci> runReaderT egLift 100 "boo" "value of e: 100"
More generally, let’s name the
If we want to modify the environment, we use
withReaderT which takes as its first parameter a function to modify the environment. Note that the result is of type
ReaderT r' m a so the function is of type
r' -> r which modifies the supplied reader of type
ReaderT r m a.
Lastly, it is convenient to apply a function to the current environment.
This is almost the same as
readerAsk except that we create a reader that returns
f r instead of
f. In other words:
Finally, we collect the functions
readerReader in a type class
MonadReader and give them more general names:
An instance declaration for our
Now we can write fairly succinct code as follows. Use the
IO monad as the inner monad in a
ReaderT, with an
Int environment and
String result type.
ghci> result <- runReaderT eg2 100 "I'm in the eg2 function and the environment is: 100" ghci> result "returned value: 100"
StateT, ReaderT, and ask
inside0 has the
IO monad nested inside a
inside1 has a
StateT with the
ReaderT inside. Yet in both we can write
e <- ask.
Inspecting the types using ghcmod-vim we find that
so there must be a type class that provides the
ask function for
First, inspect the type of
ask using ghci (here we are using the definitions from
Control.Monad.State, not the implementation in this file).
ghci> :t ask ask :: MonadReader r m => m r
StateT must have a
MonadReader instance. Confirm this with
ghci> :i StateT (lots of stuff) instance MonadReader r m => MonadReader r (StateT s m) -- Defined in `Control.Monad.Reader.Class' (lots of stuff)
Looking in Control.Monad.Reader.Class we find:
lift function comes from Monad.Trans.Class,
and looking there we see:
So actually we are after the
MonaTrans type class. Again looking at
StateT we see:
ghci> :i StateT instance MonadTrans (StateT s) (lots of stuff) -- Defined in `Control.Monad.Trans.State.Lazy' (lots of stuff)
So off we go to Control.Monad.Trans.State.Lazy where we finally get the answer:
This shows that for
lift function takes a monadic action and produces a state transformer that takes the current state, runs the action, and returns the result of the action along with the unmodified state. This makes sense in that the underlying action should not modify the state. (There are some laws that monad transformers must satisfy.)
If we did not have the
MonadTrans type class then we would have to embed the
ask call manually:
Obviously this is laborious and error-prone. In this case, Haskell’s type class system lets us implement a few classes so that
put, etc, can be used seamlessly no matter which monad transformer we are in.
The downside is that reading Haskell code can be nontrivial. In our case we had to follow a trail through a few files to see where
ask was actually implemented, and finding the right definition relied on us being able to infer the correct types of certain sub-expressions.
Personally I am finding more and more that plain vim and ghci does not cut it for Haskell development, and something richer like ghcmod-vim is a real necessity. Shameless self plug: ghc-imported-from is also very useful :-)