haskell monads continuations
Updated Wed, 24 Aug 2022 04:14:02 GMT

Escaping from the IO monad inside the Continuation monad

A confusing title for a confusing question! I understand a) monads, b) the IO monad, c) the Cont monad (Control.Monad.Cont), and d) the ContT continuation transformer monad. (And I vaguely understand monad transformers in general -- though not enough to answer this question.) I understand how to write a program where all the functions are in the Cont monad (Cont r a), and I understand how to write a program where all the functions are in the combined Cont/IO monad (ContT r IO a).

But I'm wondering how I might write a program where some functions are in a combined Cont/IO monad (ContT r IO a) and other functions are just in the Cont monad (Cont r a). Basically, I want to write the whole program in continuation style, but only use the IO monad where necessary (much like in "regular" Haskell code, I only use the IO monad where necessary).

For example consider these two functions, in non-continuation style:

foo :: Int -> IO Int
foo n = do
    let x = n + 1
    print x
    return $ bar x
bar :: Int -> Int
bar m = m * 2

Note that foo requires IO but bar is pure. Now I figured out how to write this code fully using the continuation monad, but I needed to thread IO through bar as well:

foo :: Int -> ContT r IO Int
foo n = do
    let x = n + 1
    liftIO $ print x
    bar x
bar :: Int -> ContT r IO Int
bar m = return $ m * 2

I do want all my code in continuation style, but I don't want to have to use the IO monad on functions that don't require it. Basically, I would like to define bar like this:

bar :: Int -> Cont r Int
bar m = return $ m * 2

Unfortunately, I can't find a way to call a Cont r a monad function (bar) from inside a ContT r IO a monad function (foo). Is there any way to "lift" a non-transformed monad into a transformed one? i.e., how can I change the line "bar x" in foo so that it can correctly call bar :: Int -> Cont r Int?


This is where Control.Monad.Class comes in. Make bar polymorphic in what monad it can work in:

bar :: MonadCont m => Int -> m Int
bar m = return $ m * 2

Note that the instances list at the bottom of the page shows that the instances of MonadCont known at the time the docs were generated include both Cont r and Monad m => ContT r m. Additionally, the MonadCont class is what defines the callCC function, which is what is necessary to use the continuation features. This means you can use the full expressiveness of continuations within bar, even though this example does not.

In this way, you write functions that provably can't use IO, because they don't have a MonadIO constraint, nor does their type explicitly mention IO. But they are polymorphic in which monad they work within, such that they can be called trivially from contexts that do include IO.

Comments (1)

  • +1 – Thanks. That works. I also found my own solution, which gave me exactly what I wanted (I didn't have to change Bar): liftCont :: Cont (m r) a -> ContT r m a; liftCont c = ContT $ runCont c. My solution unpacks the Cont and constructs a ContT. I think your solution is nicer because it's polymorphic and doesn't require actual manipulation of data structures, so tick for you. But I'll post mine as another answer, since it's helpful in case you can't modify bar. Also +1 for explanation of why it would be impossible to use IO in bar. — Jul 06, 2011 at 13:54