Comp210: Principles of Computing and Programming
Fall 2004 -- Lecture #30   


The Exam 2 Solution has been posted.

Changing the World: mutating structures

A structure, for today's examples:

(define-struct cat (name age high?))

;; a cat is:

;;  (make-cat <symbol> <num> <boolean>)

;; where age is in cat-years (about 12/7 months),

;; and high? indicates whether the cat is high on catnip.



; Examples:

(define hyde (make-cat 'bartok 21 true))

(define cat2 (make-cat 'freddy-kruger 83 false))



;; sneak: cat --> cat

;; Returns a new cat, which is like a-cat, except that

;; it is exposed to catnip (regardless of whether a-cat was).

;;

(define (sneak a-cat)

  (make-cat (cat-name a-cat) (cat-age a-cat) true))



(sneak hyde) = hyde

(sneak cat2) = (make-cat 'freddy-kruger 83 true)

The way the world works is not that sneaking into the toy closet destroys the old cat and creates a new cat with many similar attributes. (This is perhaps a very Zen way of looking at things: "the cat you once had is no more; there is only the cat of the current moment, playfully ripping apart your ankles.")

Really, we think of it being the same cat, only that it has been modified over time. This has been a major exception to our motto "your program mirrors your thinking of the problem". We now introduce a way to really model this after all (but our world is going to get more complicated; we deferred until now to explore the richness of the more limited world, before moving on):

hyde

(set-cat-high?! hyde true)

hyde

(set-cat-name!  hyde 'Pumar-Lord-Of-The-Urban-Jungle)

hyde

(set-struct-field! a-struct-reference a-new-value)

Sets the specified field of the given instance of the specified struct, referenced by a-struct-reference, to the given value (a-new-value).

Example:

(define-struct Person (name age)) ;; define a Person struct

(define SW (make-Person "Stephen" 43)) ;; create a reference to an instance of a Person struct.

(Person-name SW) ;; returns "Stephen" as expected.

(set-Person-name! SW "swong") ;; set the value of the name field in the Person instance referenced by SW to "swong"

(Person-name SW) ;; returns "swong" now.

set-struct-field! has no return value. It only causes the mutation of the referenced structure, i..e. it works purely by "side effect".

 

Write a function grow-cat!, which takes in a cat, and returns the same cat one (cat)year older.
Go back and modify your function so that after growing, the kitty is no longer high.
Write grow-cats!, which takes in a list of cats.

If Elizabeth Windsor dyes her hair, what happens to the queen of england? She has purple hair too!
(define hyde   (make-cat 'bartok 21 true))

(define jekyll hyde)

(set-cat-age! hyde 22)

jekyll ; A cat w/ age 22.

jekyll and hyde are two different placeholders that represent the same underlying structure. (One student that changing jekyll wouldn't change hyde, because of the order defined, but that isn't so:)
(set-cat-age! jekyll 23)

hyde   ; A cat w/ age 23.

Both placeholders really do both refer to the same. What is the relation between hyde and some other cat named 'bartok, 22 cat-years, who is high -- is that the same underlying cat, or is it an entirely different cat that just happens to look alike on the outside? (Hopefully the latter, since that better reflects the real world.)

How, exactly, does this work? The law-of-scheme as we know it doesn't explain this. Let's attempt the law-of-scheme as it stands. Think about what happened at each step. From the top:

  

  ; An incorrect hand-evaluation:



  (define hyde   (make-cat 'bartok 21 true))

  (define jekyll hyde) 

  ; The placeholder hyde gets replaced with its defined value:

= (define jekyll (make-cat 'bartok 21 true))



  (set-cat-age! hyde 22)

= (set-cat-age! (make-cat 'bartok 21 true) 22)

= ; ?? Nothing returned; we expected?:  (make-cat 'bartok 22 true)



  jekyll ; Law of scheme for placheholder says 

         ; to look up its define'd value, which was (make-cat 'bartok 21 true)

= (make-cat 'bartok 22 true)   ; Uh-oh, not its defined value!





  ; Furthermore, we have another mystery:

  (set-cat-age! jekyll 11)

= (set-cat-age! (make-cat 'bartok 22 true) 11)  ; Evaluate placeholder "jekyll"

= ; ?? Nothing returned; we expected?:  (make-cat 'bartok 11 true)



  hyde ; Again, last we saw, hyde stood for a cat of age 10, but:

= (make-cat 'bartok 11 true)    mysterious!

Clearly the law of scheme as we understood it, doesn't account for what we're seeing. What exactly is the mechanism, so that both refer to the same underlying cat?

Here is the truth: Whenever you call make-cat (or any make-[struct] -- including cons, or (make-, build-)vector) what gets returned is not a box with those entries, but actually a reference to a box with those entries. A reference is a new type of value. We'll indicate references with arrows (on the chalk board), or with hat-variables (in text):

  (define hyde (make-cat 'bartok 10 true))

= (define hyde hyde^)

where hyde^ is a value; that is hyde^ is a valid thing for a function to return. make-cat actually did two things: it (a) created a cat (a box with three drawers named "age", ...), (b) returned a reference to that box. We'll draw this on the board as:
             Cat:

             +==================+

             # name:  'bartok   #

             #        ~~~~~~~~  #

             # age:     22      #

             #        ~~~~~~~   #

             # high?:   true    #

             #        ~~~~~~~   #

             +==================+

                ^

                |

(define hyde ---/ )

Well, it gets unwieldy to draw those boxes, so we'll write the box as (make-cat 'bartok 22 true), as ever. And rather than repeatedly drawing arrows which point off to the box, we'll write (define hyde^ (make-cat 'bartok 22 true)). Thus we'll write the entire above picture as:
(define hyde^ (make-cat 'bartok 22 true))   ; hyde^ is a signpost to this make-cat

(define hyde hyde^)  ; The value of hyde is a signpost to that make-cat.

In a nutshell: Reference values are an abstract concept; to depict them we'll choose either of:

The behavior of make-struct:

The behavior of define:

 

This mechanism now explains the behavior we saw earlier, where hyde and jekyll were clearly the same underlying cat (we just had two different wasys of referring to it).

Example: a hand-evaluation

  (define liz (make-person 'elizabeth-windsor 'grey))

  (define queen liz)

  (set-person-haircolor! liz 'blue)

  (person-haircolor queen)

When we evaluated the following in lecture, we had a series of pictures (about six of them -- but i kept drawing on top of the previous picture). Here is the full hand-evaluation, where each "picture" is instead a list of define's and expressions. We won't harp on these full hand-evaluations (which now consist not of a single expression, but a list of several defines/expressions), but you should be clear on what's happening, and how it still follows the law-of-scheme.
We highlight the (sub)expression about to be evaluated next.
  ; We're about to start evaluating all this:

  (define liz (make-person 'elizabeth-windsor 'grey))

  (define queen liz)

  (set-person-haircolor! liz 'blue)

  (person-haircolor queen)



= (define liz^ (make-person 'elizabeth-windsor 'grey))

  (define liz liz^)

    ; We just evaluated the first "define liz..." above.

  (define queen liz)

  (set-person-haircolor! liz 'blue)

  (person-haircolor queen)





= (define liz^ (make-person 'elizabeth-windsor 'grey))

  (define liz liz^)

  (define queen liz^)

    ; It was easy to define queen -- just look up value of placeholder "liz",

    ; which is liz^.

  (set-person-haircolor! liz 'blue)

  (person-haircolor queen)



= (define liz^ (make-person 'elizabeth-windsor 'grey))

  (define liz liz^)

  (define queen liz^)

  (set-person-haircolor! liz^ 'blue) ; Again look up value of "liz", which is

                                     ; the reference "liz^"

  (person-haircolor queen)



= (define liz^ (make-person 'elizabeth-windsor 'blue))

  (define liz liz^)

  (define queen liz^)

  ; set-person-haircolor! returned nothing, but note the above side effect.

  (person-haircolor queen)



= (define liz^ (make-person 'elizabeth-windsor 'blue))

  (define liz liz^)

  (define queen liz^)

  (person-haircolor liz^)  ; Evaluating placeholder "queen" easily gave "liz^"



= (define liz^ (make-person 'elizabeth-windsor 'blue))

  (define liz liz^)

  (define queen liz^)

  'blue   ; Easy to evaluate person-haircolor -- just follow the

          ; reference, and peek inside that structure.

Note that each step was very small -- just evaluating a placeholder (which means finding what it was defined as, same as it always has been), or making/setting/accessing a structure.

This bit about using references to structures, rather than immediate structures, may seem like a bit of superficial syntactic wordplay, but it makes a huge difference. What if we'd said:?

  (define liz (make-person ...))

  (define queen-of-england (make-person ...))

  (set-person-haircolor! liz 'purple)

Note that references are created whenever you make a structure, even if you don't associate a placeholder with it. In fact, it's an easy observation: there is exactly one unique structure created, for every call to make-structure.

Here is another (slightly shorter) review of law-of-scheme, aimed at labbies.

The code for the above section can be found here: setstruct.scm

More of today's code which includes some exercises.

Above, we introduced a more complicated language: we added the notion of a reference to structure, which was itself a value. This was only helpful because we also introduced functions which modify structures: set-[struct]-[field]!. This led to the question "if we make a structure named A, and then define B to be A, and we then modify one of these -- does the other change? (Answer: Yes.) What if we had some other structure that had all the same fields as A -- does that change? (Answer: no.)

This entailed some revisions: make-[struct] actually returns not a structure, but one of these new references -- like a signpost that refers to the actual structure (box with three elements). By having several placeholders that all have a signpost referring to the same object, we can have our program model things the way we think of the world.


Can we write a function, to tell if cats A and B look alike to all external appearances? (Same name, same age, same metabolic status.) Sure.
Even if we modified the cat structure to contain a list of other cats (its pedigree, perhaps) -- that is, do each the cats in A's pedigree should look like the ones in B's pedigree. Sure -- this is reminiscent of xexpr=?. And it is exactly what the built-in equal? does: if each is a simple type (number, symbol, ...) we call the appropriate comparer; if it's a structure (e.g. a cons structure), you recursively call equal? on each part.

However: can we write a function which tells if two cats are identically the same? (To ponder: how to write such a function.) There is a built-in function eq? which does this.

(equal? hyde (make-cat 'bartok 9 true))  = true

(equal? hyde jekyll)                     = true

(eq?    hyde (make-cat 'bartok 9 true))  = false

(eq?    hyde jekyll)                     = true

Not that the name is important, but: things that are merely equal? we call "extensionally" equal (since: if you take either value and "extend" it (compute some result based on its value), you get the same answer).
Things that "identically equal" (a confusing term) we sometimes call "intensionally equal" (note the spelling).

Two references to structures that are "equal?" are not necessarily "eq?". Two references to structures that are "eq?" are always "equal?".

What about structures within structures? Recall from early lectures:

(define-struct brand (type speed seats service))

;;

;; A brand is a structure

;;   (make-brand symbol num num num)

;; where type is the model/make, speed is the cruising speed in mph, 

;; ...



; Examples of the data:

;

(define dc10 (make-brand  'DC-10 550 282 15000))

; ..









(define-struct plane (brand miles mechanic))



;; A plane is a structure

;;    (make-plane brand num symbol)

;; where brand is a brand structure,  miles is ...



; Examples of the data:

;

(define p1 (make-plane dc10  1000 'Joe))

(define p2 (make-plane dc10  500 'Jane))

(define p2 (make-plane (make-brand 'DC-10 550 282 15000)  500 'Jane))

Draw pictures of what's going on here. What if we modify the brand for DC-10?

Recall that lists are just structures.

(define-struct cat (name age high?))

;; a cat is:

;;  (make-cat <symbol> <num> <boolean>)

;; where age is in months.



; Examples:

(define hyde (make-cat 'bartok 9 true))

(define cat2 (make-cat 'freddy-kruger 83 false))



(define clinic-patients (list hyde cat2))



clinic-patients

(set-cat-age! hyde (add1 (cat-age hyde)))

clinic-patients

What does the picture look like, for this?
To do: What is the picture associated with (cons 3 (cons 4 empty))? (Remember that cons should've been called make-cons, to be consistent with other structure constructors.)

What about a structure containing itself -- is this possible? Well, with references, a structure may certainly refer to itself. (May or may not be what we actually intend!)

(define-struct cat (name age high? bestbuddy))

(define hyde (make-cat 'bartok 9 true false))

   ; Hmmm, we don't even have a candidate for hyde's bestbuddy yet.

   ; Use a dummy value, for now.

(define cat2 (make-cat 'freddy-kruger 83 false hyde))



hyde

cat2



(set-cat-bestbuddy! hyde hyde)

;(set-cat-bestbuddy! hyde cat2)

Note that a structure which contains (a reference to) itself might take a long time to difficult to print out. DrScheme takes a bit of care to detect such cycles, and may use shared to print things out.

(shared [(-0- (make-kat 'bartok 

                         9

                         true

                         (make-kat 'freddy-kruger 83 false -0-)))]

    -0-)

"Shared" is much like "local": a declaration of placeholders (here, -0-), and then a single expression. By using those placeholders, it can print values more concisely (at the expense of having placeholders as part of a "value" -- we'd never seen that before, nor needed it). Unlike local, in shared you can define something in terms of itself immediately, for creating circular structures. (Note: shared is an input form, as well as an output form, just like everything else in DrScheme.) We won't make a big deal out of shared -- we'll just use local followed by set-[struct]-[field]!. But don't be intimidated if DrScheme gives back an answer explicitly showing shared data.

 

(Written mostly by Ian Barland)


Last Revised Tuesday, 24-Aug-2004 13:49:04 CDT

©2004 Stephen Wong and Dung Nguyen