set!
examples,
set!
vs.
set-structure-component!
I/O
set!
examples
To change which thing a variable refers to, we use set!
, as
introduced in class.
Do the following exercises as a group. For each, first try it in your head or on paper, then check your answer using DrScheme. These are "toy" examples that are not necessarily typical of real programs. However, they illustrate important issues of using state.
|
In class, we've seen some more realistic uses of set!
.
Here are a couple more.
|
Sample solutions: Version 1, version 2. |
Note the "effects" comment for the first example.
You should always provide such a comment when your function
changes state that is visible outside the function.
E.g., in the first function, factorial-counter
is global,
so factorial-count!
needs to describe what happens to it.
In the second, the counter is local, but it still needs to be
described since its presence can be noticed when you use the
'count
message.
set-structure-component!
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
mary-kate-olsen
and michelle-tanner1
are really the same person, and
mary-kate-olsen
and ashley-olsen
are different people, although they look identical.
eq?
tests whether two things are really
the same thing, and
equal?
tests whether they look alike.
|
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.
Disclaimer: In case it isn't blindingly obvious, this is a joke. The course staff does not advocate killing the Olsen twins. (Fortunately, the fictional Michelle Tanner lives only in reruns.)
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.
(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
.
set!
vs.
set-structure-component!
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
(Partly discussed in class.)
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
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
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.
Execute the following code, typing in answers when prompted.
(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) |
printf
debuggingSo far, you have used two good techniques for debugging code:
printf
appropriately can provide one more good technique,
by displaying important pieces of information.
You briefly saw an example of this in class.
The most common places to do this are at the beginning of a function,
the end of a function, and after a bunch of
set!
or set-structure-component!
assignments.
With practice, you'll see that these and other techniques each have
strengths and weaknesses.
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))))
Call buggy-factorial 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.