在JavaScript中实现定界的延续monad-`reset`幂等错误

问题描述

这是一个艰难的过程。我一直在尝试编写各种monad,这是我在任何地方都找不到的简洁示例的唯一示例,因此我尝试使用this test suite编写自己的shiftreset( JS)和this question(Agda)作为参考。特别是

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)

我遇到的问题是,当我测试通过多个reset中止时,我的实现失败了:

// 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],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]]

我的版本感觉错误-参考中只有一个id,我的有两个。过去,我很沮丧。朝正确方向的任何提示将不胜感激!

解决方法

这是问题所在。

  1. 定界延续的Adga实现不考虑区域。
  2. 定界连续符的JavaScript实现确实考虑了区域。

您正在尝试将定界延续的Adga实现与JavaScript测试套件一起使用,因此您的进展不会很快。

不包含区域的定界连续符

请考虑以下程序。

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)

使用不带区域的定界连续符时,每个shift被定界为最接近的reset。因此,在上述程序中,结果为((5 + 1) * 2) ^ 2,其结果为144

您对shiftreset的实现是基于Agda实现的。因此,它的值为144。还有Haskell implementation个没有区域的定界连续符,更简单。

用区域定界的延续

现在,考虑使用带区域的定界延续的同一程序。

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)

在这里,我们明确指定shift由外部reset分隔。因此,结果为(5 * 2) ^ 2,其结果为100

带区域的定界延续的实现更加复杂。一个很好的起点是阅读我的教授Amr Sabry等人的A Monadic Framework for Delimited Continuations的原始论文。