开发者

Escaping from the IO monad inside the Continuation monad

开发者 https://www.devze.com 2023-03-18 04:39 出处:网络
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

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.

0

精彩评论

暂无评论...
验证码 换一张
取 消

关注公众号