3.2.4 内部的定义
在1.1.8部分中介绍了程序能有内部的定义的思想.因此,导致了
在如下的程序中计算平方根时的程序块结构:
(define (sqrt x)
(define (sqrt-iter guess)
(if (good-enough? guess)
guess
(sqrt-iter (improve guess))
))
(define (good-enough? guess)
(< (abs (- (square guess) x)) 0.0001))
(define (improve guess)
(average guess (/ x guess)))
(sqrt-iter 1.0)
)
现在我们能使用环境模型来看看为什么这些内部的定义行为是符合预期的。
图3.11显示了在解释表达式(sqrt 2)中的要点,内部程序good-enough?
首次被调用时,guess等于1。
图3.11 有内部定义的程序sqrt
注意环境的结构。sqrt是一个在全局环境中的符号,它绑定到一个程序对象,
这个程序对象的相关的环境是全局环境。当sqrt被调用时,一个新的环境“环境1”
被创建,指向全局环境,它的参数x,绑定为2。sqrt的程序体在E1中被解释。
因为sqrt的程序体中的第一个表达式是:
(define (good-enough? guess)
(< (abs (- (square guess) x)) 0.0001))
在环境E1中解释被程序good-enough?定义的这个表达式。为了更精确的描述,
符号good-enough?被添加到E1的第一个帧中,绑定到一个程序对象,这个程序
对象的相关的环境是E1。相似的,imrove和sqrt-iter被定义在E1中。为了简洁性,
图3.11只显示了good-enough?的程序对象。
局部程序被定义以后,表达式(sqrt-iter 1.0) 被解释,仍然在环境1.在环境1,程序对象绑定到
sqrt-iter被调用以1为实际参数.这创建了一个新的环境2,guess是sqrt-iter的参数,绑定为1。
sqrt-iter调用good-enough?,以guess的值作为good-enough?的实际参数。这安装另一个环境3。
guess被绑定为1。尽管sqrt-iter和good-enough?都有参数命名为guess,这两个不同的局部变量
被放在不同的帧中.环境2和环境3的父环境都是环境1。这样的一个结果是符号x出现在
good-enough?的程序体中,它引用出现在环境中的绑定, 当原始的sqrt程序被调用时。
环境模型因此解释了两个重要的属性,做局部程序的定义,为了模块化程序的一个有用的技术。
局部程序的名称没有干扰到外部程序的名称,因为局部程序的命名被绑定到程序创建的帧中,
而不是绑定到全局环境中。
局部程序能读取到外部程序的实际参数,使用参数名,作为自由的变量。
这是因为局部程序的程序体被解释在一个环境中,这个环境是外部程序
的解释环境的下一级。
练习3.11
在3.2.3部分中,我们看到了环境模型如何描述了有局部状态的程序的行为。
现在我们已经看到了内部定义如何工作的。一个经典的消息传递的程序包括了
所有的这些方面。考虑3.1.1部分中的银行账户的程序:
(define (make-account balance)
(define (withdraw amount)
(if (>= balance amount)
(begin (set! balance (- balance amount))
balance)
"Insulfficient funds"))
(define (deposit amount)
(set! balance (+ balance amount))
balance)
(define (dispatch m)
(cond ((eq? m 'withdraw) withdraw)
((eq? m 'deposit) deposit)
(else (error "Unknown request -- MAKE ACCOUNT" m))
))
dispatch)
显示交互的序列生成的环境结构
(define acc (make-account 50))
((acc 'deposit) 40)
90
((acc 'withdraw) 60)
30
acc的局部状态保存在哪里? 假定我们定义了另一个账户
(define acc (make-account 100))
这两个账户的局部状态是如何保持区别的?在acc和 acc2之间
程序结构的哪一个部分是共享的?