Home > OS >  How to extract delimited continuation (reset/shift) for future use in Haskell?
How to extract delimited continuation (reset/shift) for future use in Haskell?

Time:07-02

The following is a simple example of using delimited continuation (reset/shift):

import Control.Monad
import Control.Monad.Trans
import Control.Monad.Trans.Cont

test :: Integer
test = evalCont . reset $ do
    r <- shift $ \k -> do
        return $ k 10
    return $ 1   r

λ> test1
11

It works well.

However, I'd like to extract the continuation k as a pure function for future use, instead of just calling it inside shift.

For example, I hope the test2 could return the k:

test2 :: Integer -> Integer
test2 = evalCont . reset $ do
    r <- shift $ \k -> do
        return $ k
    return $ 1   r

but GHC complains:

    ? Couldn't match type 'Integer -> Integer' with 'Integer'
      Expected type: Cont (Integer -> Integer) (Integer -> Integer)
        Actual type: ContT
                       (Integer -> Integer)
                       Data.Functor.Identity.Identity
                       ((Integer -> Integer) -> Integer -> Integer)
    ? In a stmt of a 'do' block: return $ k
      In the expression: do return $ k
      In the second argument of '($)', namely '\ k -> do return $ k'
   |
88 |         return $ k
   |         ^^^^^^^^^^

Anyone could help me to work around this problem?

Thanks.

CodePudding user response:

Inspired by @BenjaminHodgson's comment, here is the temporary solution:

data Ret a = Fun (Integer -> Ret a) | Val a

instance Show a => Show (Ret a) where
    show (Fun f) = "Jst f"
    show (Val a) = show a

test2 :: Ret Integer
test2 = evalCont . reset $ do
    r <- shift $ \k -> do
         return $ Fun k
    return $ Val (1   r)

main :: IO ()
main = do
        print $ case test2 of (Fun f) -> f 100
        print $ case test2 of (Fun f) -> f 50

λ> main
101
51

Disclaimer: I'm not sure the recursive type Ret is necessary.

I will appreciate if someone could provide a better solution or explanation. Thanks.

CodePudding user response:

The standard Cont is incompletely general. "Real" Cont looks like this

newtype Cont    i o a =    Cont { runCont :: (a -> i) -> o }
-- versus the standard
newtype SadCont   r a = SadCont { sadCont :: (a -> r) -> r }
-- SadCont r a = Cont r r a

The standard SadCont is used because it supports >>= and return at their usual types (so it can be a Monad). But "real" delimited continuations inside Cont allow each shift to take values from the continuation at one type and send them up towards the previous shift/reset at a different type. In this case you are just passing the entire continuation as a function from shift to reset.

{-# LANGUAGE RebindableSyntax #-}
-- ^ placing this at the top of a file or passing -XRebindableSyntax to GHC allows do notation to use custom (>>=) and (>>)

-- not Monad operations!
return :: a -> Cont r r a
return x = Cont ($ x)
(>>=) :: Cont m o a -> (a -> Cont i m b) -> Cont i o b
Cont x >>= f = Cont $ \k -> x (($ k) . runCont . f)
(>>) :: Cont m o a -> Cont i m b -> Cont i o b -- RebindableSyntax also wants this
a >> b = a >>= const b

evalCont :: Cont a o a -> o
evalCont (Cont x) = x id

-- shift/reset are actually just
reset = evalCont
shift = Cont
-- note that the types of reset and shift differ significantly from transformers
-- reset returns a pure value here and shift requires a pure value from its function
-- I think my choices are more correct/standard, e.g. they line up with the old Scala shift/reset http://lampwww.epfl.ch/~hmiller/scaladoc/library/scala/util/continuations/package.html

In your example

test2 :: Integer -> Integer
test2 = reset $ do
    r <- shift $ \k -> k
    return $ 1   r

TL;DR Cont is deliberately "broken", so it loses the generality of differing input and output types but gains Monadicity. You can hack around it by putting the input and output types into a (recursive) sum as you've discovered. Alternatively (this answer) you can define and use "real" Cont.

  • Related