In the programs we've written so far this semester, we've assumed that all the data that a program needs can be either included in the source code, generated automatically within the program, or (at worst) supplied in the interactions window as an argument in a call to one of the program's procedures.
Unfortunately, this simplifying assumption doesn't always hold. In many cases, we'd like our program to take over the job of interacting with users, reading in values and displaying results. To support programs of this kind, Scheme provides several primitive procedures that perform interactive input or output as ``side effects.'' In this lab, we'll study four of them:
The read procedure takes no arguments and returns one value.
When it is invoked, it pauses and waits for the user to supply a
representation of a Scheme value -- a numeral, a string literal (enclosed
in double-quotation marks, as if in a Scheme program), a Boolean or
character literal, a symbol (which need not be preceded by a single
quotation mark), or a list (which again need not be quoted). The
read procedure returns the value represented.
Under DrScheme, the read procedure's interaction with the user
takes place in an interaction box, visually separated from the rest
of the interaction window:
The user of the program types into this box a text representation of the value that she wants to send to the program -- the number 25, say:
When the user presses the <Enter> key to end the line, DrScheme
releases the value that she has entered to the read procedure,
which returns it.
The write procedure takes one argument and prints out a
representation of that argument. The nature of the value that it
returns is unspecified (under DrScheme, for instance, it's the
special ``void'' value) -- the printing is a side effect of the evaluation
of the call to write, not its result.
DrScheme also encloses the material that write prints out
inside an interaction box. You can distinguish user input from program
output in an interaction box by its color: User input is displayed in
green, program output in purple. Both are distinguished from DrScheme's
usual way of exhibiting the value of an expression; such values are printed
in dark blue and are not placed inside an interaction box.
The display procedure also takes one argument and prints out a
representation of it, but it differs from write in that it
does not enclose the representations of strings in double quotation marks
and does not print the mesh-backslash combination when displaying a
character:
The newline procedure takes no arguments and returns an
unspecified value; as a side effect, it terminates the current output line.
Successive calls to write and display normally
produce output that is all strung together on one line. Calls to
newline are used to break up such output into separate lines.
The call (newline) has exactly the same effect as
(display #\newline), for which you can consider it a
convenient shorthand.
The last example introduces a new kind of expression, the
begin-expression, which evaluates each of its subexpressions
once, and only once, in sequence. The value of the last subexpression is
the value of the entire begin-expression; the values of the
other subexpressions are discarded. Consequently,
begin-expressions are useful only in connection with
procedures like write, display, and
newline, which we invoke only for their side-effects. We'll
use begin to hook together a number of calls to such
procedures, so as to evaluate them in sequence.
Here's a small illustration of the use of the read procedure.
The square-root-computer procedure asks the user to supply a
number, computes the square root of the number that the user supplies, and
prints out the result, appropriately labelled, all within the interaction
box:
(define square-root-computer
(lambda ()
(display "Give me a number, and I'll compute its square root.")
(newline)
(let ((proposed-number (begin
(display "Number: ")
(read))))
(if (number? proposed-number)
(begin
(display "The square root of ")
(display proposed-number)
(display " is ")
(display (sqrt proposed-number))
(display ".")
(newline))
(error "square-root-computer: The input must be a number.")))))
Here are the results of a couple of calls to the
square-root-computer procedure. Notice that the value of
proposed-number is not supplied as an argument to
square-root-computer, but is read in as the program is being
executed. The green printing shows where the user typed it in.
Start DrScheme, copy the definition of square-root-computer
into its definition window, press the Execute button, and
invoke square-root-procedure (with no arguments, since its
parameter list is empty). By interacting with the program, find the square
root of 7569.
Write a Scheme procedure that collects two numbers from the user and prints out their sum.
If one wants the procedure to compute many square roots instead of just one, prompting the user each time for a new number, one can set up a recursion in which the completion of each exchange initiates another:
(define multi-square-root-computer
(lambda ()
(display "Give me one number at a time.")
(newline)
(display "I'll compute its square root and then ask you for another number.")
(newline)
(display "Type STOP when you're done.")
(newline)
(let kernel ((proposed-number (begin
(display "Number: ")
(read))))
(cond ((eq? proposed-number 'stop)
(begin
(display "Goodbye!")
(newline)))
((number? proposed-number)
(begin
(display "The square root of ")
(display proposed-number)
(display " is ")
(display (sqrt proposed-number))
(display ".")
(newline)
(kernel (begin
(display "Number: ")
(read)))))
(else
(error "multi-square-root-computer: The input must be a number."))))))
Let's walk through the body of this procedure definition. When
multi-square-root-computer is invoked, it begins by printing
out three lines of instructions, then enters the recursive kernel, reading
in the first user input as it enters and associating the parameter
proposed-number with it.
The cond-expression first checks to see whether the user has
submitted the symbol stop, which it interprets as a
sentinel -- a conventional signal of the end of the input,
indicating that the user is ready to leave the program. If the sentinel is
detected, multi-square-root-computer prints out ``Goodbye!''
and returns.
If the user's input is not stop, however, the second
cond-clause is activated. If the user has submitted a number,
multi-square-root-computer figures its square root and
displays the result, embedded in a complete English sentence.
On the other hand, if the user's input is neither the symbol
stop nor a number, it is erroneous, and the procedure signals
that a precondition has failed by invoking the error procedure
to halt execution.
Add the definition of multi-square-root-computer to the
DrScheme definition window, press Execute again. Invoke
the procedure and interact with it to find the square roots of 729, 15129,
and 173056; then stop.
Define a Scheme procedure sum-of-inputs that takes no
arguments and returns the sum of as many numbers as the user chooses to
type in. Prompt the user for each addend, and have the user signal the end
of the addends by typing in the symbol end. An interaction
with this procedure might look like this:
Write a Scheme procedure named yes-or-no-prompt that prompts
the user to type in a yes-or-no answer and reads in the response. Your
procedure should return #t if the user input is the symbol
y or the symbol yes and #f if the
user input is the symbol n or the symbol no. If
the user input is anything other than one of these four symbols,
yes-or-no-prompt should invoke itself recursively to repeat
the prompt.
This document is available on the World Wide Web as
http://www.cs.grinnell.edu/~stone/courses/scheme/input-and-output.xhtml
created October 10, 1997
last revised March 17, 2000