Search code examples
javascriptfunctional-programmingmonadscontinuationsdelimited-continuations

Implementing the delimited continuation monad in JavaScript - `reset` idempotence bug


This is a tough one. I've been trying to code up various monads and this was the only one I couldn't find a succinct example anywhere, so I tried writing my own shift and reset using this test suite (JS) and this question (Agda) as reference. In particular,

shift        : ∀ {r o i j a} → ((a → DCont i i o) → DCont r j j) → DCont r o a
shift f      = λ k → f (λ x → λ k′ → k′ (k x)) id

reset        : ∀ {r i a} → DCont a i i → DCont r r a
reset a      = λ k → k (a id)

The problem I have is that my implementation fails when I test for aborting through multiple resets:

// Delimited continuation monad
class DCont {
  static of (x) { return new DCont(resolve => resolve(x)) }
  constructor (run) { this.run = run }
  chain (fn) { return new DCont(resolve => this.run(x => fn(x).run(resolve))) }
  map (fn) { return this.chain(x => DCont.of(fn(x))) }
  ap (dc) { return this.chain(fn => dc.map(fn)) }
  shift (subc) { return new DCont(resolve => subc(dc => dc.map(resolve)).run(x => x)) }
  static reset (comp) { return DCont.of(comp(DCont.of(x => x)).run(x => x)) }
}

// Setup tests
let sqr = x => x * x,
    single_shift_reset = DCont
      .reset(p => p
        .shift(k => k(k(DCont.of(5))))
        .map(x => x + 1))
      .map(x => x * 2),
    multi_shift_abort = DCont
      .reset(p => DCont
        .reset(p2 => p
          .shift(k => DCont.of(5))
          .map(x => 1000))
        .map(x => x + 1))
      .map(x => x * 2),
    liftM2 = (f, m1, m2) => m1.chain(x => m2.map(y => f(x, y))),
    listOf = (m1, m2) => liftM2((x, y) => [x, y], m1, m2),
    add = (x, y) => x + y,
    multi_shift_in_reset = DCont
      .reset(p => liftM2(add,
        p.shift(k => listOf( k(DCont.of(1)), k(DCont.of(2)) )),
        p.shift(k => listOf( k(DCont.of(10)), k(DCont.of(20)) ))
      ));

// Run tests
console.log(single_shift_reset.run(sqr)) // Expects 196 = ((5 + 1 + 1) * 2) ^ 2
console.log(multi_shift_abort.run(sqr)) // Expects 100 = (5 * 2) ^ 2
console.log(multi_shift_in_reset.run(x => x)) // Expects [[11, 21], [12, 22]]

My version feels wrong - there is only one id in the reference, mine has two. Past that I'm stumped though. Any hints in the right direction would be appreciated!


Solution

  • Here's the problem.

    1. The Adga implementation of delimited continuations doesn't take regions into account.
    2. The JavaScript implementation of delimited continuations does take regions into account.

    You're trying to use the Adga implementation of delimited continuations along with the JavaScript test suite, and hence you're not getting very far.

    Delimited Continuations without Regions

    Consider the following program.

    example = do
        x <- reset $ do
            x <- reset $ do
                shift (\k -> return 5)
                return 1000
            return (x + 1)
        return (x * 2)
    
    result = run example (\x -> x ^ 2)
    

    When using delimited continuations without regions, each shift is delimited to the closest reset. Hence, in the above program the result is ((5 + 1) * 2) ^ 2 which evaluates to 144.

    Your implementation of shift and reset is based on the Agda implementation. Hence, it evaluates to 144. There's also a Haskell implementation of delimited continuations without regions which is simpler.

    Delimited Continuations with Regions

    Now, consider the same program using delimited continuations with regions.

    example = do
        x <- reset $ \p -> do
            x <- reset $ \p' -> do
                shift p (\k -> return 5)
                return 1000
            return (x + 1)
        return (x * 2)
    
    result = run example (\x -> x ^ 2)
    

    Here, we explicitly specify that the shift is delimited by the outer reset. Hence, the result is (5 * 2) ^ 2 which evaluates to 100.

    The implementation of delimited continuations with regions is more complex. A good place to start would be to read the original paper by my professor, Amr Sabry, et al., A Monadic Framework for Delimited Continuations.