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
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.
liftCont :: Cont (m r) a -> ContT r m a;
liftCont c = ContT $ runCont c. My solution unpacks the
Contand 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
External links referenced by this document: