Section 40

Mutable Structures

Encapsulating and managing state variables is similar to forming and managing structures. When we first apply a function that abstracts over state variables we provide initial values for some of the variables. The service manager serves the (current) value of these variables, which is similar to extracting the values of fields in structures. Not surprisingly then, the technique can simulate the constructors and selectors of a define-struct definition. This simulation naturally suggests the introduction of functions that modify the value in a structure's field. The following subsections spell out the details behind this idea; the last subsection generalizes it to vectors.

40.1  Structures from Functions

(define-struct posn (x y))














     
(define (f-make-posn x0 y0)
  (local ((define x y0)
	  (define y y0)
	  (define (service-manager msg)
	    (cond
	      [(symbol=? msg 'x) x]
	      [(symbol=? msg 'y) y]
	      [else (error 'posn "...")])))
    service-manager))

(define (f-posn-x p)
  (p 'x))

(define (f-posn-y p)
  (p 'y))
Figure 116:  A functional analog of posn

Take a look at figure 116. The left-hand side is the one-line definition of a posn structure. The right-hand side is a functional definition that provides almost all the same services. In particular, the definition provides a constructor that consumes two values and constructs a compound value, and two selectors for extracting the values that went into the construction of a compound value.

To understand why f-make-posn is a constructor and why f-posn-x and f-posn-y are selectors, we can discuss how they work, and we can confirm that they validate the expected equations. Here we do both, because the definitions are unusual.

The definition of f-make-posn encapsulates two variable definitions and one function definition. The two variables stand for the arguments of f-make-posn and the function is a service manager; it produces the value of x when given 'x and the value of y when given 'y. In the preceding section, we might have written something like

(define a-posn (f-make-posn 3 4))

(+ (a-posn 'x) (a-posn 'y))

to define and to compute with f-make-posn. Since selecting values is such a frequent operation, figure 116 introduces the functions f-posn-x and f-posn-y, which perform these computations.

When we first introduced structures rigorously in intermezzo 1, we said that the selectors and constructors can be described with equations. For a definition such as that for posn, the two relevant equations are:

  (posn-x (make-posn V-1 V-2)) 
= V-1
and
  (posn-y (make-posn V-1 V-2)) 
= V-2

where V-1 and V-2 are arbitrary values.

To confirm that f-posn-x and f-make-posn are in the same relationship as posn-x and make-posn, we can validate that they satisfy the first equation:

  (f-posn-x (f-make-posn 3 4))
= (f-posn-x (local ((define x 3)
		    (define y 4)
		    (define (service-manager msg)
		      (cond
			[(symbol=? msg 'x) x]
			[(symbol=? msg 'y) y]
			[else (error 'posn "...")])))
	    service-manager))

= (f-posn-x service-manager)
  ;; add to top-level definitions:
  (define x 3)
  (define y 4)
  (define (service-manager msg)
    (cond
      [(symbol=? msg 'x) x]
      [(symbol=? msg 'y) y]
      [else (error 'posn "...")]))

= (service-manager 'x)

= (cond
    [(symbol=? 'x 'x) x]
    [(symbol=? 'x 'y) y]
    [else (error 'posn "...")])

= x

= 3

It is an exercise to show that f-posn-y and f-make-posn satisfy the analogous equation.

Exercise 40.1.1.   Which function does the simulation of structures not provide? Why not?    Solution

Exercise 40.1.2.   Here is yet another implementation of posn structures:

(define (ff-make-posn x y)
  (lambda (select)
    (select x y)))

(define (ff-posn-x a-ff-posn)
  (a-ff-posn (lambda (x y) x)))

(define (ff-posn-y a-ff-posn)
  (a-ff-posn (lambda (x y) y)))

Evaluate (ff-posn-x (ff-make-posn V-1 V2)) in this context. What does the calculation demonstrate?    Solution

Exercise 40.1.3.   Show how to implement the following structure definitions as functions:

  1. (define-struct movie (title producer))

  2. (define-struct boyfriend (name hair eyes phone))

  3. (define-struct cheerleader (name number))

  4. (define-struct CD (artist title price))

  5. (define-struct sweater (material size producer))

Pick one and demonstrate that the expected laws hold.    Solution

40.2  Mutable Functional Structures

(define (fm-make-posn x0 y0)
  (local ((define x y0)
	  (define y y0)
	  (define (service-manager msg)
	    (cond
	      [(symbol=? msg 'x) x]
	      [(symbol=? msg 'y) y]
	      [(symbol=? msg 'set-x) (lambda (x-new) (set! x x-new))]
	      [(symbol=? msg 'set-y) (lambda (y-new) (set! y y-new))]
	      [else (error 'posn "...")])))
    service-manager))

(define (fm-posn-x p)
  (p 'x))

(define (fm-posn-y p)
  (p 'y))

(define (fm-set-posn-x! p new-value)
  ((p 'set-x) new-value))

(define (fm-set-posn-y! p new-value)
  ((p 'set-y) new-value))

Figure 117:  An implementation of posns with mutators

Together, sections 39 and 40.1 suggest that structures are mutable. That is, we should be able to change the values of some field in a structure. After all, we introduced the service managers in section 39 to hide state variables, not just ordinary variable definitions.

Figure 117 shows how a small change to the definitions of figure 116 turns the locally hidden variables into state variables. The modified service manager offers two services per state variable: one for looking up the current value and one for changing it.

Consider the following definition and expression:

(define a-posn (fm-make-posn 3 4))

(begin
  (fm-set-posn-x! a-posn 5)
  (+ (posn-x a-posn) 8))

Evaluating them by hand shows how structures change. Here is the first step:

  ... 
= 
  (define x-for-a-posn 3)
  (define y-for-a-posn 4)
  (define (service-manager-for-a-posn msg)
    (cond
      [(symbol=? msg 'x) x-for-a-posn]
      [(symbol=? msg 'y) y-for-a-posn]
      [(symbol=? msg 'set-x)
       (lambda (x-new) (set! x-for-a-posn x-new))]
      [(symbol=? msg 'set-y)
       (lambda (y-new) (set! y-for-a-posn y-new))]
      [else (error 'posn "...")]))
  (define a-posn service-manager-for-a-posn)

  (begin
    (fm-set-posn-x! a-posn 5)
    (+ (posn-x a-posn) 8))

It renames and lifts the local definitions from inside of fm-make-posn. Because the function definition doesn't change for the rest of the evaluation, we focus on just the variable definitions:

  (define x-for-a-posn 3)
  (define y-for-a-posn 4)
  (begin
    (fm-set-posn-x! a-posn 5)
    (+ (posn-x a-posn) 8))

= (define x-for-a-posn 3)
  (define y-for-a-posn 4)
  (begin
    (fm-set-posn-x! service-manager-for-a-posn 5)
    (+ (posn-x a-posn) 8))

= (define x-for-a-posn 3)
  (define y-for-a-posn 4)
  (begin
    ((service-manager-for-a-posn 'set-x) 5)
    (+ (posn-x a-posn) 8))

= (define x-for-a-posn 3)
  (define y-for-a-posn 4)
  (begin
    (set! x-for-a-posn 5)
    (+ (posn-x a-posn) 8))

= (define x-for-a-posn 5)
  (define y-for-a-posn 4)
  (+ (posn-x a-posn) 8) 

At this point, the definition of x-for-a-posn has been modified in the expected manner. From now on every reference to this state variable, which represents the (simulated) x field a-posn, stands for 5. Every further reference to x-for-a-posn produces 5.

Exercise 40.2.1.   Develop a functional representation for the following structure definition:

(define-struct boyfriend (name hair eyes phone))

such that the fields of the simulated structure can be changed.    Solution

Exercise 40.2.2.   Here is a modification of the function-based implementation of posn structures in exercise 40.1.2:

(define (ffm-make-posn x0 y0)
  (local ((define x x0)
	  (define (set-x new-x) (set! x new-x))
	  (define y y0)
	  (define (set-y new-y) (set! y new-y)))
  (lambda (select)
    (select x y set-x set-y))))

(define (ffm-posn-x a-ffm-posn)
  (a-ffm-posn (lambda (x y sx sy) x)))

(define (ffm-posn-y a-ffm-posn)
  (a-ffm-posn (lambda (x y sx sy) y)))

(define (ffm-set-posn-x! a-ffm-posn new-value)
  (a-ffm-posn (lambda (x y sx sy) (sx new-value))))

(define (ffm-set-posn-y! a-ffm-posn new-value)
  (a-ffm-posn (lambda (x y sx sy) (sy new-value))))

Demonstrate how to modify a structure like (ffm-make-posn 3 4) so that its y field contains 5.    Solution

40.3  Mutable Structures

Scheme structures are mutable. In Advanced Student Scheme, a structure definition such as

(define-struct posn (x y))

introduces six primitives, not just four:

  1. make-posn, the constructor;

  2. posn-x and posn-y, the selectors;

  3. posn?, the predicate; and

  4. set-posn-x! and set-posn-y!, the MUTATORS.

The mutators are operations that change the contents of a structure.

Recall that we think of a structure as a box with compartments. For example, the structure

(make-posn 3 4)

should be visualized as a box with two compartments:

[curriculum7-Z-G-1.gif]
A constructor creates a box; a selector extracts the value from a particular compartment; the predicate recognizes it; and the mutator changes the content of a compartment. That is, a mutator has an effect on its arguments; its result is the invisible value. Pictorially, we should imagine an evaluation step for an expression such as
(define p (make-posn 3 4))
(set-posn-x! p 5)

as a box with the old x value deleted and a new one inserted into the same box:

[curriculum7-Z-G-2.gif]

Consider the following definitions:

(define-struct star (name instrument))

(define p (make-star 'PhilCollins 'drums))

Let's consider the effect and computation of the following expression:

(begin
  (set-star-instrument! p 'vocals)
  (list (star-instrument p)))

According to our explanation, the first subexpression modifies the instrument field of the star structure named p; the second one produces a list of one item, the current value the instrument field in the structure named p. By analogy to section 40.2, the evaluation proceeds as follows:

  (define-struct star (name instrument))
  (define p (make-star 'PhilCollins 'drums))
  (begin
    (set-star-instrument! p 'vocals)
    (list (star-instrument p)))

= (define-struct star (name instrument))
  (define p (make-star 'PhilCollins 'vocals))
  (begin
    (void)
    (list (star-instrument p)))

= (define-struct star (name instrument))
  (define p (make-star 'PhilCollins 'vocals))
  (list 'vocals)

The first step changes one part of the value in the definition of p, but not the entire value. The second one extracts the current value of the instrument field and places it in a list.

The introduction of mutators for structures requires two changes to our system of evaluation rules:

  1. Every constructor expression adds a definition with a new, unique name to the top level, unless it already occurs in a definition.75

  2. A name that stands for a structure is a value.

We can understand these changes if we think of each structure as a function that manages services such as looking up the current value of a field and modifying the field. After all, local function definitions also create top-level definitions with unique names. And the names of functions are values, too.

Using these two new rules we can study the unusual behavior of mutators in more depth. Here is a first example:

(define-struct star (name instrument))

(define p (make-star 'PhilCollins 'drums))

(define q p)

(begin
  (set-star-instrument! p 'vocals)
  (list (star-instrument q)))

It differs from the first in two ways. First, it defines q to be p. Second, the second subexpression of the begin-expression refers to q, not p. Let's check our understanding of the evaluation process:

  (define-struct star (name instrument))
  (define p (make-star 'PhilCollins 'drums))
  (define q p)
  (begin
    (set-star-instrument! p 'vocals)
    (list (star-instrument q)))

= (define-struct star (name instrument))
  (define p (make-star 'PhilCollins 'vocals))
  (define q p)
  (begin
    (void)
    (list (star-instrument q)))

As before, the first step changes one part of the definition of p. The second step is to look up q's current value:

   ...
= (define-struct star (name instrument))
  (define p (make-star 'PhilCollins 'vocals))
  (define q p)
  (list (star-instrument p))

= (define-struct star (name instrument))
  (define p (make-star 'PhilCollins 'vocals))
  (define q p)
  (list 'vocals)

Because q is p and the current value of the instrument field of p instrument is 'vocals, the result is again (list 'vocals).

What we have just seen is the effect of SHARING (the effects of mutators), which means that a modification of a struture affects the program in more than one place. Sharing is also visible inside lists as our second example shows:

(define-struct star (name instrument))

(define q (list (make-star 'PhilCollins 'drums)))

(begin
  (set-star-instrument! (first q) 'vocals)
  (list (star-instrument (first q))))

Here, the right-hand side of the definition of q is an expression whose only subexpression isn't a value. More precisely, it is a structure expression that must be evaluated:

  ... 
= (define-struct star (name instrument))
  (define p (make-star 'PhilCollins 'drums))
  (define q (list p))
  (begin
    (set-star-instrument! (first q) 'vocals)
    (list (star-instrument (first q))))

= (define-struct star (name instrument))
  (define p (make-star 'PhilCollins 'drums))
  (define q (list p))
  (begin
    (set-star-instrument! p 'vocals)
    (list (star-instrument (first q))))

Thus the first step is to introduce a new definition, for which we choose p as the name. The second step replaces (first q) by p, because q is a list of one item: p. The rest proceeds almost as above:

... 
= (define-struct star (name instrument))
  (define p (make-star 'PhilCollins 'vocals))
  (define q (list p))
  (begin
    (void)
    (list (star-instrument (first q))))

= (define-struct star (name instrument))
  (define p (make-star 'PhilCollins 'vocals))
  (define q (list p))
  (list (star-instrument p))

= (define-struct star (name instrument))
  (define p (make-star 'PhilCollins 'vocals))
  (define q (list p))
  (list 'vocals)

Finally, effects can be shared among items in different lists. Take a look at this third variant of our program:

(define-struct star (name instrument))

(define q (list (make-star 'PhilCollins 'drums)))

(define r (list (first q) (star-instrument (first q))))

(begin
  (set-star-instrument! (first q) 'vocals)
  (list (star-instrument (first r))))

The new definition introduces the variable r, which stands for a list that contains two items. Let's use our new rules to determine the values and the effects of this program:

... 
= (define-struct star (name instrument))
  (define p (make-star 'PhilCollins 'drums))
  (define q (list p))
  (define r (list (first q) (star-instrument (first q))))
  (begin
    (set-star-instrument! (first q) 'vocals)
    (list (star-instrument (first r))))

= (define-struct star (name instrument))
  (define p (make-star 'PhilCollins 'drums))
  (define q (list p))
  (define r (list p (star-instrument p)))
  (begin
    (set-star-instrument! (first q) 'vocals)
    (list (star-instrument (first r))))

= (define-struct star (name instrument))
  (define p (make-star 'PhilCollins 'drums))
  (define q (list p))
  (define r (list p 'drums))
  (begin
    (set-star-instrument! (first q) 'vocals)
    (list (star-instrument (first r))))

As above, the first step introduces a definition for the new star structure. The second and third step create the list named r, which contains p, the newly created structure, and 'vocals, its current instrument value.

The next step selects the first item from q and modifies its instrument field:

... 
= (define-struct star (name instrument))
  (define p (make-star 'PhilCollins 'vocals))
  (define q (list p))
  (define r (list p 'drums))
  (begin
    (void)
    (list (star-instrument (first r))))

= (define-struct star (name instrument))
  (define p (make-star 'PhilCollins 'vocals))
  (define q (list p))
  (define r (list p 'drums))
  (list (star-instrument p))

= (define-struct star (name instrument))
  (define p (make-star 'PhilCollins 'vocals))
  (define q (list p))
  (define r (list p 'drums))
  (list 'vocals)

Because r contains p as the first item and because the instrument field of p is still 'vocals, the result is (list 'vocals) here, too. But, this program still has some knowledge of 'drums, the original value of the star structure.

In summary, mutators give us more power than constructors and selectors. Instead of just creating new structures and revealing their contents, we can now change their contents, while the structures remain the same. Next we must contemplate what this means for the design of programs.

Exercise 40.3.1.   Name the mutators that the following structure definitions introduce:

  1. (define-struct movie (title producer))

  2. (define-struct boyfriend (name hair eyes phone))

  3. (define-struct cheerleader (name number))

  4. (define-struct CD (artist title price))

  5. (define-struct sweater (material size producer))    Solution

Exercise 40.3.2.   Develop the function swap-posn, which consumes a posn structure and swaps the values in the two fields. Its result is (void).    Solution

Exercise 40.3.3.   Develop the function one-more-date, which consumes a girlfriends structure and increases the contents of the number-past-dates field by 1. The structure definition is

(define-struct girlfriends (name hair eyes number-past-dates))

The result of one-more-date is (void).    Solution

Exercise 40.3.4.   Evaluate the following program, step by step:

(define-struct cheerleader (name number))

(define A (make-cheerleader 'JoAnn 2))

(define B (make-cheerleader 'Belle 1))

(define C (make-cheerleader 'Krissy 1)) 

(define all (list A B C))

(list
  (cheerleader-number (second all))
  (begin
    (set-cheerleader-number! (second all) 17)
    (cheerleader-number (second all))))

Underline in the last program where definitions differ from the initial program.     Solution

Exercise 40.3.5.   Evaluate the following program:

(define-struct CD (artist title price))

(define in-stock
  (list
    ((make-CD 'R.E.M "New Adventures in Hi-fi" 0)
     (make-CD 'France "simple je" 0)
     (make-CD 'Cranberries "no need to argue" 0))))

(begin
  (set-CD-price! (first in-stock) 12)
  (set-CD-price! (second in-stock) 19)
  (set-CD-price! (third in-stock) 11)
  (+ (CD-price (first in-stock))
     (CD-price (second in-stock))
     (CD-price (third in-stock))))

Show every step.    Solution

40.4  Mutable Vectors

Recall from intermezzo 29 that vectors, like structures, are compound values. To extract a value from a structure, programs use selector operations. To extract a value from a vector, programs use natural numbers as indices. Hence functions that process vectors defer to auxiliary functions that process vectors and natural numbers.

Not surprisingly, vectors, like structures, are mutable compound values. The only mutator for vectors is vector-set!, a function that consumes a vector, an index, and a value. Thus, for example, the following program evaluates to 'blank:

(define X (vector 'a 'b 'c 'd))

(begin
  (vector-set! X 0 'blank)
  (vector-set! X 1 'blank)
  (vector-set! X 2 'blank)
  (vector-set! X 3 'blank)
  (vector-ref X 2))

The four vector-set! expressions change X so that all of its four fields contain 'blank. The last expression extracts the value of one of the fields.

In general, an evaluation concerning mutable vectors proceeds just like an evaluation for mutable structures. In particular, a vector expression introduces a new definition:

  (list (vector 1 2 3))
= (list v)
  ;; add to top-level definitions: 
  (define v (vector 1 2 3))

The variable name v is new and unique. Similarly, a vector-set! expression modifies a part of a vector definition:

  (set-vector! (vector 1 2 3) 0 'a)

= (define v (vector 1 2 3))
  (set-vector! v 0 'a)

= (define v (vector 'a 2 3))
  (void)

Finally, effects to vectors are shared just like effects to structures.

Exercise 40.4.1.   Evaluate the following program:

(define X (vector 0 0 0 0))

(define Y X)

(begin
  (vector-set! X 0 2)
  (vector-set! Y 1 (+ (vector-ref Y 0) (vector-ref Y 1)))
  (vector-ref Y 1))

Show all steps.    Solution

Exercise 40.4.2.   Develop the function clear, which consumes a vector with three slots and sets them to 0.    Solution

Exercise 40.4.3.   Develop the function swap, which consumes a vector with two slots and swaps the values in these slots.    Solution

Exercise 40.4.4.   Extend the board representation of exercise 29.3.14 with the function

;; board-flip! : board N N  ->  boolean
;; to negate the board position with indices i, j on a-board
(define (board-flip! a-board i j) ...)

Don't forget to develop examples and tests for the function.    Solution

40.5  Changing Variables, Changing Structures

Structure mutators and set!-expressions are related. Indeed, in section 40.2 we explained the effects of the first with the second. Still, there are also important differences that a programmer must understand. Let's start with the syntax:

(set! <variable> <expression>)
     
 set-<structure-tag>-<field>!
A set!-expression is an expression that consists of two pieces: a variable and an expression. The variable is fixed; it is never evaluated. The expression is evaluated. In contrast, a structure mutator is a function. As such, it is a value that the program can apply (to two arguments), pass to other functions, store inside of structures, and so on. Structure mutators are created in response to structure definitions, just as structure constructors and selectors.

Next we must consider lexical scope issues (see section 18.2). A set!-expression contains a variable. For the set!-expression to be valid, this variable must be bound. The connection between a set!-expression's variable and its binding occurrence is static and can never be changed.

The scope of a mutator is that of its corresponding define-struct. Thus, in the following program

(define-struct aaa (xx yy))

(define UNIQUE 
  (local ((define-struct aaa (xx yy)))
    (make-aaa 'my 'world)))

...

the underlined occurrence of define-struct has a limited lexical scope, and its scope is a hole in the scope of the top-level define-struct. A result of this scoping is that the mutator for the top-level define-struct cannot mutate the structure called UNIQUE. The two mutators are unrelated functions that coincidentally have the same name; the rules for the evaluation of local-expression dictate that we rename one consistently.

To highlight the differences in syntax and lexical scope, take a look at the following two, apparently similar programs:

(define the-point (make-posn 3 4))

(set! x 17)
     
(define the-point (make-posn 3 4))

(set-posn-x! the-point 17)
The one on the left is illegal, because the x in the set!-expression is an unbound variable. The program on the right is perfectly legal; it refers to the field x of a posn structure.

The largest difference between set!-expressions and mutators concerns their semantics. Let's study two examples to understand the differences once and for all. The first illustrates how similar looking expressions evaluate in a radically different manner:

(define the-point (make-posn 3 4))

(set! the-point 17)
     
(define the-point (make-posn 3 4))

(set-posn-x! the-point 17)
The program on the left consists of a definition for the-point and an assignment to the-point; the one on the right starts with the same definition for the-point followed by an application of the mutator. The evaluation of both affects the variable definition but in different ways:
(define the-point 17)

(void)
     
(define the-point (make-posn 17 4))

(void)
On the left, the-point now stands for a number; on the right, it is still a posn structure but with a new value in the x-field. More generally, a set!-expression changes the value on the right-hand side of a definition, and the application of a mutator changes the value of just one field in a structure that occurs on the right-hand side of a definition.

The second example shows how an application of mutator evaluates the arguments, which is not the case for set!-expressions:

(define the-point (make-posn 3 4))
(define an-other (make-posn 12 5))

(set! (cond
	[(zero? (point-x the-point))
	 the-point]
	[else
	 an-other])
      1)

     
(define the-point (make-posn 3 4))
(define an-other (make-posn 12 5))

(set-posn-x!
  (cond
    [(zero? (point-x the-point))
     the-point]
    [else
     an-other])
  1)
Whereas the program on the left is illegal, because a set!-expression must contain a fixed variable in the second position, the one on the right is legitimate. The evaluation of the program on the right changes the x-field in an-other to 1.

Finally, mutators are values, which means a function can consume a mutator and apply it:

;; set-to-2 : S-mutator S-structure  ->  void
;; to change a field in s to 2 via mutator
(define (set-to-2 mutator s)
  (mutator s 2))

(define-struct bbb (zz ww))

(local ((define s (make-posn 3 4))
	(define t (make-bbb 'a 'b)))
  (begin
    (set-to-2 set-posn-x! s)
    (set-to-2 set-bbb-ww! t)))

The function set-to-2 consumes a mutator and a structure that the mutator can modify. The program uses it to change the x-field in a posn structure and the ww-field in a bbb structure. In contrast, if we were to apply a function to a set!-expression, it would receive (void) and nothing else.

Mixing set! and Structure Mutators: When a program uses both set!-expressions and structure mutators, our evaluation rules fail for some cases. Specifically, they don't explain sharing properly. Consider this program fragment:

(define the-point (make-posn 3 4))

(define another-point the-point)

(begin
  (set! the-point 17)
  (= (posn-x another-point) 3))

According to our rules, the two definitions refer to the same structure. The second one does so by indirection. The set!-expression changes what the-point stands for, but it shouldn't affect the second definition. In particular, the program should produce true. If we were to use our rules in a naive manner, we would not be able to validate this point.

A proper explanation of structures must introduce a new definition for every application of a structure constructor, including those on the right-hand side of definitions in the original program. We will place the new definitions at the beginning of the sequence of definitions. Furthermore, the variable in the new definition must be unique so that it cannot occur in a set!-expression. We will use variables such as struct-1, struct-2, and so on, and agree to use them for this purpose only. These names, and only these names, are values.

Using the minor changes to our rules, we can evaluate the program fragment properly:

  (define the-point (make-posn 3 4))

  (define another-point the-point)

  (begin
    (set! the-point 17)
    (= (posn-x another-point) 3))

= (define struct-1 (make-posn 3 4))
  (define the-point struct-1)
  ;; evaluate from here:
  (define another-point the-point)
  (begin
    (set! the-point 17)
    (= (posn-x another-point) 3))

= (define struct-1 (make-posn 3 4))
  (define the-point struct-1)
  (define another-point struct-1)
  ;; evaluate from here:
  (begin
    (set! the-point 17)
    (= (posn-x another-point) 3))

At this point, the structure is created, and both of the original variables refer to the new structure. The rest of the evaluation changes the definition of the-point but not another-point:

  ...
= (define struct-1 (make-posn 3 4))
  (define the-point 17)
  (define another-point struct-1)
  ;; evaluate from here:
  (begin
    (void)
    (= (posn-x another-point) 3))

= (define struct-1 (make-posn 3 4))
  (define the-point 17)
  (define another-point struct-1)
  ;; evaluate from here:
  (= (posn-x another-point) 3)

= (define struct-1 (make-posn 3 4))
  (define the-point 17)
  (define another-point struct-1)
  ;; evaluate from here:
  (= 3 3)

The final result is true, as expected.

The modified evaluation rules are a bit more cumbersome than the old ones. But they fully explain the difference between the effects of set!-expressions and those of structure mutation, which, for programming in modern languages, is an essential concept. 


75 For simplicity, we use this simple approximation. When a program also uses set!-expression, we must rely on the refinement of intermezzo 8.3 to understand its behavior completely. Also see the note on this topic on page 50.