Section 35

Assignment to Variables

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 defined, 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.

35.1  Simple Assignments at Work

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:

  1. (set! x 5)
    

  2. (define x 3)
    
    (set! (+ x 1) 5)
    

  3. (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

35.2  Sequencing Expression Evaluations

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

35.3  Assignments and Functions

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

35.4  A First Useful Example

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 defined 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.

(define address-book empty)

;; add-to-address-book : symbol number  ->  void
(define (add-to-address-book name phone)
  (set! address-book (cons (list name phone) address-book)))

;; lookup : symbol (listof (list symbol number))  ->  number or false
;; to lookup the phone number for name in ab
(define (lookup name ab)
  (cond
    [(empty? ab) false]
    [else (cond
	    [(symbol=? (first (first ab)) name)
	     (second (first ab))]
	    [else (lookup name (rest ab))])]))

Figure 98:  The basic address-book program

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 lookups 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:

  1. a text-field for entering a name;

  2. a text-field for displaying the search result and for entering a phone number;

  3. a button for looking up the phone number for a name;

  4. a button for adding a name and a phone number; and

  5. 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.