Our environment model of evaluation and our metacircular evaluator execute
definitions in sequence, extending the environment frame one definition at a
time. This is particularly convenient for interactive program development, in
which the programmer needs to freely mix the application of procedures with the
definition of new procedures. However, if we think carefully about the
internal definitions used to implement block structure (introduced in
1.1.8), we will find that name-by-name extension of the environment may
not be the best way to define local variables.
Consider a procedure with internal definitions, such as
我们的环境模型求值和元循环求值器都是按顺序执行定义的,每次向环境框架中添加一个定义。这对于交互式程序开发特别方便,因为程序员需要自由地将过程的应用与新过程的定义混合起来。然而,如果我们仔细思考用于实现块结构(在 1.1.8 中引入)的内部定义,就会发现逐一扩展环境的方式未必是定义局部变量的最佳方法。考虑一个带有内部定义的过程,例如
Our intention here is that the name odd? in the body of the procedure
even? should refer to the procedure odd? that is defined after
even?. The scope of the name odd? is the entire body of
f, not just the portion of the body of f starting at the point
where the define for odd? occurs. Indeed, when we consider that
odd? is itself defined in terms of even?—so that even?
and odd? are mutually recursive procedures—we see that the only
satisfactory interpretation of the two defines is to regard them as if
the names even? and odd? were being added to the environment
simultaneously. More generally, in block structure, the scope of a local name
is the entire procedure body in which the define is evaluated.
我们的意图是:在过程 even? 的体中,名字 odd? 应当指向定义在 even? 之后的那个过程 odd?。名字 odd? 的作用域是 f 的整个过程体,而不仅仅是从 odd? 的 define 出现处开始的那部分。确实,当我们考虑到 odd? 本身是用 even? 定义的——即 even? 和 odd? 是相互递归的过程——我们会看到,对这两个 define 唯一令人满意的解释是:将它们视为 even? 和 odd? 这两个名字被同时加入环境。更一般地说,在块结构中,一个局部名字的作用域是对该 define 进行求值所在的整个过程体。
As it happens, our interpreter will evaluate calls to f correctly, but
for an “accidental” reason: Since the definitions of the internal procedures
come first, no calls to these procedures will be evaluated until all of them
have been defined. Hence, odd? will have been defined by the time
even? is executed. In fact, our sequential evaluation mechanism will
give the same result as a mechanism that directly implements simultaneous
definition for any procedure in which the internal definitions come first in a
body and evaluation of the value expressions for the defined variables doesn’t
actually use any of the defined variables. (For an example of a procedure that
doesn’t obey these restrictions, so that sequential definition isn’t equivalent
to simultaneous definition, see Exercise 4.19.)
实际上,我们的解释器对 f 的调用能给出正确结果,但这是出于一个"偶然"的原因:由于内部过程的定义排在最前面,在所有定义都完成之前不会有对这些过程的调用被求值。因此,当 even? 被执行时,odd? 已经被定义了。事实上,对于任何满足以下条件的过程——内部定义排在体的最前面,且被定义变量的值表达式在求值时并不实际使用任何被定义的变量——我们的顺序求值机制将给出与直接实现同时定义的机制相同的结果。(关于一个不满足这些限制、从而顺序定义与同时定义不等价的过程的例子,参见练习 4.19。)
There is, however, a simple way to treat definitions so that internally defined
names have truly simultaneous scope—just create all local variables that will
be in the current environment before evaluating any of the value expressions.
One way to do this is by a syntax transformation on lambda expressions.
Before evaluating the body of a lambda expression, we “scan out” and
eliminate all the internal definitions in the body. The internally defined
variables will be created with a let and then set to their values by
assignment. For example, the procedure
然而,有一种简单的方法可以处理定义,使得内部定义的名字真正具有同时的作用域——只需在对任何值表达式求值之前,先在当前环境中创建所有将要出现的局部变量。一种做法是对 lambda 表达式进行语法变换。在对 lambda 表达式的体求值之前,我们"扫描出"并消除体中的所有内部定义。内部定义的变量将通过 let 创建,然后用赋值将它们设置为各自的值。例如,过程
would be transformed into
将被变换为
where *unassigned* is a special symbol that causes looking up a variable
to signal an error if an attempt is made to use the value of the
not-yet-assigned variable.
其中 *unassigned* 是一个特殊符号,当试图使用某个尚未赋值的变量的值时,查找该变量将发出错误信号。
An alternative strategy for scanning out internal definitions is shown in
Exercise 4.18. Unlike the transformation shown above, this enforces the
restriction that the defined variables’ values can be evaluated without using
any of the variables’ values.
扫描出内部定义的另一种策略见练习 4.18。与上面展示的变换不同,那种策略强制了一项限制:被定义变量的值必须能够在不使用任何这些变量的值的情况下被求值。
(define (f x)
(define (even? n)
(if (= n 0)
true
(odd? (- n 1))))
(define (odd? n)
(if (= n 0)
false
(even? (- n 1))))
⟨rest of body of f⟩) (lambda ⟨vars⟩
(define u ⟨e1⟩)
(define v ⟨e2⟩)
⟨e3⟩) (lambda ⟨vars⟩
(let ((u '*unassigned*)
(v '*unassigned*))
(set! u ⟨e1⟩)
(set! v ⟨e2⟩)
⟨e3⟩))