1

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!

den-chan
  • 95
  • 9
  • Idempotence issues often occur in a strict environment. Agda seems to be evaluated in normal order. I guess the problem lies in the strictness of `reset`. Unfortunately I haven't the time to delve into the details.. –  Oct 01 '20 at 12:04

1 Answers1

2

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.

Aadit M Shah
  • 72,912
  • 30
  • 168
  • 299
  • 1
    Ahh, now everything clicks! The reference JS I linked to uses the same `newPrompt`, `pushPrompt`, `withSubCont` and `pushSubCont` primitives as the paper you linked (which I had already come across), but I didn't realise the reason for the difference in complexity. In that case, my own `shift` method ought meaningfully to be static. I could have realised something was wrong when I wasn't able to find a way to cleanly incorporate a `this.run` into the function! – den-chan Oct 03 '20 at 14:32
  • Also, the Haskell implementation of `shift` requires a transformer being run on the `Identity` monad, so for the given scope I like the Agda version better. – den-chan Oct 03 '20 at 14:48
  • @Aadit Still, shouldn't an adaption of `reset a = λ k → k (a id)` for a strictly evaluated environment use the constructor instead of `of` (aka `return`), because the latter is strict? Something like `const reset = cont => Cont(k => k(cont.run(id)))`. –  Oct 03 '20 at 15:26
  • @scriptum Yes, if you want to defer the computation until the continuation is actually provided. – Aadit M Shah Oct 03 '20 at 15:45