Raku(do) 所依赖的延续的细节是什么?

What are the specifics about the continuations upon which Raku(do) relies?

在 1990 年代和 2000 年代,编程语言爱好者几乎没有讨论定界延续的话题。它最近重新成为编程语言讨论中的主要内容。

我希望有人至少可以权威地说 Rakudo(与 Raku 相比)的延续是否具有下面列出的六个特征中的每一个。我会在列表后多说一些我希望得到的答案。

从在线消息中逐字引用(经过格式修改)[1] 由负责添加工作的人撰写JVM 的延续:


Aiui 延续没有直接暴露在 Raku 中,因此与 Raku 相关的正确答案(相对于 Rakudo)可能是“没有延续”。但这对我来说并不清楚,所以在下面,如果我很幸运的话,我会在其中描述我希望的答案,我会假装在 Raku 和 Raku 的背景下谈论它们是有意义的和乐道作为两个截然不同的领域。

这是我想象的可能的答案(虽然我只是有点疯狂地猜测什么是真实的):

(或类似的东西。)

如果答案还提供了关于上述内容的更多详细信息,也许是一些代码链接,那将是一个特别棒的补充。

同样,如果一个答案包括几个简短的例子,说明这种持续的力量如何在当前的 Raku 功能中浮出水面,并推测它可能有一天,比如 10 年后,在其他功能中浮出水面,那将使一个过分精彩的答案。

PS。感谢@Larry,他对事物的理解足够深刻,知道延续需要成为图片的一部分;感谢 Stefan O'Rear 的贡献,包括我认为是一次性多提示分隔延续的初步实现;感谢 jnthn 让梦想成真。

脚注

1 目前正在进行将延续作为第一个 class 构造引入 JVM 的工作。这项工作的主要推动者是 Ron Pressler。以上是根据a message he wrote in November.

Rakudo 使用延续作为两个功能的实现策略:

  • gather/take - 用于实现惰性迭代器
  • 使线程池上的await成为非阻塞

实现的continuations的特性遵循这些语言特性的要求。我将按照与上面略有不同的顺序来介绍它们,因为它便于解释。

  • Stackful - 是的,因为我们需要能够在调用堆栈中相对于 [=] 的任何深度执行 takeawait 10=] 或线程池工作者的工作循环。例如,您可以在 gather 内编写递归图遍历算法,然后在每个遇到的节点内编写 take 。对于 await,这是 Raku 的 awaitawait 之间区别的核心,正如在许多其他语言中看到的那样:您不必一直重构调用堆栈。
  • 定界 - 是的。继续重置操作安装一个标记(或“提示”),当我们进行继续控制操作时,我们在这个分隔符处对堆栈进行切片。我无法想象你如何在没有分隔的情况下实现所涉及的 Raku 功能。
  • 多提示 - 是的,这是必需的,因为您可以在另一个 gather 中迭代由 gather 提供的一个数据源实施,或在 gather.
  • 中执行 await
  • Asymmetric - 在继续执行之后,在 reset 指令之后继续执行。在 await 的情况下,我们去 worker 任务队列中寻找另一个任务,在 take 的情况下,我们回到迭代器的 pull-one 方法并且可以 return 取值。我认为这种方法非常适合只有少数功能使用延续的语言。
  • One-shot/non-reentrant - 是的,至少在 MoarVM 中,运行时的内存安全取决于此 属性。它由原子比较和交换操作强制执行,因此如果两个线程竞相调用延续,则只有一个可以成功。没有任何 Raku 功能需要可重入延续所暗示的额外复杂性。
  • Cloneable - 不,因为没有 Raku 功能需要它。从理论上讲,就说“是的,我们可以做到”而言,这在 MoarVM 中实现并不太糟糕,但我怀疑它会引发很多问题,例如“应该克隆多深”。如果您只是克隆了所有调用记录和类似记录,您仍然会在克隆之间共享 Scalar 容器、Arrays 等。

据我了解——尽管我是从远处观察的——JVM 延续至少部分地针对与 Raku await 机制相同的设计 space,所以我'如果他们最终没有提供 Raku 需要的东西,我会感到惊讶。这显然会简化 Raku 代码到 JVM 的编译(目前它像生成代码一样进行全局 CPS 转换,奇怪的是结果比我预期的要简单),而且它几乎肯定也会表现得更好,因为转换需要从 JIT 编译器的角度来看,可能会掩盖很多事情。

就代码而言,您可以看到 the current continuations implementation, which uses the continuation data structure which in turn has various bits of memory management。在撰写本文时,这些都已作为正在进行的调度程序工作所需的新调用堆栈表示的一部分进行了重大重构;这些更改确实使继续工作更有效率,但不会更改整个操作集。