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.
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.) 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:
(define placeholder^ (make-foo 1 'a))and replace the original definition placeholder with placeholder^.
(define placeholder^ (make-foo 3 'a))
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.
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.
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.
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 x and y now?
(define x 5) (+ (begin (set! x 6) x) x)and
(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?
(define x 1) (define y 0) (local ((define x 3)) (begin (set! x (+ x 1)) (set! y x) y))What are x and y now?
Note that you don't actually need the begin here, e.g.:
(define x 1) (define y 0) (local ((define x 3)) (set! x (+ x 1)) (set! y x) y)Inside a local, there is an implicit begin.
(define x 1) (define test (lambda (x) (begin (set! x (+ x 3)) x))) x => ? (test 1) => ? x => ? (test x) => ? x => ?Note that again the begin here is optional, lambdas have an implicit begin:
(define x 1) (define test (lambda (x) (set! x (+ x 3)) 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'))))
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 "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 '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)
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:
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.