We ordinarily view the world by objects. Each object can contain internal state and could be changed over time. In a system composed with many objects, object can influence each other's through interactions. This can make subsystems or objects internally closely coupled which keeping loosely coupled with other systems.
We will investigate two prominent organizational strategies arising from two rather different ``world views'' of the structure of systems. The first organizational strategy concentrates on objects, viewing a large system as a collection of distinct objects whose behaviors may change over time. An alternative organizational strategy concentrates on the streams of information that flow in the system, much as an electrical engineer views a signal-processing system.
Assignment and Local State
Think of this API:
(withdraw 25)
(withdraw 25)
(withdraw 60)
(withdraw 35)
Expression (withdraw 25)
was evaluated twice, and yield different values. Until now, all of our procedure could be viewed as mathmatical functions. Calling a procedure with same arguments, always preduced the same results.
We could implment withdraw
like this:
(define (withdraw amount)
(if (>= balance amount)
(begin (set! balance (- balance amount))
balance)
"Insufficient funds"))
Decrementing is accomplished by set!
, whose syntax is:
(set! <name> <new-value>)
And begin
can evaluated a sequnce of expressions and return the last expression's value.
Although our withdraw
work as expected, the global variable balance
was exposed and could be freely examined and modified.
We can make balance
internal to withdraw
:
(define new-withdraw
(let ((balance 100))
(lambda (amount)
(if (>= balance amount)
(begin (set! balance (- balance amount))
balance)
"Insufficient funds"))))
We use let
to introduce a local variable balance
. And with this local environment, we use lambda
to create a procedure that takes amount
as argument, and behaves like previous withdraw
.
After we introduced assignment to our code, the substitution is no longer an adequate model.
Then we can create a constructor:
(define (make-withdraw balance)
(lambda (amount)
(if (>= balance amount)
(begin (set! balance (- balance amount))
balance)
"Insufficient funds")))
Play with it a little bit:
(define W1 (make-withdraw 100))
(define W2 (make-withdraw 100))
(W1 50)
(W2 70)
(W2 40)
(W1 40)
Interestingly, W1
and W2
are completely independent, each with its own local state variable balance
.
(define (make-account balance)
(define (withdraw amount)
(if (>= balance amount)
(begin (set! balance (- balance amount))
balance)
"Insufficient funds"))
(define (deposit amount)
(set! balance (+ balance amount))
balance)
(define (dispatch m)
(cond ((eq? m 'withdraw) withdraw)
((eq? m 'deposit) deposit)
(else (error "Unknown request -- MAKE-ACCOUNT" m))))
dispatch)
(define acc (make-account 100))
((acc 'withdraw) 50)
((acc 'deposit) 60)
((acc 'withdraw) 60)
We return an additional procedure dispatch
that takes a message as input and return one of the two local precedures. This is precisely the message-passing style of programming.
The Benefits of Introducing Assignment
Introducing assignment is both introducing diffculties and benefits. For the benefits part, it's a powerful technique for maintaining modular design.
To be implemented Monte Carlo Simulation section
The Costs of Introducing Assignment
Consider these two functions:
(define (make-simplified-withdraw balance)
(lambda (amount)
(set! balance (- balance amount))
balance))
(define (make-decrementer balance)
(lambda (amount)
(- balance amount)))
We can use substitution model to explain how make-decrementer
works:
((make-decrementer 25) 20)
((lambda (amount) (- 25 amount)) 20)
(- 25 20)
5
However, if we attempt a similar substitution analysis with make-simplified-withdraw
:
((make-simplified-withdraw 25) 20)
((lambda (amount) (set! balance (- 25 amount)) 25) 20)
(set! balance (- 25 20))
25
This is incorrect because set!
first set balance
to 5 then return 25 as the value of the expression. The trouble here is that substitution is based ultimately on the notion that the symbols in our language ar essentially names for values. But as soon as we introduce set!
and the idea that the value of a variable can change, a variable can no longer simply be a name.
Sameness and change
Not only substitution model is violated after introducing variable, many notions become problematical. One of them is two things being the same. Say we call make-decrementer
twice:
(define D1 (make-decrementer 25))
(define D2 (make-decrementer 25))
D1 and D2 are conceptual same because they have same computational behavior. In constract if we call make-simplified-withdraw
twice:
(define W1 (make-simplified-withdraw 25))
(define W2 (make-simplified-withdraw 25))
They are not same because they are not referentially transparent. In another word, it disables the mental model inlanguage that 'equals can be substitued for equals'.
However, in real world the meaning of 'same' is hardly clear itself. Even Peter and Paul's bank accounts have same deposit, they are different accounts. A bank account is still 'the same' even if we change the balance. Conversely, rational number could be still the same even they have different numerator and denomenator.
Pitfall of imperative programming
In constrast to functional programming, programming that make extensive use of assignment is known as imperative programming. Program written in imperative style are suspectipble to bugs that cannot occur in functional programs. For previous factorial
example:
(define (factorial n)
(define (iter product counter)
(if (> counter n)
product
(iter (* counter product)
(+ counter 1))))
(iter 1 1))
If we make it in a imperative manner:
(define (factorial n)
(let ((product 1)
(counter 1))
(define (iter)
(if (> counter n)
product
(begin (set! product (* counter product))
(set! counter (+ counter 1))
(iter))))
(iter)))
This is correct but if we write it in the opposite order it would have produced an incorrect result:
(set! counter (+ counter 1))
(set! product (* counter product))
This will become even worse if we consider applications in which several processes execute concurrently.