haskellcontinuationscontinuation-passingdelimited-continuations

How to express delimited continuations with pure functions only?


I worked through Oleg's tutorial of delimited continuations:

newtype Cont r a = Cont{runCont :: (a -> r) -> r}

instance Monad (Cont r) where
  return x = Cont (\k -> k x)
  Cont m >>= f = Cont (\k -> m (\v -> runCont (f v) k))

runC :: Cont r r -> r
runC m = runCont m id

reset :: Cont a a -> Cont r a
reset = return . runC

shift :: ((a -> r) -> Cont r r) -> Cont r a
shift f = Cont (runC . f)

liftM2 (-)
  (reset
    (liftM2 (+) (return 3)
      (shift (\k -> return (5*2))))) -- drop the continuation
        (return 1) -- 9

Since continuations are basically functions and reset/shift are not even part of the monad api, I wonder how to implement delimited continuations without the newtype and monad machinery.

Here is what I've come up with so far:

reset :: Cont a a -> Cont r a -- becomes
reset :: ((a -> a) -> a) -> (a -> r) -> r
reset k f = f $ k id

shift :: ((a -> r) -> Cont r r) -> Cont r a -- becomes
shift :: ((a -> r) -> (r -> r) -> r) -> (a -> r) -> r
shift f k = f k id

I am pretty sure this is plain wrong and if it's not I don't know how to apply the operators correctly:

(1-) (reset ((3+) (shift (\k -> 5*2)))) -- yields

• Non type-variable argument in the constraint: Num ((a -> a) -> a)
  (Use FlexibleContexts to permit this)
• When checking the inferred type
    t1 :: forall a r.
          (Num ((a -> a) -> a), Num ((a -> r) -> r)) =>
          (a -> r) -> r

Solution

  • Keep going!

    import Prelude hiding (return)
    
    -- reset :: Cont a a -> Cont r a
    reset :: ((a -> a) -> a) -> (a -> r) -> r
    reset k f = f $ k id
    
    -- shift :: ((a -> r) -> Cont r r) -> Cont r a
    shift :: ((a -> r) -> (r -> r) -> r) -> (a -> r) -> r
    shift f k = f k id
    
    -- return :: a -> Cont r a
    return :: a -> (a -> r) -> r
    return a k = k a
    
    -- liftM2 :: (a -> b -> c) -> Cont r a -> Cont r b -> Cont r c
    liftM2 :: (a -> b -> c) -> ((a -> r) -> r) -> ((b -> r) -> r) -> (c -> r) -> r
    liftM2 f ma mb k = ma $ \a -> mb $ \b -> k (f a b)
    
    example :: Num a => (a -> r) -> r
    example = liftM2 (-) (reset (liftM2 (+) (return 3) (shift (\k -> return (5*2))))) (return 1)
    

    One issue with (1-) (reset ((3+) (shift (\k -> 5*2)))) is that you're replacing Cont's return with id, when it's actually flip id:

    λ :t shift (\k -> 5*2)
    shift (\k -> 5*2) :: Num ((r -> r) -> r) => (a -> r) -> r
    λ :t shift (\k -> ($ 5*2))
    shift (\k -> ($ 5*2)) :: Num r => (a -> r) -> r
    

    Usually, when ghci says "we need to be able to treat functions as numbers for this code to work", that means you've made a mistake somewhere :)