set!, Input and output
To change which thing a placeholder refers to, we use set!, as introduced in class. We'll review set! by example.
To do: 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 placeholders 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! placeholder exp) is
Let's look at changing local variables:
(define x 1) (define y 0) (local [(define x 3)] (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 x) (set! x (+ x 3)) x) x => ? (test 1) => ? x => ? (test x) => ? x => ?
Our rules for understanding evaluation with set! 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 xx and use it instead of x:
(define x 1) (define (test x) (local [(define xx x)] (set! xx (+ xx 3)) xx))
(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, a different implementation of Scheme isn't guaranteed to use the same order.
To do: Develop factorial-count! : natnum -> number, which computes the factorial of its argument. It also keeps track of how many times the user has called in function, using the global variable factorial-count. It does not count any recursive calls, e.g.,
> (factorial-count! 3) 6 > factorial-count 1 > (factorial-count! 5) 120 > factorial-count 2Ideally, we would want to hide the variable factorial-count, but still provide access to it somehow. Your homework has a problem similar to that.
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.
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)
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) (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))To do: Use this code to see what the printed messages look like.
There are several things to note here:
We use a flag debug? to indicate whether or not to print out debugging info. This makes turning on/off debugging very easy.
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.
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]) (if test-exp then-exp) = (cond [test-exp then-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.
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 -- we have to name the result to refer to it twice.