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.

————————————————————————————————————————————————————————————————————————

My solution to the previous exercise did this to some extent.

Another approach is to define a procedure which searches a frame for a 
binding matching a given variable, and, if found, calls the procedure.

In the cases of set-variable-value! and define-variable!, the procedure 
will need to modify the value stored in the binding, so we pass the list 
to the procedure so that the procedure can modify it.

If the binding is not found, we return #f.

(define (apply-to-vals var proc frame)
  (define (scan vars vals)
    (cond ((null? vars) #f)
          ((eq? (car vars) var) (proc vals))
          (else (scan (cdr vars) (cdr vals)))))
  (scan (frame-variables frame) (frame-values frame)))

(define (lookup-variable-value var env)
  (if (eq? env the-empty-environment)
      (error "Unbound variable" var)
      (let* ((frame (first-frame env))
             (result (apply-to-vals var (lambda (x) x) frame))) ;; don't do anything if found, just return the list
        (if result
            (car result) ;; we could do the car inside the lambda above, but that would introduce a bug (the value might be false)
            (lookup-variable-value var (enclosing-environment env))))))

(define (set-variable-value! var val env)
  (if (eq? env the-empty-environment)
      (error "Unbound variable -- SET!" var)
      (let* ((frame (first-frame env))
             (result (apply-to-vals var (lambda (vals) (set-car! vals val) #t) frame)))
        (if result
            'ok
            (set-variable-value! var val (enclosing-environment env))))))

(define (define-variable! var val env)
  (let* ((frame (first-frame env))
         (result (apply-to-vals var (lambda (vals) (set-car! vals val) #t) frame)))
    (if result
        'ok
        (add-binding-to-frame! var val frame))))
