Comp 210 Lab 9: 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.

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

Now we can say

(set-person-activity! ashley-olsen 'dead)
(set-person-annoying! ashley-olsen #f)
and what do we get for the following:
(person-activity michell-tanner2) => ?
(person-activity mary-kate-olsen) => ?

If we had done these operations within a function, would it make a difference? Would these changes still be visible? Try the following:

(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)

(define kill-person!
    (lambda (person)
        (set-person-activity! person 'dead)
        (set-person-annoying! person #f)))
(kill-person! michelle-tanner2)

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

Recall that our explanation for this is the following:

Cons-cells are just predefined kinds of structure, 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.

(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?

Remember that vectors are very similar to structures. (Structure components are indexed by name; vector components are indexed by position.) Vectors use with the special form vector-set! to change a vector.

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 see more of in a few weeks).

In general, Scheme (and Java) use reference 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!

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.


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.

(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

In DrScheme, you can make a graphic image a value. In the Edit menu, select Insert Image.... Then select a graphic file you want to use. Some sample graphics are in ~comp210/Gifs.