A set!-expression, also known as an ASSIGNMENT, has the following shape:
(set! var exp)
It consists of a variable, the LEFT-HAND SIDE,
and an expression, called RIGHT-HAND SIDE.
The left-hand side of a set!-expression is a fixed
variable. In this book, we only use variables that are define
d,
either at the top-level or in a local-expression. A set!-expression may occur wherever
an expression is legal.
The value of a set!-expression is always the same and is moreover invisible. It
is therefore irrelevant. What matters about a set!-expression, instead, is the
effect of its evaluation.
Specifically, for the first step of the
evaluation of a set!-expression, we determine the value of exp
. Let's
say this value is V
. For the second step, we change the definition
of var
to
(define var V)
The EFFECT
of this second step is that from this point on,
all references to var
in an evaluation replace var
by
V
.^{68} Its former value is lost.
Understanding the true nature of assignments is difficult. We therefore first consider a simple though useless example.
Consider the following definition and expression:
(define x 3) (local ((define z (set! x (+ x 2)))) x)
The definition says that x
stands for 3
. The
local-expression introduces a definition for z
. Its body
is x
so, in the past, the value of this local-expression
would have been 3
(if anything). Now, with set!
in the
language,
this is no longer true. To understand what happens, we must
rewrite the program step by step until we have a final answer.
The first step in the evaluation lifts the local
definition:
(define x 3) (define z (set! x (+ x 2))) x
Next we must determine the value of (set! x (+ x 2))
. According to
the general explanation of set!
, this requires the evaluation of
the right-hand side of the assignment:
(define x 3) (define z (set! x 5)) x
That value is 5
because the current value of x
is
3
.
Finally, the general explanation says that the effect of the set!
expression is to change the value that the left-hand side variable
represents. In our example this means that from now on, x
is no
longer 3
but 5
. The best way to express this change is to
modify the definition of x
for the next step:
(define x 5) (define z (void)) x
The value of set!
is (void)
, the invisible value. By
replacing the set!-expression with the invisible value, we indicate that its
evaluation is finished.
At this point, it is easy to see that the result is 5
. The first
definition says that x
currently represents 5
, and the
last expression is x
. Hence the value of the function evaluation
is 5
.
Exercise 35.1.1. Consider the following:
(set! x 5)
(define x 3) (set! (+ x 1) 5)
(define x 3) (define y 7) (define z false) (set! (z x y) 5)
Which ones are syntactically legal programs? Which ones are illegal? Solution
Exercise 35.1.2. Evaluate the following program:
(define x 1) (define y 1) (local ((define u (set! x (+ x 1))) (define v (set! y (- y 1)))) (* x y))
If set!
were not a part of the language, what could we say about
the result of the local-expression? That is, consider the skeleton
(define x 1) (define y 1) (local ((define u ...) (define v ...)) (* x y))
where the right-hand sides of the definitions have been removed. What would this expression have produced before the introduction of set!-expressions? Solution
The hand-evaluation shows that the local
definition for z
serves to evaluate a set!-expression and ``to throw away'' its
value. After all, a set!
's true purpose is to change a definition
and not to generate a value. Because this situation is quite common, Scheme
also provides the begin-expression:
(begin exp-1 ... exp-n exp)
A begin-expression consists of the keyword begin
followed
by a sequence of n + 1 expressions. The evaluation determines the values of
all expressions, in order, and then throws away the first n. The value of
the last expression is the value of the entire begin-expression.
In general, the first n subexpressions in a begin-expression
change some definitions; only the last one has an interesting value.
We can now rewrite our first sample program with set!
into
a short expression:
(define x 3) (begin (set! x (+ x 2)) x)
The use of begin
not only simplifies the program, it also suggests
a straight-line ordering of the evaluation.
The hand-evaluation also shows that the evaluation of set!-expression introduces additional timing constraints. More concretely, the above evaluation consists of two parts: the one before and the one after the assignment exerted its effect on the state of the definitions. Before we introduced assignments, we could replace a variable by its value or a function application by the function's body whenever we wished. Now, we must wait until we truly need the value of a variable before we perform the substitution. After all, definitions may change.
While some partial ordering is always a part of computation, the timing
constraints of set!
are new. By altering a definition, an
assignment ``destroys'' the current value. Unless the programmer carefully
plans the arrangement of assignments, such an action may be fatal. The
exercises illustrate the problem in more detail.
Exercise 35.2.1. Evaluate the following program by hand:
(define x 1) (define y 1) (begin (set! x (+ x 1)) (set! y (- y 1)) (* x y))
How many time periods can we distinguish in this hand-evaluation?
Compare this with the evaluation of
(define a 5) (* (+ a 1) (- a 1)))
Does the nesting imply an ordering among our calculations? Does the order of addition and subtraction matter? Solution
Exercise 35.2.2. Evaluate the following program by hand:
(define x 3) (define y 5) (begin (set! x y) (set! y x) (list x y))
How many time periods can we distinguish in this hand-evaluation?
Now evaluate the following:
(define x 3) (define y 5) (local ((define z x)) (begin (set! x y) (set! y z) (list x y)))
Is it true that the definition of x
contains the initial value of
y
and y
contains the initial value of x
after
the two set!-expressions are evaluated, no matter what the initial
values are?
Discuss what the two examples teach us about time and ``destruction of values'' in definitions. Solution
Exercise 35.2.3. Evaluate the following program by hand:
(define x 3) (define y 5) (begin (set! x y) (set! y (+ y 2)) (set! x 3) (list x y))
How many time intervals must we distinguish in this hand-evaluation? Solution
An assignment can also occur in a function body:
(define x 3) (define y 5) (define (swap-x-y x0 y0) (begin (set! x y0) (set! y x0))) (swap-x-y x y)
Here the function swap-x-y
consumes two values and performs two
set!
s.
Let us see how the evaluation works. Because (swap-x-y x y)
is a
function application, we need to evaluate the arguments, which are plain
variables here. So we replace the variables with their (current) values:
(define x 3) (define y 5) (define (swap-x-y x0 y0) (begin (set! x y0) (set! y x0))) (swap-x-y 3 5)
From here we proceed with the usual substitution rule for application:
(define x 3) (define y 5) (define (swap-x-y x0 y0) (begin (set! x y0) (set! y x0))) (begin (set! x 5) (set! y 3))
That is, the application is now replaced by an assignment of x
to
the current value of y
and of y
to the current value of
x
.
The next two steps are also the last ones and thus they accomplish what the name of the function suggests:
(define x 5) (define y 3) (define (swap-x-y x0 y0) (begin (set! x y0) (set! y x0))) (void)
The value of the application is invisible because the last expression evaluated was a set!-expression.
In summary, functions with set!
have results and effects. The
result may be invisible.
Exercise 35.3.1. Consider the following function definition:
(define (f x y) (begin (set! x y) y))
Is it syntactically legal or illegal? Solution
Exercise 35.3.2. Evaluate the following program by hand:
(define x 3) (define (increase-x) (begin (set! x (+ x 1)) x)) (increase-x) (increase-x) (increase-x)
What is the result? What is increase-x
's effect?
Solution
Exercise 35.3.3. Evaluate the following program by hand:
(define x 0) (define (switch-x) (begin (set! x (- x 1)) x)) (switch-x) (switch-x) (switch-x)
What is the result? What is switch-x
's effect?
Solution
Exercise 35.3.4. Evaluate the following program by hand:
(define x 0) (define y 1) (define (change-to-3 z) (begin (set! y 3) z)) (change-to-3 x)
What is the effect of change-to-3
? What is its
result?
Solution
Let's take a look at the definitions in figure 98. The function
add-to-address-book
consumes a symbol and a number. The former
represents a name, the latter a phone number. Its body contains a
set!-expression for address-book
, a variable define
d at
top-level. The function lookup
consumes an address book
and a name; its result is the matching phone number or false
, if
the name is not in address-book
.
Using lookup
, we can study the effect of the set!
expression in add-to-address-book
. Suppose we evaluate
(lookup 'Adam address-book)
with the given definitions:
(lookup 'Adam address-book) = (lookup 'Adam empty) = (cond [(empty? empty) false] [else ...]) = false
Because address-book
is empty
, we get false
, and
the calculation is straightforward.
Now let's evaluate the following in the Interactions window:
(begin (add-to-address-book 'Adam 1) (add-to-address-book 'Eve 2) (add-to-address-book 'Chris 6145384))
The first subexpression is a plain function application. So, the first step relies on the usual law of substitution:^{69}
(define address-book empty) (begin (set! address-book (cons (list 'Adam 1) address-book)) (add-to-address-book 'Eve 2) (add-to-address-book 'Chris 6145384))
The next expression to be evaluated is the set!-expression that is
nested in the begin-expressions, in particular its right-hand
side. The first argument to cons
is a value, but the second one is
still a variable whose current value is empty
. With this, we
can see what happens next:
(define address-book empty) (begin (set! address-book (cons (list 'Adam 1) empty)) (add-to-address-book 'Eve 2) (add-to-address-book 'Chris 6145384))
At this point we are ready to evaluate the set!-expression.
Specifically, we change the definition of address-book
so that the
variable now stands for (cons (list 'Adam 1) empty)
:
(define address-book (cons (list 'Adam 1) empty)) (begin (void) (add-to-address-book 'Eve 2) (add-to-address-book 'Chris 6145384))
The begin-expression throws away the invisible value.
Evaluating the remaining applications of add-to-address-book
yields
(define address-book (list (list 'Chris 6145384) (list 'Eve 2) (list 'Adam 1))) (void)
In short, the three applications turn address-book
into a list of
three pairs.
If we now evaluate (lookup 'Adam address-book)
in the
Interactions
window again, we get 1
:
(lookup 'Adam address-book) = (lookup 'Adam (list (list 'Chris 6145384) (list 'Eve 2) (list 'Adam 1)) = ... = 1
The comparison of this evaluation and the one at the beginning of the
section shows how set!
changes the meaning of
address-book
over time and how the two functions,
add-to-address-book
and lookup
, implement the services
that we discussed in section 34. The exercises show how
useful this collaboration of two functions is in the context of a graphical
user interface.
Exercise 35.4.1. The software for managing address books permits users to remove entries. Develop the function
;; remove : symbol -> void
(define (remove name) ...)
which changes address-book
so that all future lookup
s for
name
yield false
.
Solution
Exercise 35.4.2. The teachpack phone-book.ss implements a graphical user interface based on the model-view pattern discussed in section 22.3. Figure 95 shows what the graphical user interface offers:
a text-field for entering a name;
a text-field for displaying the search result and for entering a phone number;
a button for looking up the phone number for a name;
a button for adding a name and a phone number; and
a button for removing the phone number for a name.
Use the teachpack's connect
function to create a GUI for the
functions in this section and in exercise 35.4.1.
The function has the following contract, purpose, and header:
;;model-T = (button% control-event% -> true)
;;connect : model-T model-T model-T -> true
(define (connect lookup-cb change-cb remove-cb) ...)
That is, it consumes three model functions and wires them up with the GUI. The names of the parameters specify which call-back function goes with which button.
A model function may obtain the contents of the name field with
(name-control)
and the contents of the number field with
(number-field)
.
Solution
^{68} We have already encountered several kinds of effects: drawing to a canvas, changing the text field in a GUI, the creating of files by teachpacks, and so on. These effects aren't as complex as those of set! because they don't affect the program proper.
^{69} Because the calculation does not affect the function definitions, we do not include them in the calculation here. This convention saves space and time, but it should be used carefully.