set! examples, set! vs. set-structure-component!, I/O
To change which thing a variable refers to, we use set!, as will be introduced in class. We'll preview set! by example.
To do as a group: Try evaluating the following examples. First try them in your head or on paper. Then try them in DrScheme 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 variables x and y now? It might help you to ask what values x and y have after each set!.
The hand-evaluation rule for (set! variable 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?
(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 are the values of x and y after the following? 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, by definition, a different implementation of Scheme isn't guaranteed to use the same order.
To do:
Develop
; factorial-count! : natnum -> natnum ; Purpose: Returns the factorial of its argument. ; Effects: Increments factorial-counter. ; Examples: ; #| ; > factorial-counter ; 0 ; > (factorial-count! 3) ; 6 ; > factorial-counter ; 1 ; > (factorial-count! 5) ; 120 ; > factorial-counter ; 2
While this seems kind of silly, this idea comes up all the time. More generally, we can keep track of lots of kinds of information about how a function is used. A common reason is to profile the function, to see how often a function is used or what its arguments typically are, to know how to better optimize it.
However, we would like to hide the counter factorial-count locally inside the function factorial-count!, so that nobody else can change it. Simply adding a local definition is easy enough, but there are two big problems if the counter isn't global:
Develop
factorial-count! : ('init or 'count or natnum) -> (void or natnum)The input is simply a "message" indicating one of the possible actions of the function:
Here's a standard solution. We'll see more examples like this in class.
So let's make some sample structures:
(define-struct person (birthyear haircolor annoying activity)) (define mary-kate-olsen (make-person 1986 'blonde true 'smiling)) (define ashley-olsen (make-person 1986 'blonde true 'smiling)) (define michelle-tanner1 mary-kate-olsen) (define michelle-tanner2 ashley-olsen)You'll see why we're 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? First, step through the evaluation in your head, then confirm it with DrScheme.
(define (kill-person! person) (begin (set-person-activity! person 'dead) (set-person-annoying! person false))) (kill-person! ashley-olsen) (person-activity michelle-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.
eq? tests whether two things are really the same thing, while equal? tests whether they look alike. Q:
(eq? mary-kate-olsen michelle-tanner1) = ? (eq? mary-kate-olsen ashley-olsen) = ? (equal? mary-kate-olsen michelle-tanner1) = ? (equal? mary-kate-olsen ashley-olsen) = ?
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 a list is not just a single structure, but 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? (set-cdr! (cdr mylist) mylist) ; what is mylist now? ; what is yourlist now?
The shared output you'll see is basically the same as local.
We are going to get some practice using set! and set-structure-component!, as introduced in class. As a reminder,
(define var expr) introduces a new variable and initializes its value to be the result of evaluating the expression expr.
(set! var expr) changes the value of the variable var to be the result of evaluating the expression expr.
(set-structure-component! structure-expr expr), changes the value stored in the named component of a structure to be the result of evaluating expr. The first subexpression must evaluate to the specified kind of structure.
When to use set! (Will be discussed in lecture.)
Remember that set! and set-structure-component! are as dangerous as they are useful. Careless side-effecting can have disasterous effects on your program. Use them wisely, little grasshoppper!
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, although Scheme has lots more. 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. The ~n means 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. (The "s" standards for "s-expression", Scheme lingo for "any value".)
(printf "~s ~s ~s~n" (+ 1 1) (list 'a 'b) true)displays three things separated by spaces and terminated by a newline. The three things displayed are the values of the remaining arguments.
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. In real life and later classes, we'd want to check and report any errors, including bad inputs.
(error 'myfunction "error message")causes an error. It also displays an error message using the symbol provided (typically the current function name), the string for the error message. The format function can be used to format the error string before it is passed to error.
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)
So far, you have used two good techniques for debugging code:
The following version of factorial doesn't work:
(define (buggy-factorial n) (cond [(zero? n) 1] [else (* (sub1 n) (buggy-factorial (sub1 n)))]))
We will rewrite our code to print out the input(s) and output(s) of the function:
(define debug? true) ; do I want to display debugging info? (define (buggy-factorial n) (begin (if debug? (printf "buggy-factorial input: ~s~n" n)) (local [(define result (cond [(zero? n) 1] [else (* (sub1 n) (buggy-factorial (sub1 n)))]))] (begin (if debug? (printf "buggy-factorial output: ~s~n" result)) result))))To do: Call this function with some example inputs to see what the printed messages look like.
There are several things to note here:
We use a boolean "flag" debug? to indicate whether or not to print out debugging info. This makes turning on/off debugging very easy. It can get very annoying to add and remove debugging repeatedly.
For larger programs, we can extend this idea in various ways. It would be better to hide debug?. It is often useful to use multiple debugging flags, e.g., to debug separate parts of the code independently. It is often useful to use numbers instead of booleans to display different amounts of debugging info.
The conditional we use is called if, which is just shorthand for a cond:
(if test-exp then-exp else-exp) = (cond [test-exp then-exp] [else else-exp])
In general, it is very bad style to not have an else case, but it can be useful when we are only interested in the side-effects of the conditionals, and not their result values. For a better way of handling this situation, see the information in Dr. Scheme on when and unless.
It is important to label any debugging values you display so that you know what your output means.
It is very easy to add the printf to display the input. However, it is somewhat cumbersome to add the printf to display the output since we have to name the result to refer to it twice.