从概念上讲,什么是提示?
Scheme 通常有延续的想法,但 Racket 用分隔延续的想法扩展了这一点。延续的想法是它捕获剩余的计算。我不会试图解释一般的延续,因为这超出了这个问题的范围。
但是,我将解释是什么让定界延续变得特别。通常,捕获延续会捕获整个计算,一直到顶层。这使得它们在实现复杂控制结构方面的使用相对有限,因为应用延续将完全释放对程序执行的控制。
使用定界延续,您只能捕获延续的特定部分。实际捕获的评估部分由prompts分隔,它们的作用类似于沿着当前延续的标记,指定要捕获多少延续。
好的,但这意味着什么?
与未定界延续相比,如果没有实际看到它的实际作用,定界延续的概念并不清楚。
标准(非定界)延续
考虑以下示例代码。
(define *k* #f)
(sqrt
(+ 1 2 3
(call/cc
(λ (k)
(set! *k* k)
0))))
这段代码非常简单——它捕获一个延续并存储到全局绑定*k*
中。延续本身看起来像这样:
(sqrt (+ 1 2 3 _))
(其中_
代表调用延续时要填充的“洞”。)
应用这种延续将完全按照人们的预期工作。
> (*k* 3) ; evaluates (sqrt (+ 1 2 3 3))
3
这一切都很普通。那么定界延续引入的区别是什么?
定界延续
如果我们只想在*k*
. 例如,如果我们只想捕捉这种延续怎么办?
(+ 1 2 3 _) ; the inner portion of the last continuation
我们可以通过建立一个continuation prompt来做到这一点,它将调整实际捕获的 continuation 的多少。
(sqrt
(call-with-continuation-prompt
(λ ()
(+ 1 2 3
(call/cc
(λ (k)
(set! *k* k)
0))))))
现在,应用*k*
给出内部结果:
> (*k* 3)
9
定界延续的类比
延续可能是一个有点抽象的概念,所以如果上面的代码示例不是很清楚,请考虑这个类比。
评估模型是一个堆栈——每个函数调用都会将一个新帧压入堆栈,从一个函数返回时会将该帧从堆栈中弹出。我们可以将调用堆栈可视化为一堆卡片。
通常,当一个延续被捕获时,它会捕获当前帧和它下面的所有帧,如下图所示。
以蓝色表示的顶层未被捕获。它实际上是分隔系统中的默认提示。
但是,安装新提示会在帧之间创建一种透明分隔线,这会影响哪些帧被捕获为延续的一部分。
这个分隔线划定了延续的范围。
附录:提示标签和延续障碍
这是定界延续的基础知识,但还有其他方法可以控制延续,从而为延续系统提供更多权力(以及保护其免受恶意代码的侵害),这些是提示标记和延续障碍。
提示标签的概念本质上是一个标记给定提示的“标签”。使用上面的卡片类比,每个透明分隔器都可以被赋予一个标签。然后,当您捕获一个延续时,您可以指定一直捕获到该特定标签,即使中间有其他带有其他标签的提示。
另一方面,持续障碍是一种安全措施。就像提示一样,它们可以可视化为位于调用堆栈元素之间的“分隔符”,但它们不是用作控制堆栈被捕获多少的标记,而是用作防止继续“跳过”障碍。
有关这方面的更多详细信息,请考虑阅读Racket 参考中关于延续障碍的部分。这是一段摘录:
具体来说,只有在替换不引入任何延续障碍时,才能将延续替换为另一个延续。它只能通过跳转到作为当前延续尾部的延续来消除延续障碍。因此,延续屏障可防止“向下跳跃”进入受屏障保护的延续。