Comp 210 Lab 10: Side-effects

Side-effects, Side-effects on structures, set!, Input and output, Images


Side-effects

Until this week, with most of the Scheme expressions we'd seen, we've only been interested in their result -- a given expression always evaluated to the same thing, and it didn't have any effect on the evaluation of a different expression.

Q: What has been the one exception to this -- an expression that does affect how other expressions evaluate?

Expressions that do change, or effect, the overall state of the world, including the behavior of other expressions, are said to have side-effects.

Today's lab discusses some expressions, other than (define ...), that have side-effects.


Side-effects on structures

So let's make some sample structures:

   (define-struct person (birthyear haircolor annoying activity))

   (define mary-kate-olsen (make-person 1986 'blonde #t 'smiling))
   (define ashley-olsen    (make-person 1986 'blonde #t 'smiling))

   (define michelle-tanner1 mary-kate-olsen)
   (define michelle-tanner2 ashley-olsen)
You'll see why I'm using the ultra-annoying Olsen twins as an example. (Heh, heh. Evil grin.) In case you don't know, they are the child stars who played Michelle Tanner in an inanely stupid TV show "Full House".

Clearly, your expectations are that

Q: What does the following do? Confirm your understanding using Donkey, to see how the hand-evaluation really works.

   (define kill-person!
      (lambda (person)
         (begin
            (set-person-activity! person 'dead)
            (set-person-annoying! person #f))))
   (kill-person! ashley-olsen)

   (person-activity michell-tanner2) => ?
   (person-activity mary-kate-olsen) => ?

Note that, by convention, most Scheme functions and special forms which change the state of something are named with a bang (!). It helps to remind us that these things can be dangerous.

Recall from lecture, the explanation for this behavior:

  1. When you see the definition of any make-struct, the hand-evaluation rule is to rewrite it at the top-level like

       (define placeholder^ (make-foo 17 'abc))
         
    and replace the original call to make-struct with the reference value, placeholder^.

  2. When evaluating an expression, treat the reference placeholder^ as a value. This new rule is the fundamental reason why structures have different properties than, e.g., numbers or symbols.

  3. When hand-evaluating (set-foo-firstval! placeholder^ 3), go back and change the original definition to

       (define placeholder^ (make-foo 3 'abc))
         

Cons-cells are just predefined kinds of structures, using a naming convention doesn't quite correspond to user-defined structures. They use the special forms set-car! and set-cdr!. (car and cdr are the traditional names for first and rest, respectively. However, the implementors of DrScheme forgot to define the names set-first! and set-rest!.) Remember that while each cons-cell is a structure, a list is a bunch of cons-cells.

To do:

   (define mylist (list 1 3 5 7))
   (define yourlist (cdr mylist))

   (set-car! yourlist 10)         ; the car/first should be a list element
   ; what is mylist now?
   ; what is yourlist now?

   (set-cdr! mylist (list 2 4))   ; the cdr/rest should be a list
   ; what is mylist now?
   ; what is yourlist now?

To do: What is alist after the following? Think about it first, then see what DrScheme/Donkey does.

   (define alist (list 1 3 5 7))
   (set-cdr! (cdr alist) alist)

The shared output you'll see is basically the same as local.

To do: You can get a similar result from the example in class. What is theWindyCity after the following?
   ; A <city> is
   ;  (make-city <symbol> <list-of-cities>)
   (define-struct city (name nhbrs))

   (define theWindyCity  (make-city 'chicago null))
   (define ricesTown (make-city 'houston (list theWindyCity)))
   (set-city-nhbrs! theWindyCity (list ricesTown))
Now add a sightseeing flight, from chicago to itself.

For the curious...

A reference is really just the same thing as a location, pointer, or memory address. These latter terms have more low-level connotations, dependent on the machine model (which we'll describe later in this course).

Scheme, Java, and other languages use references a lot, but hide them from the programmer. Much of the problem of writing Scheme-like functions in languages such as C is that, in those languages, the programmer is expected to deal with references explicitly.

For the curious...

Previously we used eq? to test the equality of symbols, and the like. But we said not to use it to test the equality of lists. Well, we can now say when you do want to use it on lists and other structures.

eq? tests whether two things are really the same thing. For example,

(eq? mary-kate-olsen michelle-tanner1) = #t
(eq? mary-kate-olsen ashley-olsen)     = #f
When working with structures, this is typically not the kind of equality we are interested in testing, but it can be useful.

For the really curious...

This explanation using "hats" (^) is a simplified explanation of what's going on in a Scheme implementation. To evaluate an expression, Scheme uses a store mapping placeholders to simple values (including numbers and references) and an environment mapping references to complicated values (including structures). As in the above examples, we can have multiple placeholders mapping to the same reference.

See Comp 311 for more details.


set!

set-struct-field! changes a piece of the structure referenced by a placeholder, but it does not change which structure is actually being referenced by that placeholder. To change which thing a placeholder refers to, we use set!. We'll introduce and explain set! by example.

To do: Try evaluating the following examples. First try them in your head or on paper. Then try them in DrScheme or Donkey to see if you were right.

For the curious...

To do: What are the values of x and y after the following?

   (define x 5)
   (+ (begin (set! x 6) x)
      x)

   (define y 5)
   (+ y
      (begin (set! y 6) y))
For each, do you get 10, 11, or 12? What does that tell you about the order of evaluation of function arguments?

This example is very bad style. You should not write code that depends on the the order of evaluation because it is very confusing. Moreover, a different implementation of Scheme isn't guaranteed to use the same order.


Input and output

Since Scheme automatically displays the result of whatever expression we give it, so far we haven't needed to know how to display something ourselves. Here are a few functions for getting information into and out of our programs. Try them out.

printf debugging

A primitive, but easy and versatile, way to help debugging is to use printf to display important pieces of information. The most common places to do this are at the beginning and/or end of functions.

To do: Run the following.

   (define debug? #t)  ; do I want to display debugging info?

   (define buggy-factorial
      (lambda (n)
         (if debug? (printf "buggy-factorial input: ~s~n" n))
         (local
             [(define result
                    (cond [(zero? n) 1]
                          [else      (* (sub1 n) (buggy-factorial (sub1 n)))]))]
             (if debug? (printf "buggy-factorial output: ~s~n" result))
             result)))

There are several things to note here:


Images

DrScheme has the neato whizbang feature that you can make a graphic image a value.

To do: Define a placeholder to be an image. Starting with (define foo . In the Edit menu, select Insert Image... and select a graphic file you want to use. (Some sample graphics are in ~comp210/Gifs.) Don't forget to end the definition with a closing parenthesis.