As we have seen, the
assignment statement
enables us to model objects
that have local state. However, this advantage comes at a price. Our
programming language can no longer be interpreted in terms of the
substitution model of
function
application that we introduced in
section 1.1.5. Moreover, no simple model with
nice

mathematical properties can be an adequate framework for
dealing with objects and assignment in programming languages.

So long as we do not use assignments, two evaluations of the same
function
with the same arguments will produce the same result, so
that
functions
can be viewed as computing mathematical functions.
Programming without any use of assignments, as we did throughout the
first two chapters of this book, is accordingly known as
*
functional programming*.

To understand how assignment complicates matters, consider a
simplified version of the `make_withdraw`
function
of
section 3.1.1 that does not bother to check
for an insufficient amount:

function make_simplified_withdraw(balance) { return amount => { balance = balance - amount; return balance; }; }

Compare this
function
with the following `make_decrementer`
function, which does not use assignment:

function make_decrementer(balance) { return amount => balance - amount; }

The function
`make_decrementer`
returns a
function
that subtracts its input
from a designated amount `balance`,
but there is no accumulated effect
over successive calls,
as with `make_simplified_withdraw`:

const d = make_decrementer(25);

d(20); // output: 5

d(10); // output: 15

We can use the substitution model to explain how `make_decrementer` works. For instance, let us analyze the evaluation
of the expression

(make_decrementer(25))(20);

(amount => 25 - amount)(20);

Now we apply the operator by substituting 20 for `amount` in the
body of the
function definition
expression:

25 - 20;

Observe, however, what happens if we attempt a similar substitution
analysis with `make_simplified_withdraw`:

(make_simplified_withdraw(25))(20);

We first simplify the operator by substituting 25 for
`balance` in
the return expression of
`make_simplified_withdraw`.
This reduces the expression to[1]

(amount => { balance = 25 - amount; return 25; })(20);

Now we apply the function
by substituting 20 for `amount` in the
body of the
function:

balance = 25 - 20; return 25;

If we adhered to the substitution model, we would have to say that the
meaning of the
function
application is to first set `balance` to
5 and then return 25 as the value of the expression. This gets the
wrong answer. In order to get the correct answer, we would have to
somehow distinguish the first occurrence of `balance` (before the
effect of the assignment) from the second occurrence of `balance`
(after the effect of the assignment), and the substitution model
cannot do this.

The trouble here is that substitution is based ultimately on the
notion that the symbols in our language are essentially names for
values.
place

in our
computational model.

The issue surfacing here is more profound than the mere breakdown of a
particular model of computation. As soon as we introduce change into
our computational models, many notions that were previously
straightforward become problematical. Consider the concept of two
things being the same.

Suppose we call
`make_decrementer`
twice with the same argument to
create two
functions:

const d1 = make_decrementer(25); const d2 = make_decrementer(25);

Are
`d1`
and
`d2`
the same? An acceptable answer is yes,
because
`d1`
and
`d2`
have the same computational
behavior—each is a
function
that subtracts its input from 25. In
fact,
`d1`
could be substituted for
`d2`
in any computation
without changing the result.

Contrast this with making two calls to
`make_simplified_withdraw`:

const w1 = make_simplified_withdraw(25); const w2 = make_simplified_withdraw(25);

Are
`w1`
and
`w2`
the same? Surely not, because calls to
`w1`
and
`w2`
have distinct effects, as shown by the following
sequence of interactions:

w1(20);

w1(20);

w2(20);

Even though
`w1`
and
`w2`
are equal

in the sense that they
are both created by evaluating the same expression,
`make_simplified_withdraw(25)`, it is not true that
`w1`
could be
substituted for
`w2`
in any expression without changing the result
of evaluating the expression.

A language that supports the concept that equals can be substituted
for equals

in an expression without changing the value of the expression is said to be
*referentially transparent*. Referential transparency is violated
when we include assignment in our computer language. This makes it
tricky to determine when we can simplify expressions by substituting
equivalent expressions. Consequently, reasoning about programs that
use assignment becomes drastically more difficult.

Once we forgo referential transparency, the notion of what it means
for computational objects to be the same

becomes difficult to
capture in a formal way. Indeed, the meaning of same

in the real
world that our programs model is hardly clear in itself. In general,
we can determine that two apparently identical objects are indeed
the same one

only by modifying one object and then observing
whether the other object has changed in the same way. But how can we
tell if an object has changed

other than by observing the same

object twice and seeing whether some property of the object differs
from one observation to the next? Thus, we cannot determine
change

without some *a priori* notion of sameness,

and we
cannot determine sameness without observing the effects of change.

As an example of how this issue arises in programming, consider the
situation where Peter and Paul have a bank account with

const peter_acc = make_account(100); const paul_acc = make_account(100);

const peter_acc = make_account(100); const paul_acc = peter_acc;

In the first situation, the two bank accounts are distinct.
Transactions made by Peter will not affect Paul's account, and vice
versa. In the second situation, however, we have defined
`paul_acc`
to be *the same thing* as
`peter_acc`
In effect,
Peter and Paul now have a joint bank account, and if Peter makes a
withdrawal from
`peter_acc`
Paul will observe less money in
`paul_acc`.
These two similar but distinct situations can cause
confusion in building computational models. With the shared account,
in particular, it can be especially confusing that there is one object
(the bank account) that has two different names
(`peter_acc`
and
`paul_acc`);
if we are searching for all the places in our program where
`paul_acc`
can be changed, we must remember to look also at
things that change
`peter_acc`.[2]

With reference to the above remarks on sameness

and change,

observe that if Peter and Paul could only examine their bank balances,
and could not perform operations that changed the balance, then the
issue of whether the two accounts are distinct would be moot. In
general, so long as we never modify data objects, we can regard a
compound data object to be precisely the totality of its pieces. For
example, a rational number is determined by giving its numerator and
its denominator. But this view is no longer valid in the presence of
change, where a compound data object has an identity

that is
something different from the pieces of which it is composed. A bank
account is still the same

bank account even if we change the
balance by making a withdrawal; conversely, we could have two
different bank accounts with the same state information. This
complication is a consequence, not of our programming language, but of
our perception of a bank account as an object. We do not, for
example, ordinarily regard a rational number as a changeable object
with identity, such that we could change the numerator and still have
the same

rational number.

In contrast to functional programming, programming that makes
extensive use of assignment is known as
*imperative
programming*. In addition to raising complications about
computational models, programs written in imperative style are
susceptible to bugs that cannot occur in functional programs. For
example, recall the iterative factorial program from
section 1.2.1:

function factorial(n) { function iter(product,counter) { if (counter > n) { return product; } else { return iter(counter*product, counter+1); } } return iter(1,1); }

function factorial(n) { let product = 1; let counter = 1; function iter() { if (counter > n) { return product; } else { product = counter * product; counter = counter + 1; return iter(); } } return iter(); }

counter = counter + 1; product = counter * product;

The complexity of imperative programs becomes even worse if we consider applications in which several processes execute concurrently. We will return to this in section 3.4. First, however, we will address the issue of providing a computational model for expressions that involve assignment, and explore the uses of objects with local state in designing simulations.

// make_joint function to be written by students const paul_acc = make_joint(peter_acc, "open sesame", "rosebud");

function make_joint(linked_acc, linked_pw, joint_pw) { return (message, input_pw) => { // Check authentication for joint account if (input_pw !== joint_pw) { return x => "Wrong joint account password"; } else { const access_linked = linked_acc(message, linked_pw); // Check authentication for linked account if (access_linked(0) === "Incorrect Password") { // access_linked(0) does a deposit / withdrawal of 0, in order // to test for the "Incorrect Password" message. return x => "Wrong linked account password"; } else { // All authentication passed, return accessed account to user return access_linked; } } }; }

There is currently no solution available for this exercise. This textbook adaptation is a community effort. Do consider contributing by providing a solution for this exercise, using a Pull Request in Github.

[1] We don't substitute for
the occurrence of `balance` in the assignment statement because
the name in an assignment is not evaluated.
If we did substitute for it, we would get
`25 = 25 - amount;`, which makes no sense.

[2] The phenomenon of a
single computational object being accessed by more than one name is
known as
*aliasing*. The joint bank account situation illustrates
a very simple example of an alias. In section 3.3
we will see much more complex examples, such as *side-effect bugs* are so difficult to locate and to
analyze that some people have proposed that programming languages be
designed in such a way as to not allow side effects or aliasing
(Lampson et al. 1981 ;
Morris, Schmidt, and Wadler 1980 ).

distinctcompound data structures that share parts. Bugs can occur in our programs if we forget that a change to an object may also, as a

side effect,change a

differentobject because the two

differentobjects are actually a single object appearing under different aliases. These so-called

[3] In view of this, it is ironic that introductory programming
is most often taught in a highly imperative style. This may be a
vestige of a belief, common throughout the 1960s and 1970s, that
programs that call
functions
must inherently be less efficient than
programs that perform assignments.
(Steele 1977
debunks this
argument.) Alternatively it may reflect a view that step-by-step
assignment is easier for beginners to visualize than
function
call.
Whatever the reason, it often saddles beginning programmers with

should I set this variable before or after that oneconcerns that can complicate programming and obscure the important ideas.

3.1.3
The Costs of Introducing Assignment