Exercise 4.12. The procedures set-variable-value!, define-variable!, and lookup-variable-value can be expressed in terms of more abstract procedures for traversing the environment structure. Define abstractions that capture the common patterns and redefine the three procedures in terms of these abstractions.
本题我们将以在 练习 4.11 中新定义的 frame 作为基础。
让我们来再观察这三个过程。假设我们要找的是 x (或者是给 x 赋值,或者是定义 x ),对于这三个过程而言:
set-variable-value! 在某个 frame 中找到了包含 x 与 x 值的序对,然后把这个序对中 x 的值给替换掉;如果没有找到则提示 error
define-variable! 在某个 frame 中找到了包含 x 与 x 值的序对,然后把这个序对中 x 的值给替换掉;如果没有找到则在第一个 frame 中创建一个这样的序对。
lookup-variable-value 在某个 frame 中找到了包含 x 与 x 值的序对,然后把这个序对中 x 的值返回;如果没有找到则提示 error。
可以看出我们这里的共同的模式为:
- 到 env 去找与 x 有关的序对,返回这个序对以供使用
- 如果找到,则做一件事情
- 如果没有找到则做另外一件事情。
因此,我们可以抽象出第一步这个共同的模式–无论哪个操作都需要去 env 中去找到与 x 有关的序对,然后返回这个序对。
(define (lookup-env var env)
(define (lookup-frame frame)
(cond ((null? frame)
(lookup-env var (enclosing-environment env)))
((eq? var (car (car frame)))
(car frame))
(else
(lookup-frame (cdr frame)))))
(if (eq? env the-empty-environment)
'()
(lookup-frame (first-frame env))))
这里值得注意的是,在写 lookup-frame 的时候,一定不能使用 false 来表示在当前的 frame 中没有找到某个值。一定是在 lookup-frame 当前 frame 为 null? 的时候直接递归去找下一个 env。因为返回 false 时,可能是找到了变量但变量本身的值为 false,也可能是没有找到所以返回 false。
有了这个操作之后,我们需要定义的三个操作就可以非常一致地定义如下:
(define (lookup-variable-value var env)
(let ((var-val (lookup-env var env)))
(if (null? var-val)
(error 'lookup-variable-value "Unbound variable" var)
(cdr var-val))))
(define (set-variable-value! var val env)
(let ((var-val (lookup-env var env)))
(if (null? var-val)
(error 'set-variable-value! "Unbound variable -- SET!" var)
(set-cdr! var-val val))))
(define (define-variable! var val env)
(let ((var-val (lookup-env var env)))
(if (null? var-val)
(add-binding-to-frame! var val (first-frame env))
(set-cdr! var-val val))))
最后依然是使用 regression test 进行测试。