set-struct-field!
;
a Little I/O
set-struct-field!
I/O
First, a review, due to some misunderstandings on the exam:
;; clone-list: list --> list ;; Return a list which is equal? to lst, ;; but not eq? ;; -- in fact, it shares ;; no cons cells (structures) in common with lst. ;; (define (clone-list lst) (cond [(empty? lst) empty] [(cons? lst) (something (first lst) (clone-list (rest lst)))]))In place of
something
how might we (mis)use
cons
,
list
,
or
append
in the answer?
Reminder: For the current homework,
do not use any function with !
in its name.
set-struct-field!
In lecture we've discussed how, in Advanced Student level, we've extended scheme to include references to structures, and how to mutate structures (change them over time). This lets our program more closely match what we're modeling.
The remainder of this lab is for you to work through entirely on your own, asking labbies at your leisure!
to do:
Paste the code below into drscheme,
and write complete the function create-cat
.
(define-struct cat (name age high? buddy)) ; ; A cat is: ; (make-cat [symbol] [number] [boolean] [cat]) ; where age is in cat-years, ; high? is whether they are high on catnip, ; and buddy is their best buddy (which every cat has). ; A dummy value to use for cats' "buddy" field, initially; ; presumably we'll remember to assign it a real value, ; before we ever need to use it! ; (define no-buddy '??dummy??) ;; create-cat: symbol, number --> cat ;; Return a newly-created cat, ;; with name nm and age yrs, ;; who is not high and has the buddy field initialized to no-buddy. ;; (define (create-cat nm yrs) ...) (define cat01 (create-cat 'morris 23)) (define cat02 (create-cat 'leo 47)) (define cat03 (create-cat 'cheshire 1)) (define mgm-logo cat02) ;; On paper, draw a picture of what things are like. ;; Hint: draw all cat structures in a row in the middle of the page. ;; Keep all "define"s off on the left of the page. ;; (Each placeholder is bound to a *reference*, which you ;; can draw as an arrow.) ; One can have structures in a list, not bound to placeholders. ; (define others (list (create-cat 'puss 11) (create-cat 'shere-khan 22))) (define housecats (list (first others) cat03)) ; Or (cons (first others) (cons cat03 empty)) ;; Extend your picture to include these changes. ;; Hint: make each list a row of cons cells, all in a row, ;; with references off to the cats in the middle of the page. (set-cat-high? cat03 true) ; Light up his smile. ;; Look at the value of cat03, others, and housecats. ;; Are the changes what you expect? |
Make the suggested drawings; when finished, compare with the pictures that the lab-leader has drawn on the board! (Call a labby over to discuss them, if you like -- being able to draw the relations between structures is a skill which you'll need to have down cold!)
to do Write the function:
;; befriend!: cat, cat, --> (void) ;; Set c1's buddy to be c2. ;; Note that c2's buddy remains unchanged. ;; (define (befriend! c1 c2) ...) |
Optional: Discuss with your partner and/or labby: Does the following data definition make sense?
begin
to do:
Read the next paragraphs about begin
,
and then modify befriend!
as described.
We'd like to modify befriend!
so that it returns a boolean:
is c1
's friendship requited by c2
?
That is, we set c1
's buddy to be c2
,
but is it the case that c2
's buddy is now c1
?
However, writing code for this presents a problem:
a function's body consists of exactly one expression,
but we want to have two expressions:
one to set-cat-buddy!
as before,
and another to test whether c2
's buddy is
equal to c1
.
How to sequence:
begin
.
(begin (+ 2 3) (* 2 3) (- 2 3))evaluates each of its (three) expressions, throws away the results of most of them, and returns the value of the last expression. In this case, -1.
(begin expression ... expression)There must be at least one expression.
begin
to be useful!
Go back and rewrite befriend!
as described,
now that we know about begin
.
Note:
Some consider it bad practice to have functions that both
calculate/return a value, and have a side-effect.
In practice, doing so is very handy in a few situations,
but by and large leads to bugs
(next week you call your function for a value, forgetting that it has a side-effect,
or vice versa).
In this example, it would be much better to write a separate
function requites?
, instead of blending this function
in with befriends!
.
Challenge problem (if you finish early):
;; pass-the-catnip: cat --> (void) ;; Set not only this cat's metabolic status to being high on catnip, ;; but also it's buddy's status (and, that buddy's status, and ...). ;; ; Uh-oh: the data def'n for cats refers to itself, ; and doesn't have any base-case! ; (As in sorting, we are recurring on something other than a substructure ; of the input, leaving the template behind.) ; How to avoid an infinite loop? ; ; Two approaches come to mind; you can try either one: ; - Keep an accumulator of which cats have already been processed; ; if given a cat which is already in the processed-cats, stop and return a dummy value. ; - Add a new field to cat, called "has-been-processed?"; ; if given a cat which has-been-processed, stop and return a dummy value.
Cons-cells are just predefined kinds of structures, using a naming
convention doesn't quite correspond to user-defined structures.
For mutating these structures, scheme provides the functions
set-first!
and set-rest!
.
(Remember that a list is not just a single structure, but
a bunch of cons-cells, or
empty
(which is a flat value -- not a (reference to a) structure!)
(define mylist (list 1 3 5 7)) (define yourlist (rest mylist)) (set-first! yourlist 10) ; The first should be a list element. ; what is mylist now? ; what is yourlist now? (set-rest! mylist (list 2 4)) ; The rest should be a list. ; what is mylist now? ; what is yourlist now? (set-rest! (rest mylist) mylist) ; what is mylist now? ; what is yourlist now? |
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
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, ~v~n" 'world)displays the same thing. The
~v
indicates that the
next argument's value should be printed in place of the ~v
.
(Mnemonic: "v" meaning any value.)
(printf "~v ~v ~v~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.
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, ~v.~n" name)Observe how words you type in are read as symbols. You can click on "eof" to type in an "end-of-file" signal. (Aside: you can use ~a to format output suitable
for non programmers -- that is, symbols aren't preceded by
' and strings aren't surrounded by " s.)
|
printf
debugging
Alternately to explicitly adding printfs ourselves,
compare with:
Load the teachpack trace.ss,
and evaluate (trace buggy-fact)
.
So far, you have used two good techniques for debugging code:
printf
appropriately can provide one more good technique,
by displaying important pieces of information.
The most common places to do this are at the beginning of a function
and
the end of a function
With practice, you'll see that these and other techniques each have
strengths and weaknesses.
The following version of factorial doesn't work:
;; buggy-fact: natnum --> number ;; Return n!. ;; OOPS -- doesn't work -- always returns 0?!! ;; (define (buggy-fact n) (cond [(zero? n) 1] [else (* (sub1 n) (buggy-fact (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? ;; buggy-fact: natnum --> number ;; Return n!. ;; OOPS -- doesn't work -- always returns 0?!! ;; (define (buggy-fact n) (begin (when debug? (printf "buggy-fact input: ~v~n" n)) (local [(define result (cond [(zero? n) 1] [else (* (sub1 n) (buggy-fact (sub1 n)))]))] (begin (when debug? (printf "buggy-fact output: ~v~n" result)) result)))) |
We can make a couple of tweaks on this, though it's not really necessary:
local
into doing the work of the begin
.
(See the example.)
printf
returns a useless void-value,
so does unless
(in the event its test doesn't hold);
still, we can define a placeholder to capture this useless result
of unless
.
;; buggy-fact-help: natnum --> number ;; Return n!. ;; OOPS -- doesn't work -- always returns 0?!! ;; (define (buggy-fact-help n) (cond [(zero? n) 1] [else (* (sub1 n) (buggy-fact (sub1 n)))])) ; ; Note how we call the debug-printing version, ; so that we can see debug-info of recursive calls. (define debug? true) ; Do I want to display debugging info? ;; buggy-fact: natnum --> number ;; Return n!. ;; OOPS -- doesn't work -- always returns 0?!! ;; ;; Implementation note: just a wrapper which can output debug info, ;; and calls the workhorse buggy-fact-help. ;; (define (buggy-fact n) (local {(define ignore1 (when debug? (printf "buggy-fact input: ~v~n" n))) (define result (buggy-fact-help n)) (define ignore2 (when debug? (printf "buggy-fact output: ~v~n" result)))} result)) |
Todo:
Call buggy-fact with some example inputs
to see what the printed messages look like.
Then, fix it!
|
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.
To ponder:
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 when
:
(when test-exp exp ... exp)evaluates its
test-exp
,
and if true it returns
(begin exp ... exp)
);
if the
test-exp
is false, then no useful value is returned (just as
set-struct-field!
and
printf
don't return any useful value).
There is a complement to when
, named unless
.
For the interested: go ahead and look up if
in help-desk.
if
is a shorthand form of cond
.
Please still use cond
if doing the template's case-analysis
(such as testing for cons?
vs empty?
);
use if
in situations which will never generalize beyond
two cases, (such as testing if a number exceeds some threshold or not).
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.
See you next week!