We can turn to the environment model to see how
procedures
functions
and assignment can be used to represent objects with local state. As an
example, consider the
withdrawal processor
from
section 3.1.1 created by calling the
procedure
function
Original | JavaScript |
(define (make-withdraw balance) (lambda (amount) (if (>= balance amount) (begin (set! balance (- balance amount)) balance) "Insufficient funds"))) | function make_withdraw(balance) { return amount => { if (balance >= amount) { balance = balance - amount; return balance; } else { return "insufficient funds"; } }; } |
Original | JavaScript |
(define W1 (make-withdraw 100)) | const W1 = make_withdraw(100); |
Original | JavaScript |
(W1 50) 50 | W1(50); 50 |
Original | JavaScript | |
Figure 3.11
Result of defining make-withdraw in the
global environment.
|
Figure 3.12
Result of defining
make_withdraw in
the program environment.
|
The interesting part of the computation happens when we apply the procedure function make-withdraw make_withdraw to an argument:
Original | JavaScript |
(define W1 (make-withdraw 100)) | const W1 = make_withdraw(100); |
Original | JavaScript | |
Figure 3.13
Result of evaluating
(define W1 (make-withdraw 100)).
|
Figure 3.14
Result of evaluating
const W1 = make_withdraw(100);.
|
Now we can analyze what happens when W1 is applied to an argument:
Original | JavaScript |
(W1 50) 50 | W1(50); 50 |
Original | JavaScript |
(if (>= balance amount) (begin (set! balance (- balance amount)) balance) "Insufficient funds") | if (balance >= amount) { balance = balance - amount; return balance; } else { return "insufficient funds"; } |
Original | JavaScript | |
Figure 3.15
Environments created by applying the procedure object
W1.
|
Figure 3.16
Environments created by applying the function object
W1.
|
When the
set!
assignment
is executed, the binding of balance in E1 is
changed. At the completion of the call to
W1, balance is 50,
and the frame that contains balance is still
pointed to by the
procedure
function
object W1. The frame that binds
amount (in which we executed the code that
changed balance) is no longer relevant, since
the
procedure
function
call that constructed it has terminated, and there are no pointers to that
frame from other parts of the environment. The next time
W1 is called, this will build a new frame that
binds amount and whose enclosing environment is
E1. We see that E1 serves as the place
that holds the local
state variable for the
procedure
function
object W1.
Figure 3.17
Figure 3.18
shows the situation after the call to W1.
Original | JavaScript | |
Figure 3.17
Environments after the call to W1.
|
Figure 3.18
Environments after the call to
W1.
|
Observe what happens when we create a second withdraw
object
by making another call to
make_withdraw:
make_withdraw:
Original | JavaScript |
(define W2 (make-withdraw 100)) | const W2 = make_withdraw(100); |
Original | JavaScript | |
Figure 3.19
Using (define W2 (make-withdraw 100)) to
create a second object.
|
Figure 3.20
Using
const W2 = make_withdraw(100);
to create a second object.
|
Original | JavaScript |
(define (make-withdraw initial-amount) (let ((balance initial-amount)) (lambda (amount) (if (>= balance amount) (begin (set! balance (- balance amount)) balance) "Insufficient funds")))) | function make_withdraw(initial_amount) { return (balance => amount => { if (balance >= amount) { balance = balance - amount; return balance; } else { return "insufficient funds"; } })(initial_amount); } |
Original | JavaScript | |
Recall from section 1.3.2 that let is simply syntactic sugar for a procedure function call: (let ((var exp)) body) is interpreted as an alternate syntax for ((lambda (var) body) exp) | The outer lambda expression is invoked immediately after it is evaluated. Its only purpose is to create a local variable balance and initialize it to initial_amount. |
Original | JavaScript |
(define W1 (make-withdraw 100)) (W1 50) (define W2 (make-withdraw 100)) | const W1 = make_withdraw(100); W1(50); const W2 = make_withdraw(100); |