The evaluator implemented above is simple, but it is very inefficient, because
the syntactic analysis of expressions is interleaved with their execution.
Thus if a program is executed many times, its syntax is analyzed many times.
Consider, for example, evaluating (factorial 4) using the following
definition of factorial:
上面实现的求值器很简单,但效率非常低,因为对表达式的语法分析与其执行是交织在一起的。这样,如果一个程序被执行多次,其语法就会被分析多次。例如,考虑使用以下 factorial 的定义来求值 (factorial 4):
Each time factorial is called, the evaluator must determine that the
body is an if expression and extract the predicate. Only then can it
evaluate the predicate and dispatch on its value. Each time it evaluates the
expression (* (factorial (- n 1)) n), or the subexpressions
(factorial (- n 1)) and (- n 1), the evaluator must perform the
case analysis in eval to determine that the expression is an
application, and must extract its operator and operands. This analysis is
expensive. Performing it repeatedly is wasteful.
每次调用 factorial 时,求值器都必须判断其体是一个 if 表达式,并提取谓词。只有这样才能对谓词求值并根据其值进行分派。每次求值表达式 (* (factorial (- n 1)) n),或子表达式 (factorial (- n 1)) 和 (- n 1) 时,求值器都必须在 eval 中执行情况分析,以确定该表达式是一个应用,并提取其运算符和运算对象。这种分析代价不菲,反复执行则是一种浪费。
We can transform the evaluator to be significantly more efficient by arranging
things so that syntactic analysis is performed only once. We split eval, which takes an expression and an
environment, into two parts. The procedure analyze takes only the
expression. It performs the syntactic analysis and returns a new procedure,
the
execution procedure, that encapsulates the work to be done in
executing the analyzed expression. The execution procedure takes an
environment as its argument and completes the evaluation. This saves work
because analyze will be called only once on an expression, while the
execution procedure may be called many times.
With the separation into analysis and execution, eval now becomes
通过安排使语法分析只执行一次,我们可以将求值器变换得显著更高效。我们将接受一个表达式和一个环境的 eval 拆分为两部分。过程 analyze 只接受表达式,它执行语法分析并返回一个新的过程——执行过程 (execution procedure),该过程封装了执行已分析表达式时需要完成的工作。执行过程以一个环境为参数,并完成求值。这节省了工作量,因为对一个表达式 analyze 只调用一次,而执行过程可能被调用多次。将分析与执行分离后,eval 变为
The result of calling analyze is the execution procedure to be applied
to the environment. The analyze procedure is the same case analysis as
performed by the original eval of 4.1.1, except that the
procedures to which we dispatch perform only analysis, not full evaluation:
调用 analyze 的结果是要被应用于环境的执行过程。analyze 过程执行的情况分析与 4.1.1 中原来的 eval 所执行的相同,只是我们所分派到的那些过程只执行分析,而不执行完整的求值:
Here is the simplest syntactic analysis procedure, which handles
self-evaluating expressions. It returns an execution procedure that ignores
its environment argument and just returns the expression:
下面是最简单的语法分析过程,它处理自求值表达式。它返回一个执行过程,该执行过程忽略其环境参数,直接返回该表达式:
For a quoted expression, we can gain a little efficiency by extracting the text
of the quotation only once, in the analysis phase, rather than in the execution
phase.
对于引用表达式,我们可以通过在分析阶段而不是执行阶段提取引用的文本来获得一点效率提升——只提取一次即可。
Looking up a variable value must still be done in the execution phase, since
this depends upon knowing the environment.
查找变量的值仍然必须在执行阶段完成,因为这取决于对环境的了解。
Analyze-assignment also must defer actually setting the variable until
the execution, when the environment has been supplied. However, the fact that
the assignment-value expression can be analyzed (recursively) during
analysis is a major gain in efficiency, because the assignment-value
expression will now be analyzed only once. The same holds true for
definitions.
Analyze-assignment 也必须将实际设置变量的操作推迟到执行阶段,届时环境已经提供。然而,赋值的值表达式能够在分析阶段被(递归地)分析,这是一个重要的效率提升,因为赋值的值表达式现在只需被分析一次。对于定义,情况也是如此。
For if expressions, we extract and analyze the predicate, consequent,
and alternative at analysis time.
对于 if 表达式,我们在分析阶段提取并分析谓词、推论和替代部分。
Analyzing a lambda expression also achieves a major gain in efficiency:
We analyze the lambda body only once, even though procedures resulting
from evaluation of the lambda may be applied many times.
分析一个 lambda 表达式同样能带来重要的效率提升:我们只分析一次 lambda 体,即使由求值 lambda 所产生的过程可能被应用多次。
Analysis of a sequence of expressions (as in a begin or the body of a
lambda expression) is more involved. Each expression in the
sequence is analyzed, yielding an execution procedure. These execution
procedures are combined to produce an execution procedure that takes an
environment as argument and sequentially calls each individual execution
procedure with the environment as argument.
分析一个表达式序列(如 begin 中的表达式或 lambda 表达式的体)涉及更多工作。序列中的每个表达式分别被分析,各自产生一个执行过程。这些执行过程被组合起来,产生一个以环境为参数的执行过程,它依次以该环境为参数调用每个单独的执行过程。
To analyze an application, we analyze the operator and operands and construct
an execution procedure that calls the operator execution procedure (to obtain
the actual procedure to be applied) and the operand execution procedures (to
obtain the actual arguments). We then pass these to
execute-application, which is the analog of apply in
4.1.1. Execute-application differs from apply in that the
procedure body for a compound procedure has already been analyzed, so there is
no need to do further analysis. Instead, we just call the execution procedure
for the body on the extended environment.
为了分析一个应用,我们分析运算符和运算对象,并构造一个执行过程,它调用运算符的执行过程(以获得实际要被应用的过程)和各运算对象的执行过程(以获得实际的参数)。然后我们将这些传递给 execute-application,它是 4.1.1 中 apply 的对应物。execute-application 与 apply 的区别在于,复合过程的过程体已经被分析过了,因此无需再做进一步的分析。我们只需在扩展后的环境上调用体的执行过程即可。
Our new evaluator uses the same data structures, syntax procedures, and
run-time support procedures as in 4.1.2, 4.1.3, and
4.1.4.
我们的新求值器使用与 4.1.2、4.1.3 和 4.1.4 中相同的数据结构、语法过程和运行时支持过程。
(define (factorial n)
(if (= n 1)
1
(* (factorial (- n 1)) n))) (define (eval exp env) ((analyze exp) env)) (define (analyze exp)
(cond ((self-evaluating? exp)
(analyze-self-evaluating exp))
((quoted? exp)
(analyze-quoted exp))
((variable? exp)
(analyze-variable exp))
((assignment? exp)
(analyze-assignment exp))
((definition? exp)
(analyze-definition exp))
((if? exp)
(analyze-if exp))
((lambda? exp)
(analyze-lambda exp))
((begin? exp)
(analyze-sequence
(begin-actions exp)))
((cond? exp)
(analyze (cond->if exp)))
((application? exp)
(analyze-application exp))
(else
(error "Unknown expression
type: ANALYZE"
exp)))) (define (analyze-self-evaluating exp)
(lambda (env) exp)) (define (analyze-quoted exp)
(let ((qval (text-of-quotation exp)))
(lambda (env) qval))) (define (analyze-variable exp)
(lambda (env)
(lookup-variable-value exp env))) (define (analyze-assignment exp)
(let ((var (assignment-variable exp))
(vproc (analyze
(assignment-value exp))))
(lambda (env)
(set-variable-value!
var (vproc env) env)
'ok)))
(define (analyze-definition exp)
(let ((var (definition-variable exp))
(vproc (analyze
(definition-value exp))))
(lambda (env)
(define-variable! var (vproc env) env)
'ok))) (define (analyze-if exp)
(let ((pproc (analyze (if-predicate exp)))
(cproc (analyze (if-consequent exp)))
(aproc (analyze (if-alternative exp))))
(lambda (env)
(if (true? (pproc env))
(cproc env)
(aproc env))))) (define (analyze-lambda exp)
(let ((vars (lambda-parameters exp))
(bproc (analyze-sequence
(lambda-body exp))))
(lambda (env)
(make-procedure vars bproc env)))) (define (analyze-sequence exps)
(define (sequentially proc1 proc2)
(lambda (env) (proc1 env) (proc2 env)))
(define (loop first-proc rest-procs)
(if (null? rest-procs)
first-proc
(loop (sequentially first-proc
(car rest-procs))
(cdr rest-procs))))
(let ((procs (map analyze exps)))
(if (null? procs)
(error "Empty sequence: ANALYZE"))
(loop (car procs) (cdr procs)))) (define (analyze-application exp)
(let ((fproc (analyze (operator exp)))
(aprocs (map analyze (operands exp))))
(lambda (env)
(execute-application
(fproc env)
(map (lambda (aproc) (aproc env))
aprocs)))))
(define (execute-application proc args)
(cond ((primitive-procedure? proc)
(apply-primitive-procedure proc args))
((compound-procedure? proc)
((procedure-body proc)
(extend-environment
(procedure-parameters proc)
args
(procedure-environment proc))))
(else (error "Unknown procedure type:
EXECUTE-APPLICATION"
proc))))