Heavy lifting
=============
I'm trying to improve my skills with monad stacks. Till now, I've got
away with quite a lot, but not *grokked* what I was doing. (Sorry, I'm
British, and we double consonants before an *-ed* or *-ing* suffix, if
the vowel is short.)
Here's some simple code::
module Main where
import Control.Monad.Reader
main = do
putStrLn "Hello, world!"
runReaderT mything 5
mything :: ReaderT Int IO ()
mything = do
i <- ask
lift $ putStrLn $ "i == " ++ show i
Of course ``main`` is in the ``IO`` monad; ``mything`` is in a stack
consisting of ``ReaderT`` wrapped around ``IO``. The interesting part
is that we can use ``lift`` to run an action in the inner monad.
The definition of ``lift`` looks like this::
lift :: MonadTrans t => forall m a. Monad m => m a -> t m a
Lift a computation from the argument monad to the constructed monad.
Now, I'm a bit vague about what ``forall`` means there, but lets not
worry about that now. Clearly here the "argument monad" is ``IO``, and
the "constructed monad" is ``ReaderT``. It's the computation itself that
is "lift"ed::
+---------+
| ReaderT | ^ ^
+---------+ | | lift
| IO | | putStrLn ... |
+---------+
OK, so let's look at monad-control. This offers the ``control`` function
which can be used like so::
module Main where
import Control.Monad.Reader
import Control.Monad.Trans.Control
main = do
putStrLn "Hello, world!"
runReaderT mything 5
mything :: ReaderT Int IO ()
mything = do
i <- ask
control $ \run -> do
putStrLn $ "1. i == " ++ show i
run $ do
j <- ask
lift $ putStrLn $ "2. j == " ++ show j
lift $ putStrLn $ "3. i == " ++ show i
Within the ``control`` block, we are in ``IO`` (and hence can use
``putStrLn`` directly). The ``run`` function that is constructed by
``control`` gives us an escape hatch back to ``ReaderT`` (and, as you'd
expect, once we're back in ``ReaderT`` we can use ``lift`` again for
``IO`` actions).
Now, it would be nice to split the control block off into a separate
function, to get a better idea of the types involved. To make that work,
though, we have to give it a fairly hairy type signature involving
RunInBase. That utilizes rank *N* types, so we also need the relevant
extension::
{-# LANGUAGE RankNTypes #-}
module Main where
import Control.Monad.Reader
import Control.Monad.Trans.Control
main = do
putStrLn "Hello, world!"
runReaderT mything 5
mything :: ReaderT Int IO ()
mything = do
i <- ask
lift $ putStrLn $ "1. i == " ++ show i
control otherthing
lift $ putStrLn $ "4. i == " ++ show i
otherthing :: RunInBase (ReaderT Int IO) IO -> IO (StM (ReaderT Int IO) ())
otherthing run = do
putStrLn $ "2. in IO"
run $ do
j <- ask
lift $ putStrLn $ "3. j == " ++ show j
In passing, it is curious that in the previous example GHC was happy to
*use* a rank *N* type without the extension; it is only if we need to
*name* it that the extension is required.
Anyway, let's liven things up again. Here's some code with a more exotic
monad stack::
module Main where
import Control.Monad.Reader
import Control.Monad.Writer
main = do
putStrLn "Hello, world!"
cnt <- execWriterT $ runReaderT mything 5
putStrLn $ "cnt is " ++ show (getSum cnt)
mything :: ReaderT Integer (WriterT (Sum Integer) IO) ()
mything = do
i <- ask
lift $ tell $ Sum 1
tell $ Sum 1
lift $ lift $ putStrLn $ "i is " ++ show i
liftIO $ putStrLn $ "i is " ++ show i
I've used stacks like this before. It's always looked to me that in the
function call ``ReaderT`` is on the "inside", whereas in the type it's
on the "outside". After all, if I write ``Right $ Just 5`` I get a value
of type ``Either a (Maybe Int)``. Anyway, for reasons that elude me at
the moment, with monads it's different. Each new monadic environment
adds a new monad to the top of the stack, and the left of the type
This example demonstrates some further wrinkles. We can simply use
``ask`` from the ``ReaderT`` monad of coures. And we can lift ``tell``
from ``WriterT``, but we can also write a plain ``tell``, apparently
unlifted. (There's some kind of magic going on under the hood that I
don't understand to make this work.)
To run a computation in ``IO``, we can, as you might expect, lift it
twice. But as an alternative we can simply use ``liftIO`` which plucks
``IO`` from the bottom of any monadic stack in a single step, no matter
how deep the stack. (Again, this is magic as far as my current level of
understanding is concerned.)
Using monad-control with this more complex stack is not significantly
different from the earlier example. I've used a type alias to stop the
type of ``otherthing`` running off the side of the screen::
{-# LANGUAGE RankNTypes #-}
module Main where
import Control.Monad.Reader
import Control.Monad.Trans.Control
import Control.Monad.Writer
main = do
putStrLn "Hello, world!"
cnt <- execWriterT $ runReaderT mything 5
putStrLn $ "cnt is " ++ show (getSum cnt)
type Stack = ReaderT Integer (WriterT (Sum Integer) IO)
mything :: Stack ()
mything = do
i <- ask
tell $ Sum 1
control otherthing
liftIO $ putStrLn $ "i is " ++ show i
otherthing :: RunInBase (Stack) IO -> IO (StM Stack ())
otherthing run = do
putStrLn "in IO here"
run $ do
j <- ask
liftIO $ putStrLn $ "j is " ++ show j
tell $ Sum 1
I hope somebody out there finds these little examples useful. My style
of learning revolves around lots and lots of tiny examples; each one
(hopefully!) understood thoroughly. This predilection seems to put me in
a minority of Haskell programmers; so many packages on hackage have not
a single example. Yes, in theory I can work out how things fit together
from the types, but I often feel that I'm struggling to reverse engineer
how a package is *intended* to be used.
I like instant rewards, and it's nice to start with an example that will
actually compile. (The alternative, starting by bouncing back and forth
between a bunch of error messages and the package documentation, is much
less appealing.) Once I have something that will build, I can
continuously deform it till it does what I want. If any small step
introduces an error, then I can focus on what I did wrong in that step.
Anyway, as a result of putting together this blog post, I have improved
my understanding of monad stacks considerably. Although it's not reached
the level of *grokking* yet, I was able to generalize ``runTCPClient ::
ClientSettings -> (AppData -> IO a) -> IO a`` and ``runTCPServer ::
ServerSettings -> (AppData -> IO ()) -> IO ()`` from the
**streaming-commons** package to work with any monad ``m`` for which
``(MonadIO m, MonadBaseControl IO m) => m``.