Side-effects, Side-effects on structures, set!, Input and output, Images
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.
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:
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^.
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.
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.
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.
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) = #fWhen working with structures, this is typically not the kind of equality we are interested in testing, but it can be useful.
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.
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.
(define x 1) (define y 2) (set! x (+ x 1)) (set! y (+ y 1)) (set! x (+ x y))What are the values of the placeholders x and y now? It might help you to ask what values x and y have after each set!.
Compare this to the following similar code that you should already understand:
(define-struct box (name contents)) (define x (make-box 'box1 1)) (define y (make-box 'box2 2)) (set-box-contents! x (+ (box-contents x) 1)) (set-box-contents! y (+ (box-contents y) 1)) (set-box-contents! x (+ (box-contents x) (box-contents y)))
The hand-evaluation rule for (set! placeholder exp) is
Let's look at changing local variables:
(define x 1) (define y 0) (local [(define x 3)] (begin (set! x (+ x 1)) (set! y x) y))What are the values of x and y now?
Let's try a similar example with function variables:
(define x 1) (define test (lambda (x) (begin (set! x (+ x 3)) x))) x => ? (test 1) => ? x => ? (test x) => ? x => ?
Our rules for understanding evaluation don't make sense for this code. In Comp 311, you'll learn a more complicated set of rules that does handle it. However, we can rewrite this to something which we do understand. Let's introduce a local variable x' and use it instead of x:
(define x 1) (define test (lambda (x) (local [(define x' x)] (begin (set! x' (+ x 3)) x'))))
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.
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 displays information to the screen. It takes at least one argument -- a string giving the format of what to display. printf may take more arguments, if so specified by the format. E.g.,
(printf "hello, world~n")displays the string hello, world, followed by a newline.
(printf "hello, ~s~n" 'world)displays the same thing. The ~s indicates that the next argument's value should be printed in place of the ~s.
(printf "~s ~s ~s~n" (+ 1 1) (list 'a 'b) #t)displays three things separated by spaces and terminated by a newline. The three things displayed are the values of the remaining arguments.
This is considered a side-effect because it changes what is displayed on the screen.
error causes an error to happen and displays an error message. In this class, we haven't been concerned with writing functions that check for bad inputs, but usually we'd want to do that, aborting if the function receives bad input.
(error 'myfunction "error message about ~s~n" 'this-error)causes an error. It also displays an error message using the symbol provided (typically the current function name), the format string, and any other arguments. Like printf, it expects only as many other arguments as specified by the format string.
read stops a program, waits for the user to type something in, and returns the value that the user just typed in. It takes no arguments. Each time it is used, it could return a different value, since the user can type in something different each time.
(printf "I will read, but immediately forget the next thing you type.~n") (read) (printf "What is your name? ") (define name (read)) (printf "Howdy there, ~s.~n" name)
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:
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.