# 3.2 The Environment Model of Evaluation
# 3.2.1 The Rules for Evaluation
(define square (lambda (x) (* x x)))
(square 5) => (* 5 5) => 25
Environment diagram:
; _____________
; global env --> | square: --+ |
; |___________|_|<-+
; ^ | |
; | V |
; E1 -->[x: 5] [*|*]--+
; V
; parameters: x
; body: (* x x)
# 3.2.2 Applying Simple Procedures
(define (sum-of-squares x y) (+ (square x) (square y)))
(define (f a) (sum-of-squares (+ a 1) (* a 2)))
(f 5) => (sum-of-squares (+ 5 1) (* 5 2)) => (+ (square 6) (square 10)) => 136
Environment diagram:
; _____________________
; global env --> | sum-of-squares: ... |
; | square: ... |
; | f: ... |<------------+
; |_____________________|<+ |
; (f 5) ^ ^ | |
; | | | |
; E1->[a: 5] E2->[x: 6] E3->[x: 6] E4->[x:10]
; [y: 7]
; (sum-of-squares (+ (square x) (* x x) (* x x)
; (+ a 1) (square y))
; (* a 2)
# Exercise 3.9
(define (factorial n)
(if (= n 1) 1 (* n (factorial (- n 1)))))
Six environments are created in the recursive version:
(factorial 6) ; E1 -> [n: 6]
=> (* 6 (factorial 5)) ; E2 -> [n: 5]
=> (* 6 (* 5 (factorial 4))) ; E3 -> [n: 4]
=> (* 6 (* 5 (* 4 (factorial 3)))) ; E4 -> [n: 3]
=> (* 6 (* 5 (* 4 (* 3 (factorial 2))))) ; E5 -> [n: 2]
=> (* 6 (* 5 (* 4 (* 3 (* 2 (factorial 1)))))) ; E6 -> [n: 1]
=> 720
(define (factorial n) (fact-iter 1 1 n))
(define (fact-iter product counter max-count)
(if (> counter max-count)
product
(fact-iter (* counter product)
(+ counter 1)
max-count)))
Eight environments are created in the iterative version:
(factorial 6) ; E1 -> [n: 6]
=> (fact-iter 1 1 6) ; E2 -> [p: 1, c: 1, m: 6]
=> (fact-iter 1 2 6) ; E3 -> [p: 1, c: 2, m: 6]
=> (fact-iter 2 3 6) ; E4 -> [p: 2, c: 3, m: 6]
=> (fact-iter 6 4 6) ; E5 -> [p: 6, c: 4, m: 6]
=> (fact-iter 24 5 6) ; E6 -> [p: 24, c: 5, m: 6]
=> (fact-iter 120 6 6) ; E7 -> [p: 120, c: 6, m: 6]
=> (fact-iter 720 7 6) ; E8 -> [p: 720, c: 7, m: 6]
=> 720
# 3.2.3 Frames as the Repository of Local State
# Exercise 3.10
With or without the explicit state variable, make-withdraw
creates objects with the same behaviour. The only difference with the explicit variable in the let-form is that there is an extra environment. Applying make-withdraw
creates E1 to bind 100 to initial-amount
, and then the let-form desugars to a lambda application, creating a new environment E2. This environment holds balance
, beginning with the same value as initial amount
. When we evaluate (W1 20)
, we create the environment E3 that binds amount
to 20. The assignment in the code for W2 changes the value of balance
from 100 to 80. The value of initial-amount
remains the same, because it was never changed in a set!
assignment. The behaviour is no different with the let-form because, although we are now saving the original balance, we aren’t doing anything with it. We can’t access it outside of the procedure.
; ____________________
; global env -->| make-withdraw: ... |
; | W2: ---------+ |
; | W1: | |<--------------------+
; |_|____________|_____| E3 |
; | ^ | [initial-amount: 100]
; | E1 | +--------------->[*|*] ^
; | [initial-amount: 100] | | E4 |
; | ^ | +--->[balance: 60]
; V E2 | | ^
; [*|*]-->[balance: 80] | |
; V ^ | |
; parameters: amount }<---|----------------+ |
; body: ... } | |
; | (W2 40) [amount: 40]
; |
; (W1 20) [amount: 20]-----+
# 3.2.4 Internal Definitions
# Exercise 3.11
First, we just have a procedure bound in the global environment.
; global env --> [make-account: ...]
(define acc (make-account 50))
Now, we have acc
in the global frame as well. It is bound to a procedure whose environment pointer points to E1, the environment created when we evaluated (make-account 50)
. It first bound the formal parameter balance
to 50, and then three internal procedures were defined and bound in the same frame. One of them, dispatch
, points to the same procedure as acc
.
; __________________
; global env --> | make-account: ...|
; | acc: |
; |__|_______________|
; | ^
; V E1___|____________
; [*|*]---->| balance: 50 |<-----+
; V | withdraw:------|-->[*|*]
; parameters: m | deposit:-------|-+ +-----> parameters: amount
; body: ... | dipspatch:-+ | | body: ...
; ~~~~~~~~~~~~<---------------+ | +->[*|*]
; |________________|<----|-+
; +----> parameters: amount
; body: ...
((acc 'deposit) 40) => 90
First we evaluate (acc 'deposit)
. We create E2 to bind m
to the symbol deposit
, and then we evaluate the body of acc
, which is the same as the body of dispatch
. The enclosing environment of E2 is E1, because that is pointed to by the procedure. This application returns the value of deposit
;;;;; [TODO: The #<deposit> on next line trips schemehl assertion.]
;;;;; from E1. Now we evaluate `((#<deposit> 40)`. We create E3 to bind `amount` to
the value 40, and the enclosing environment is E1 (pointed to by the procedure deposit
). This finally assigns 90 to balance
in E1, and then returns that value.
; E2 [m: deposit]--+
; +----> E1 [balance:90, ...]
; E3 [amount: 40]--+
((acc 'withdraw) 60) => 30
This is almost the same, except the procedure returns the withdraw
procedures instead. I am reusing the names E2 and E3 because they have been used and are no longer relevant, since nothing poitns to them.
; E2 [m: withdraw]--+
; +---> E1 [balance: 30, ...]
; E3 [amount: 60]---+
All this time, the local state for acc
is kept in E1, the environment originally created to apply the make-account
procedure. If we define another account with (define acc2 (make-account 100))
, it will have its own environment containing balance
and bindings for the internal procedures. The only thing shared between acc
and acc2
is (possibly) the code for the internal procedures, including dispatch
, which the accounts really are. This sharing is an implementation detail, though.