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 t开发者_StackOverflowhat 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.
I found that this does exactly what I wanted (without having to change Bar
):
liftCont :: Cont (m r) a -> ContT r m a
liftCont = ContT . runCont
This unpacks the Cont
and constructs a ContT
.
I can then use liftCont
to call Bar
from Foo
:
foo n = do
let x = n + 1
liftIO $ print x
liftCont $ bar x
I don't think this is "nicer" than Carl's solution (I gave him the tick), but I posted it here because it lets you use Bar
without modifying its type, so useful if you can't modify Bar
. (It probably has worse performance though.)
Another option is to consider the mmorph package https://hackage.haskell.org/package/mmorph-1.0.0/docs/Control-Monad-Morph.html#v:hoist
In the tutorial section, look at what hoist
can do.
精彩评论