灯下 登录
计算机科学 / SICP / subsection 中英对照

4.1.7 Separating Syntactic Analysis from Execution

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 中相同的数据结构、语法过程和运行时支持过程。

Racket #lang sicp
(define (factorial n)
 (if (= n 1)
 1
 (* (factorial (- n 1)) n)))
Racket #lang sicp
(define (eval exp env) ((analyze exp) env))
Racket #lang sicp
(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))))
Racket #lang sicp
(define (analyze-self-evaluating exp)
 (lambda (env) exp))
Racket #lang sicp
(define (analyze-quoted exp)
 (let ((qval (text-of-quotation exp)))
 (lambda (env) qval)))
Racket #lang sicp
(define (analyze-variable exp)
 (lambda (env)
 (lookup-variable-value exp env)))
Racket #lang sicp
(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)))
Racket #lang sicp
(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)))))
Racket #lang sicp
(define (analyze-lambda exp)
 (let ((vars (lambda-parameters exp))
 (bproc (analyze-sequence
 (lambda-body exp))))
 (lambda (env)
 (make-procedure vars bproc env))))
Racket #lang sicp
(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))))
Racket #lang sicp
(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))))