Functional Problem Solving (CSC 151 2014F) : Readings
Primary: [Front Door] [Schedule] - [Academic Honesty] [Disabilities] [Email] - [FAQ] [Teaching & Learning] [Grading] [Rubric] - [Calendar]
Current: [Assignment] [EBoard] [Lab] [Outline] [Reading]
Sections: [Assignments] [EBoards] [Examples] [Handouts] [Labs] [Outlines] [Readings]
Reference: [Setup] [VM] [Errors] - [Functions A-Z] [Functions By Topic] - [Racket] [Scheme Report (R5RS)] [R6RS] [TSPL4]
Related Courses: [Davis (2013F)] [Rebelsky (2014S)] [Weinman (2014F)]
Misc: [Submit Questions] - [SamR] [Glimmer Labs] [CS@Grinnell] [Grinnell] - [Issue Tracker (Course)]
Summary: We explore Scheme's basic procedures for printing output.
While much of our exploration of Scheme will involve making and manipulating images, there are times that it will be more convenient to communicate with the user (or with ourselves) through simple text. We call this “textual output”. Among other things, textual output allows us to trace the way in which our program runs.
But textual output also changes the way we think about procedures. Before, a procedure was just something that computed a value (a number, a drawing, whatever). Now, procedures also have side effects - in addition to computing a value, they do something else. At times, we'll see a conflict between our traditional way of thinking about procedures and these new side effects.
Scheme provides a variety of output procedures. For now, we'll
focus on two: display
and newline
.
In its most basic form, the display
procedure
takes one input argument and displays that argument to the screen.
That argument can be a number, a symbol, a string, or one of a variety
of other types that we will learn over the rest of the semester.
>
(display 23)
23
>
(display 'hello)
hello
>
(display (/ 3 4))
3/4
>
(display (/ 3.0 4.0))
0.75
>
(display 'a) (display 23) (display 'b)
a23b
You'll note that subsequent calls to display
that
happen together concatenate the output together. That's an expected
behavior - sometimes you want more than one thing on the same line.
What should you do if you want a line between values? The
newline
procedure moves on to a new line
before printing the next value. Strangely enough,
newline
takes no parameters, so you just
write (
. The form is
different enough that we'll say it again: newline
)newline
is a procedure, so it needs to be in parentheses, but it takes no
arguments, so it is just surrounded by parentheses.
>
(display 'a) (newline) (display 23) (newline) (display 'b)
a
23
b
Note: The display
procedure prints
its result to the screen, rather than displaying it. In part, you can
tell because the output is a bit different; in DrRacket, results are
blue while output is more of a purple color. But you can also tell
when you try to compute with the values.
>
(+ (display 1) (display 2))
12
+: contract violation expected: number? given: #<void>
The nature of the value that display
returns is unspecified. That is, the printing
is a side effect of the evaluation of the call to write
,
not its result.
What do we do if we want something other than a simple symbol or a number? We can print an arbitrary set of text by surrounding the text with double quotation marks.
>
(define name "The Doctor")
>
(display "Hello, my name is ") (display name) (newline)
Hello, my name is The Doctor
Of course, we don't usually write multiple expressions per line.
But we might combine them into a procedure. Let's write a simple
procedure that greets the user. Our procedure takes one input, the
name of the person it is greeting. It then uses multiple calls to
display
to display some appropriate text, which
it ends with a newline.
;;; Procedure: ;;; greet ;;; Parameters: ;;; name, a symbol or string ;;; Purpose: ;;; Prints a friendly message. ;;; Produces: ;;; [Nothing; called for the side effect.] ;;; Preconditions: ;;; [No additional] ;;; Postconditions: ;;; Some text has been printed. (define greet (lambda (name) (display "Hello, ") (display name) (display ". ") (display "It's nice to meet you.") (newline)))
Let's see the procedure in action. You'll note that it works with both symbols (which begin with one single quotation mark) and strings (which are surrounded by double quotation marks).
>
(greet 'Sam)
Hello, Sam. It's nice to meet you.
>
(greet 'Janet)
Hello, Janet. It's nice to meet you.
>
(greet "The Doctor")
Hello, The Doctor. It's nice to meet you.
As this procedure suggests, Scheme includes a way to explicitly sequence operations. In the past, we've simply said “Scheme evaluates the parameters to a procedure before applying the procedure”, but it's open as to what order Scheme evaluates them. Now, we see that it's possible to do side-effecting operations in sequence. When the body of a Scheme procedure includes more than one expression, Scheme evaluates each expression in order and returns the value of the last expression.
There are a wide variety of ways in which programmers use output
procedures. One particularly helpful use, at least for novice
programmers, is to trace the behavior of procedures. Once you've
written a procedure, you can add a call to display
to the start of the procedure so that you know when it's called.
Doing so can help us better understand the way in which Scheme
works.
Suppose we've written some simple procedures that do basic math - add, subtract, multiply, and divide. (Yes, we should also document those procedures, but their behavior should be pretty obvious.)
(define add (lambda (x y) (+ x y))) (define subtract (lambda (x y) (- x y))) (define multiply (lambda (x y) (* x y))) (define divide (lambda (x y) (/ x y))) (define negate (lambda (x) (- x)))
Consider the following expression.
(negate (negate (add (negate 5) (subtract (multiply 3 2) (divide 1 4)))))
You should be able to tell an order in which Scheme might evaluate this by using our normal policy of “evalute the parameters before you apply the function”. (We say “an order” because it's not clear whether the first or second parameter is evaluated first.)
But let's say we wanted Scheme to tell us what it's doing. We start by augmenting all of the procedures.
(define add (lambda (x y) (display "add ") (display x) (display " and ") (display y) (newline) (+ x y))) (define subtract (lambda (x y) (display "subtract ") (display y) (display " from ") (display x) (newline) (- x y))) (define multiply (lambda (x y) (display "multiply ") (display x) (display " and ") (display y) (newline) (* x y))) (define divide (lambda (x y) (display "divide ") (display x) (display " by ") (display y) (newline) (/ x y))) (define negate (lambda (x) (display "negate ") (display x) (newline) (- x)))
Now, we can watch Scheme in action.
>
(negate (negate (add (negate 5) (subtract (multiply 3 2) (divide 1 4)))))
negate 5
multiply 3 and 2
divide 1 by 4
subtract 1/4 from 6
add -5 and 23/4
negate 3/4
negate -3/4
3/4
This tells us a bit about how DrRacket evaluates this expression. In particular, it seems to do operations from the inside-out (as we expected), given that it multiplies before subtracting, and subtracts before negating. We might also note that given multiple parameters, it seems to evaluate them from left to right. For example, it negated 5 before it did the subtraction. We can also note that it's not very smart: We all know that if you negate twice, you get the original value back. But DrRacket does the work anyway. We can even see this as it tries to multiply a complex expression by zero.
>
(multiply 0 (multiply (divide 2 3) (divide (divide 11 4) 13)))
divide 2 by 3
divide 11 by 4
divide 11/4 by 13
multiply 2/3 and 11/52
multiply 0 and 11/78
0
But this output is incomplete. While we see the operation being
performed, we don't know what value it computes (except implicitly).
What if we want to see the result of each operation? We need a helper
procedure that prints the result. But, as we've just seen, we can't
just print the result, since the display
method
doesn't return a value. Hence, we need to print and return the value.
Here's a helper that does just that.
;;; Procedure: ;;; yield ;;; Parameters: ;;; val, a value ;;; Purpose: ;;; Display a short message about val ;;; Produces: ;;; val, the same value ;;; Preconditions: ;;; [No additional] ;;; Postconditions: ;;; A short message is displayed (define yield (lambda (val) (display " yields ") (display val) (newline) val))
You'll note that we ended the procedure with val
. That's
because Scheme uses the value of the last expression in a procedure as
the value of the procedure.
Now, a little more modifications to our procedures, and we're ready for some even more interesting output.
(define add (lambda (x y) (display "add ") (display x) (display " and ") (display y) (newline) (yield (+ x y)))) (define subtract (lambda (x y) (display "subtract ") (display y) (display " from ") (display x) (newline) (yield (- x y)))) (define multiply (lambda (x y) (display "multiply ") (display x) (display " and ") (display y) (newline) (yield (* x y)))) (define divide (lambda (x y) (display "divide ") (display x) (display " by ") (display y) (newline) (yield (/ x y)))) (define negate (lambda (x) (display "negate ") (display x) (newline) (yield (- x))))
>
(negate (negate (add (negate 5) (subtract (multiply 3 2) (divide 1 4)))))
negate 5
yields -5
multiply 3 and 2
yields 6
divide 1 by 4
yields 1/4
subtract 1/4 from 6
yields 23/4
add -5 and 23/4
yields 3/4
negate 3/4
yields -3/4
negate -3/4
yields 3/4
3/4
Wasn't that fun? We'll often use a technique like this when we're trying to understand what our code is doing.
As the reading notes, procedures like add
and
divide
provide a mechanism for helping us explore
the order in which Scheme evaluates expressions.
Make a copy of side-effects-lab.rkt
,
which contains the code from the reading.
a. Verify that you get the same output from the following expression that you saw in the reading. If you don't, hypothesize why you may have seen a different result.
>
(negate (negate (add (negate 5) (subtract (multiply 3 2) (divide 1 4)))))
b. Turn the expression 5*7+3*4/5 into Scheme, using the named procedures. Do not evaluate your expression.
c. Predict the order in which subexpressions will be evaluated.
d. Check your answer experimentally.