5.4.2 序列化解释与尾递归 显式控制的解释器在ev-sequence的部分与元循环的解释器的eval-sequence 程序是很相似的。它处理在程序体中的表达式的序列或者是显式的begin表达式。
通过把要解释的表达式的序列放入unev,在栈上保存continue,跳转到ev-sequence, 就解释了显式的begin表达式。
ev-begin (assign unev (op begin-actions) (reg exp)) (save continue) (goto (label ev-sequence))
通过从compound-apply跳转到ev-sequence,已经被放在栈上的continue寄存器,被保存 在ev-application中,在程序体中的隐式的序列就被处理完成了。
在ev-sequence和ev-sequence-continue中的入口形成了循环,连续地解释序列中的每一个 表达式。未被解释的表达式的列表被保存在unev。 在解释每个表达式之前,我们检查看看是否 在序列中有其它的要被解释的表达式。如果有,我们保存未解释的表达式的其它部分(在unev中)和它们的环境(在env中)和调用eval-dispatch来解释表达式。在ev-sequence-continue中,从这个解释返回时,这两个保存的寄存器被恢复。
在序列中的最后的一个表达式被处理得有点不同,在入口点ev-sequence-last-exp. 因为在这个以后,没有更多的表达式被解释了,在去eval-dispatch之前,我们 不需要保存unev和env. 整个序列的值是最后一个表达式的值,所以最后一个表达式被 解释之后没有什么留下来要做,除了继续去在栈上保存的入口点(它在ev-application或者是ev-begin中被保存的)。不是设置continue为eval-dispatch能够返回这里,也不是从栈上恢复,再继续 这个入口点,我们在去eval-dispatch之前,从栈上恢复continue,为了eval-dispatch在解释 了表达式后,能继续。
ev-sequence (assign exp (op first-exp) (reg unev)) (test (op last-exp?) (reg unev)) (branch (label ev-sequence-last-exp)) (save unev) (save env) (assign continue (label ev-sequence-continue)) (goto (label eval-dispatch)) ev-sequence-continue (restore env) (restore unev) (assign unev (op rest-exps) (reg unev)) (goto (label ev-sequence)) ev-sequence-last-exp (restore continue) (goto (label eval-dispatch))
* 尾递归 在第一章中,我们说过如下的程序描述的过程:
(define (sqrt-iter guess x) (if (good-enough? guess x) guess (sqrt-iter (improve guess x) x)))
是一个迭代的过程。即使,程序是语法性的递归(用它自身定义的)对于解释器, 逻辑上不需要保存信息,从sqrt-iter的一个调用到另一个。一个解释器可能执行 一个程序例如sqrt-iter而没有增加存储,而程序在调用自身,被叫做一个尾递归的 解释器。在第四章中的解释器的元循环的实现没有指定解释器是否是尾递归的,因为 解释器继承了从底层的scheme的保存状态的机制。在显式控制的解释器中, 然而,我们能跟踪解释的过程,来看看,当程序调用引起的在栈上的信息的净累加 情况。
我们的解释器是尾递归的,因为为了解释一个序列的最后一个表达式,我们直接传递 给eval-dispatch而没有在栈上保存任何的信息。因此,解释在序列中的最后一个表达式, 即使它是一个程序调用(如sqrt-iter,当条件表达式,它是程序体中的最后一个表达式,归结 为一个对sqrt-iter的调用) 将没有引起栈上的任何信息的累加。
如果我们不利用这个事实(在这个例子中保存信息是不必要的),我们可能 实现eval-sequence,通过对待一个序列中的所有的表达式以一种相同的方式, 保存寄存器,解释表达式,返回到恢复的寄存器,重复这个过程直到所有的 表达式都被解释了。
ev-sequence (test (op no-more-exps?) (reg unev)) (branch (label ev-sequence-end)) (assign exp (op first-exp) (reg unev)) (save unev) (save env) (assign continue (label ev-sequence-continue)) (goto (label eval-dispatch)) ev-sequence-continue (restore env) (restore unev) (assign unev (op rest-exps) (reg unev)) (goto (label ev-sequence)) ev-sequence-end (restore continue) (goto (reg continue))
这可能似乎是对一个序列的解释的之前版本的代码,有一点修改。仅有的不同是 在一个序列的最后一个表达式时像其它的表达式一样,进行了保存与恢复的过程。 解释器将仍然能够为任何的表达式得到相同的值。但是这个修改对于尾递归的实现 却是致命的,因为我们现在在解释了序列中的最后一个表达式后,必须返回,为了恢复 无用的寄存器的保存值。在一个程序调用的嵌套之中,这些额外的保存将累加。 所以,过程例如sqrt-iter将要求空间是与迭代的次数成比例的,而不是一个常数的 空间大小。这种不同能够成为重要的。例如,在尾递归中,一个无限的循环能被表达为 仅使用程序调用的机制:
(define (count n) (newline) (display n) (count (+ n 1)))
没有了尾递归,这样的程序最终将耗尽栈上的空间,表达一个真正的迭代将需要一些控制 机制而不是程序调用。