Exercise 4.11. Instead of representing a frame as a pair of lists, we can represent a frame as a list of bindings, where each binding is a name-value pair. Rewrite the environment operations to use this alternative representation.
对于一段代码,比如
(define x 1)
(define y 2)
(define z 3)
我们的 frame 现在是这个样子的:
((x y z) (1 2 3))
现在应该把它更改为:
((x . 1) (y . 2) (z . 3))
这就只需要在 make-frame 的时候把 variables 和 values 使用 map 组合起来就可以。
(define (make-frame variables values)
(map (lambda (x y) (cons x y)) variables values))
采用现在的表示形式,我们的 add-binding-to-frame! 也需要变化。为了保证能够修改传递到过程中的参数,我们只能使用 set-cdr! 去修改 frame 的最后一个 pair 的指针。
(define (add-binding-to-frame! var val frame)
(define (last-pair lst)
(if (or (null? lst) (null? (cdr lst)))
lst
(last-pair (cdr lst))))
(set-cdr! (last-pair frame) (list (cons var val))))
; 在变更了 frame 的表示方式之后,与 env 的底层有关系的操作都需要变更。包括
lookup-variable-value
set-variable-value!
define-variable!
(define (lookup-variable-value var env)
(define (lookup-frame frame)
(cond ((null? frame)
(lookup-variable-value var (enclosing-environment env)))
((eq? var (car (car frame)))
(cdr (car frame)))
(else
(lookup-frame (cdr frame)))))
(if (eq? env the-empty-environment)
(error 'lookup-variable-value "Unbound variable" var)
(lookup-frame (first-frame env))))
;;; 4.
(define (set-variable-value! var val env)
(define (lookup-and-set-frame frame)
(cond ((null? frame)
(set-variable-value! var val (enclosing-environment env)))
((eq? var (car (car frame)))
(set-cdr! (car frame) val))
(else
(lookup-and-set-frame (cdr frame)))))
(if (eq? env the-empty-environment)
(error 'set-variable-value! "Unbound variable -- SET!" var)
(lookup-and-set-frame (first-frame env))))
;;; 5.
(define (define-variable! var val env)
(define (lookup-and-set-frame frame)
(cond ((null? frame)
(add-binding-to-frame! var val (first-frame env)))
((eq? var (car (car frame)))
(set-cdr! (car frame) val))
(else
(lookup-and-set-frame (cdr frame)))))
(lookup-and-set-frame (first-frame env)))
修改完之后,我们使用之前就写好的 regression test 来进行测试:
;(load "evaluator.scm")
(load "../testframe.scm")
;;; self-evaluating
(assert= (eval 1 the-global-environment) 1)
(assertequal? (eval "str" the-global-environment) "str")
;; definition
(eval '(define var1 1) the-global-environment)
(assert= (eval 'var1 the-global-environment) 1)
(eval '(define var-special false) the-global-environment) ;; for testing of env lookup
(asserteq? (eval 'var-special the-global-environment) false)
(eval '(define (empty? lst) (null? lst))
the-global-environment)
(eval '(define (new-append x y)
(if (null? x)
y
(cons (car x)
(new-append (cdr x) y)))) the-global-environment)
(asserteq? (eval '(empty? '(1 2)) the-global-environment)
false)
(assertequal? (eval '(new-append '(1 2) '(3 4)) the-global-environment)
'(1 2 3 4))
;; assignment
(asserteq? (eval '(set! var1 2) the-global-environment) 'ok)
(assert= (eval 'var1 the-global-environment) 2)
(assert/exn (eval 'var2 the-global-environment) "Unbound")
(assert/exn (eval '(set! var2 34) the-global-environment) "Unbound")
;; if
(assert= (eval '(if 0 1 2) the-global-environment) 1)
(assert= (eval '(if 'x 1 2) the-global-environment) 1)
(assert= (eval '(if '() 1 2) the-global-environment) 1)
(assert= (eval '(if false 1 2) the-global-environment) 2)
;; cond
(assert= (eval '(cond ((= var1 2) 19)) the-global-environment) 19)
(assert= (eval '(cond ((= var1 1) 19)
((= var1 2) 20)) the-global-environment) 20)
(asserteq? (eval '(cond ((= var1 1) 19)) the-global-environment) false)
;; lambda
(asserteq? (eval '((lambda (x) (= x 1)) 1) the-global-environment) true)
;; begin
(assert= (eval '(begin (set! var1 14)
(set! var1 (+ var1 1))
var1) the-global-environment) 15)