4.2 延续传递风格解释器

为了简单起见, 本节我们从(按值调用的)lambda演算开始, 以下是句法.

然后是解释器, 现在延续也成为其参数.

实际上, 这就相当于对于原本的直接的解释器进行延续传递风格变换.

这个解释器的闭包和延续均以函数表示, 现在我们考虑将其转换为更加传统的数据结构, 这将使原本模糊的地方变得清晰起来.

从现在起, 我们开始考虑稍微复杂一些的语言, 即2.5节带有定义递归过程结构的语言.

不过, 我们将对于程序的结构作出一些改变. 首先我们将每个参数都变为全局的, 并使用赋值以在过程之间交流信息. 而且, 对于主要过程的调用都将出现在「尾位置」, 这等价于goto, 即控制的直接移交. (到本节末, 读者应该能够理解这句陈述的含义.) 于是, 这个程序的控制结构将非常类似于在一个汇编语言中编写解释器. 还有一个小小的观察, 实际上读者应该注意到延续可以安排成栈的结构, 而且其可以被视为编程语言运行时的堆栈的抽象化. (对于低层次行为的理解不必停留于低层次, 而可以通过更高层次的概念进行演绎.)

鉴于程序的长度, 我们逐块阅读.

这是所有全局变量的定义, 其中CLO即closure, CTX即context (即continuation), 以及栈的实现.

initialize!为求值做准备.

INTERP是解释器的主体. 可以看到, 有的时候我们立即得到了所需要的值, 将其置于VAL之中, 接着我们APPLY_CTX以考虑接下来对于这个值做些什么; 另外一些时候, 我们需要考虑先对于某个子表达式求值, 并且给CTX添加之后要做什么的信息.

APPLY_CTX考虑对于值进行怎样的加工. 和INTERP的情况类似, 有时我们得到了想要的值, 于是需要进一步加工; 另外的时候, 我们需要考虑对于什么表达式进行求值, 并给CTX添加控制信息.

APPLY_CLO不过是应用闭包CLO于值VAL.

剩下的是一些简单的关于延续的数据结构的定义.

现在让我们看一个对比的例子.

从这个例子可以看到递归过程fact和<的不同性质, fact呈现了递归计算行为, 因为最大的栈深度随着输入的增长而线性地增长. 反观<, 最大的栈深度一直是3.

若我们追踪对于表达式(fact (- n 1))求值时的延续.

真正重要的原则是, 对于一个表达式的尾位置的表达式求值的延续, 应该与对于该表达式求值的延续是相等的, 这是因为尾位置的表达式的值, 就是整个表达式的值. 这也就是为什么说, 尾位置的过程调用就像goto一样, 因为无需添加任何需要做些什么的控制信息. 正是在于(fact (- n 1))没有出现在尾位置, 因而它必须记住还要做些什么.

你的回應