With the implementation of the explicit-control evaluator we come to the end of
a development, begun in Chapter 1, in which we have explored successively
more precise models of the evaluation process. We started with the relatively
informal substitution model, then extended this in Chapter 3 to the
environment model, which enabled us to deal with state and change. In the
metacircular evaluator of Chapter 4, we used Scheme itself as a language
for making more explicit the environment structure constructed during
evaluation of an expression. Now, with register machines, we have taken a
close look at the evaluator’s mechanisms for storage management, argument
passing, and control. At each new level of description, we have had to raise
issues and resolve ambiguities that were not apparent at the previous, less
precise treatment of evaluation. To understand the behavior of the
explicit-control evaluator, we can simulate it and monitor its performance.
随着显式控制求值器的实现,我们走到了一段发展历程的终点——这段历程始于第一章,我们在其中逐步探索了愈加精确的求值计算过程模型。我们从相对非正式的代换模型出发,在第三章将其扩展为环境模型,从而能够处理状态与变化。在第四章的元循环求值器中,我们以 Scheme 自身作为语言,将表达式求值过程中所构建的环境结构表达得更为明确。而现在,借助寄存器机器,我们深入审视了求值器在存储管理、参数传递和控制方面的机制。在每一个新的描述层次上,我们都必须提出并解决在前一个较不精确的求值处理层次上尚未显现的问题与歧义。为了理解显式控制求值器的行为,我们可以对其进行模拟并监控其性能。
We will install a driver loop in our evaluator machine. This plays the role of
the driver-loop procedure of 4.1.4. The evaluator will
repeatedly print a prompt, read an expression, evaluate the expression by going
to eval-dispatch, and print the result. The following instructions form
the beginning of the explicit-control evaluator’s controller
sequence:
我们将在求值器机器中安装一个驱动循环。它扮演着 4.1.4 节中 driver-loop 过程的角色。求值器将反复打印提示符、读入一个表达式、通过转到 eval-dispatch 来对该表达式求值,然后打印结果。下面这些指令构成了显式控制求值器控制器序列的开头:
When we encounter an error in a procedure (such as the “unknown procedure type
error” indicated at apply-dispatch), we print an error message and
return to the driver loop.
当我们在某个过程中遇到错误时(例如在 apply-dispatch 处所指示的"未知过程类型错误"),我们打印一条错误消息并返回驱动循环。
For the purposes of the simulation, we initialize the stack each time through
the driver loop, since it might not be empty after an error (such as an
undefined variable) interrupts an evaluation.
为了模拟的需要,我们在每次经过驱动循环时都初始化栈,因为当某次求值被错误(例如未定义的变量)中断后,栈可能并非空的。
If we combine all the code fragments presented in
5.4.1–5.4.4, we can create an evaluator machine model that we can
run using the register-machine simulator of 5.2.
如果我们将 5.4.1 至 5.4.4 节中给出的所有代码片段组合起来,就能创建一个求值器机器模型,并用 5.2 节的寄存器机器模拟器来运行它。
We must define Scheme procedures to simulate the operations used as primitives
by the evaluator. These are the same procedures we used for the metacircular
evaluator in 4.1, together with the few additional ones defined
in footnotes throughout 5.4.
我们必须定义 Scheme 过程来模拟求值器所用的基本操作。这些过程与我们在 4.1 节的元循环求值器中所用的过程相同,再加上 5.4 节各处脚注中定义的少量附加过程。
Finally, we can initialize the global environment and run the evaluator:
最后,我们可以初始化全局环境并运行求值器:
Of course, evaluating expressions in this way will take much longer than if we
had directly typed them into Scheme, because of the multiple levels of
simulation involved. Our expressions are evaluated by the
explicit-control-evaluator machine, which is being simulated by a Scheme
program, which is itself being evaluated by the Scheme interpreter.
Subheading: Monitoring the performance of the evaluator
当然,以这种方式对表达式求值所花费的时间,要比直接在 Scheme 中键入这些表达式长得多,因为其中涉及多个层次的模拟。我们的表达式由显式控制求值器机器来求值,而该机器是由一个 Scheme 程序模拟的,这个 Scheme 程序本身又由 Scheme 解释器来求值。
副标题:监控求值器的性能
Simulation can be a powerful tool to guide the implementation of evaluators.
Simulations make it easy not only to explore variations of the register-machine
design but also to monitor the performance of the simulated evaluator. For
example, one important factor in performance is how efficiently the evaluator
uses the stack. We can observe the number of stack operations required to
evaluate various expressions by defining the evaluator register machine with
the version of the simulator that collects statistics on stack use
(5.2.4), and adding an instruction at the evaluator’s print-result
entry point to print the statistics:
模拟可以成为指导求值器实现的强大工具。模拟不仅使我们能够方便地探索寄存器机器设计的各种变体,还能监控被模拟求值器的性能。例如,性能的一个重要因素是求值器对栈的使用效率。我们可以通过以下方式来观察求值各种表达式所需的栈操作次数:用收集栈使用统计信息的模拟器版本(5.2.4 节)来定义求值器寄存器机器,并在求值器的 print-result 入口点处添加一条打印统计信息的指令:
Interactions with the evaluator now look like this:
与求值器的交互现在看起来如下所示:
Note that the driver loop of the evaluator reinitializes the stack at the start
of each interaction, so that the statistics printed will refer only to stack
operations used to evaluate the previous expression.
注意,求值器的驱动循环在每次交互开始时都会重新初始化栈,因此所打印的统计信息将只涉及用于对前一个表达式求值的栈操作。
read-eval-print-loop
(perform (op initialize-stack))
(perform (op prompt-for-input)
(const ";;; EC-Eval input:"))
(assign exp (op read))
(assign env (op get-global-environment))
(assign continue (label print-result))
(goto (label eval-dispatch))
print-result
(perform (op announce-output)
(const ";;; EC-Eval value:"))
(perform (op user-print) (reg val))
(goto (label read-eval-print-loop)) unknown-expression-type
(assign
val
(const unknown-expression-type-error))
(goto (label signal-error))
unknown-procedure-type
; clean up stack (from apply-dispatch):
(restore continue)
(assign
val
(const unknown-procedure-type-error))
(goto (label signal-error))
signal-error
(perform (op user-print) (reg val))
(goto (label read-eval-print-loop)) (define eceval
(make-machine
'(exp env val proc argl continue unev)
eceval-operations
'(read-eval-print-loop
⟨entire machine controller
as given above⟩))) (define eceval-operations
(list (list 'self-evaluating?
self-evaluating)
⟨complete list of operations
for eceval machine⟩)) (define the-global-environment
(setup-environment))
(start eceval)
;;; EC-Eval input:
(define (append x y)
(if (null? x)
y
(cons (car x) (append (cdr x) y))))
;;; EC-Eval value:
ok
;;; EC-Eval input:
(append '(a b c) '(d e f))
;;; EC-Eval value:
(a b c d e f) print-result
; added instruction:
(perform (op print-stack-statistics))
(perform (op announce-output)
(const ";;; EC-Eval value:"))
… ; same as before ;;; EC-Eval input:
(define (factorial n)
(if (= n 1) 1 (* (factorial (- n 1)) n)))
(total-pushes = 3, maximum-depth = 3)
;;; EC-Eval value:
ok
;;; EC-Eval input:
(factorial 5)
(total-pushes = 144, maximum-depth = 28)
;;; EC-Eval value:
120