4.12 Exercise 4.12

If we generalize lookup-variable-value, set-variable-value!, and define-variable! fully, we can see that the common pattern in each is that we loop through one frame in the environment, running one procedure if we find a binding with a given variable name, and running a different procedure if we reach the end of the current frame without finding it. In the case of lookup-variable-value and set-variable-value!, the latter procedure is the same iterative loop, but it doesn’t need to be so.

Based on the behaviors of these procedures, we can (somewhat arbitrarily) decide that the procedure executed if we find a binding should take the lists of variables and values as its parameters, and that the procedure to be performed if the frame doesn’t contain the binding we’re looking for only needs to take the current environment.

(define (search-env if-found if-not-found)
  (lambda (var env)
    (define (scan vars vals)
      (cond ((null? vars) (if-not-found env))
            ((eq? var (car vars)) (if-found vars vals))
            (else (scan (cdr vars) (cdr vals)))))
    (if (eq? env the-empty-environment)
        (error "Unbound variable" var)
        (let ((frame (first-frame env)))
          (scan (frame-variables frame)
                (frame-values frame))))))
(define (lookup-variable-value-alt val env)
  ((search-env
    (lambda (vars vals) (car vals))
    (lambda (env) (lookup-variable-value-alt val (enclosing-environment env))))
   val env))
(define (set-variable-value-alt! var val env)
  ((search-env
    (lambda (vars vals) (set-car! vals val))
    (lambda (env) (set-variable-value-alt! var val (enclosing-environment env))))
   var env))
(define (define-variable!-alt var val env)
  ((search-env
    (lambda (vars vals) (set-car! vals val))
    (lambda (env) (add-binding-to-frame! var val (first-frame env))))
   var env))

This arguably makes it clearer what the semantics of these procedures are – in particular, whether they search beyond the first frame of the environment or not.