SICP 全笔记

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 的底层有关系的操作都需要变更。包括

  1. lookup-variable-value
  2. set-variable-value!
  3. 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)