sicp-ex-4.13



<< Previous exercise (4.12) | Index | Next exercise (4.14) >>


meteorgan

  
  
 ;; unbound variable in current frame 
 (define (unbound? expr) (tagged-list? expr 'unbound)) 
 (define (unbind-variable expr env) (make-unbound (cadr expr) env)) 
 (define (make-unbound variable env) 
         (let ((vars (frame-variables (first-frame env))) 
                   (vals (frame-values (first-frame env)))) 
                 (define (unbound vars vals new-vars new-vals) 
                         (cond ((null? vars) 
                                    (error "variable is not in the environment -- MAKE-UNBOUND"      
  
                                              variable)) 
                                  ((eq? (car vars) variable) 
                                   (set-car! env  
                                                 (cons (append new-vars (cdr vars))  
                                                          (append new-vals (cdr vals))))) 
                                  (else (unbound (cdr vars) (cdr vals)  
                                                           (cons (car vars) new-vars)  
                                                           (cons (car vals) new-vals))))) 
                 (unbound vars vals '() '()))) 
  
 ;; add this in eval 
 ((unbound? expr) (unbind-variable expr env)) 
  
 ;;there's no need to unbound bindings in the enclosing environments,and no need to send an error message(just like "define"),a simpler version is: 
 (define (make-unbound! var env) 
   (let* ((frame (first-frame env)) 
          (vars (frame-variables frame)) 
          (vals (frame-values frame))) 
     (define (scan pre-vars pre-vals vars vals) 
       (if (not (null? vars)) 
           (if (eq? var (car vars)) 
               (begin (set-cdr! pre-vars (cdr vars)) 
                      (set-cdr! pre-vals (cdr vals))) 
               (scan vars vals (cdr vars) (cdr vals))))) 
     (if (not (null? vars)) 
         (if (eq? var (car vars)) 
             (begin (set-car! frame (cdr vars)) 
                    (set-cdr! frame (cdr vals))) 
             (scan vars vals (cdr vars) (cdr vals)))))) 

wing's answer is more 4 times quick than my:

  
 (define (make-unbound! var env) ;4*linear 
     (let ((frame (first-frame env))) 
         (define pairs  
             (filter (lambda (pair) (not (eq? (car pair) var-target))) 
                     (map cons (car frame) (cdr frame)))) 
         (set-car! frame (map car pairs)) 
         (set-cdr! frame (map cdr pairs)))) 



master

This isn't that elegant. The problem is that the way I wrote search-frame it returns the exact cons cell where the match occurred, so there really isn't any way to access the surrounding frame. Could easily be picked up by a garbage collector though.

 (define (make-unbound! var env) 
   (let ((frame (first-frame env))) 
     (search-frame var 
                   frame 
                   (lambda (res) (and res (set-car! res '()) (set-cdr! res '()))) 
                   (lambda () '())))) 

Shade

I believe 'make-unbound!' should construct a list for the evaluator to check with 'unbound?', and only afterwards it calls the unbinding procedure:

 (define (make-unbound! var)            
   (list 'unbound! var))                
 (define (unbound? exp) (tagged-list? exp 'unbound!)) 
 (define (unbound!-var exp)              
   (cadr exp))                           
 (define (unbound! var env)                                
   (define (remove-binding var bindings) 
     (cond ((null? bindings) '()) 
           ((eq? var (caar bindings)) (cdr bindings)) 
           (else (cons (car bindings)  
                       (remove-binding var (cdr bindings)))))) 
   (let ((frame (first-frame env))) 
     (set-cdr! frame (remove-binding var (frame-bindings frame))))) 

pvk

Here's both options:

(define (make-unbound-in-frame! var frame)
  (define (scan bindings-before bindings-after)
    (cond ((null? bindings-after)
           bindings-before)
          ((eq? var (caar bindings-after))
            (append bindings-before (cdr bindings-after)))
          (else
           (scan (append bindings-before (list (car bindings-after)))
                 (cdr bindings-after)))))
  (set-cdr! frame (scan '() (cdr frame))))
(define (make-unbound-local! var env)
  (make-unbound-in-frame! (var (first-frame env))))
(define (make-unbound! var env)
  (if (eq? env the-empty-environment)
      'done
      (begin (make-unbound-local! var env)
             (make-unbound! var (enclosing-environment env)))))

My frames are in format ('*frame* (var1 val1) (var2 val2) ...) (as in sicp-ex-4.11). I didn't find the loops I wrote for sicp-ex-4.12 useful, as my scan-frame could only do anything to the final segment of the frame-bindings starting with the variable it was looking for.

As for the question the book asked, I found myself strongly preferring to unbind variables globally, for the following reasons:

  1. (make-unbound! var env) guarantees to the programmer that a subsequent lookup of var in env will return an "Unbound variable" error. (make-unbound-local! var env) can provide no such guarantee (and notice that we've so far given the programmer no tools for looking up a variable in a frame).
  2. If we only have make-unbound-local!, we can't unbind variables from inside procedure calls. The major downside of global make-unbound! is that a programmer might accidentally unbind a variable from a package they had installed. In my opinion, this is a problem that should be dealt with in the package loading system.