In the programs we've written so far this semester, we've relied on the Chez Scheme interactive interface to collect the input. We've assumed that the users of our programs know enough about Scheme to formulate a call to at least one of the procedures that we've defined.
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 writing out computed results without the assistance of the interactive interface.
To support programs of this kind, Scheme provides several procedures that
perform interactive input or output. We have already encountered two of
them: display and newline. The others are:
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. If the user
types in anything after the representation of a value, everything extra is
preserved, so that additional values can be extracted from the input on
subsequent calls to read.
The read-char procedure takes no arguments and returns a
character as its value. When it is invoked, it pauses and waits for the
user to supply a character, then returns the character supplied. If the
user supplies more than one character, all the extras are preserved, so
that additional values can be recovered by subsequent calls to
read-char.
The peek-char procedure takes no arguments and returns
a character as its value. When it is invoked, it pauses and waits for the
user to supply a character, then returns the character supplied. Unlike
read-char, peek-char does not actually consume
the character it returns; instead, it leaves it in the input channel, so
that it is still available during a subsequent call to read or
read-char.
The write procedure takes one argument and prints out a
representation of that argument. Whereas display accommodates
human readers by printing out strings without the enclosing double
quotation marks and characters without the mesh-backslash,
write prints out values in a form that enables them to be
read back in by the read procedure. The value returned by
write is unspecified; like display, it is invoked
only for its side effect.
The write-char procedure takes one argument, which must be a
character, and prints out that character -- no extras, just the one
character. The value returned by write-char is unspecified;
it is invoked only for its side effect.
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, without using the Chez
Scheme interactive interface.
(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 (not (number? proposed-number))
(error 'square-root-computer
"To compute a square root, I need a number"))
(display "The square root of ")
(display proposed-number)
(display " is ")
(display (sqrt proposed-number))
(display ".")
(newline))))
Create a file square-root-computer.ss containing the preceding
Scheme procedure, followed by a call to that procedure. Save the file.
Start Scheme in a dtterm window by typing in the following
command line:
scheme square-root-computer.ss
This runs the program. Use it to find the square root of 303/25.
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 square-root-computer
(lambda ()
(display "Give me as many numbers as you like.")
(newline)
(display "I'll compute its square root of each one.")
(newline)
(let loop ((proposed-number (begin
(display "Number: ")
(read))))
(if (not (number? proposed-number))
(error 'square-root-computer
"To compute a square root, I need a number"))
(display "The square root of ")
(display proposed-number)
(display " is ")
(display (sqrt proposed-number))
(display ".")
(newline)
(loop (begin
(display "Number: ")
(read))))))
In this form, the procedure doesn't provide any way for the user to break
out of the loop (except by supplying a non-numeric value, so that the
error procedure is invoked). What is needed is some kind of a
sentinel -- a conventional value indicating the end of the input.
The user could then type in the sentinel value when she wanted to leave the
program.
Here's how the procedure looks if the symbol stop is used as
the sentinel:
(define square-root-computer
(lambda ()
(display "Give me as many numbers as you like.")
(newline)
(display "I'll compute its square root of each one.")
(newline)
(display "Type STOP at the prompt to get me to stop.")
(newline)
(let loop ((proposed-number (begin
(display "Number: ")
(read))))
(if (not (eq? proposed-number 'stop))
(begin
(if (not (number? proposed-number))
(error 'square-root-computer
"To compute a square root, I need a number"))
(display "The square root of ")
(display proposed-number)
(display " is ")
(display (sqrt proposed-number))
(display ".")
(newline)
(loop (begin
(display "Number: ")
(read))))))))
Save this version of the procedure (again followed by a call to the
procedure) in square-root-computer.ss and restart Chez Scheme
with the same command line as in exercise 1. Use the program to compute
the square roots of 153, 729, and 1111; then exit from the loop.
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:
> (sum-of-inputs) Addend: 53 Addend: 42 Addend: 39 Addend: 41 Addend: end 175
To illustrate the use of read-char, here is a procedure that
reads in a line of input from the user and returns all the characters typed
as a string:
(define read-line
(lambda ()
(let loop ((next-char (read-char))
(so-far '()))
(if (char=? next-char #\newline)
(list->string (reverse so-far))
(loop (read-char) (cons next-char so-far))))))
Here is a loop with which you can test the read-line
procedure:
(define test-read-line
(lambda ()
(let loop ((line (begin
(display "Type in a line: ")
(read-line))))
(if (not (string=? line "stop"))
(begin
(write line)
(newline)
(loop (begin
(display "Type in a line: ")
(read-line))))))))
How can the user exit from the loop initiated by a call to
test-read-line?
Write a Scheme procedure named yes-or-no-prompt that prompts
the user to type in a yes-or-no answer, reads in the response using
read-line, and returns #t if the response is a
non-null string beginning with y or Y and
#f if the response is the null string or a string beginning
with some other character.
This document is available on the World Wide Web as
http://www.math.grin.edu/courses/Scheme/spring-1998/input-and-output.html
created October 10, 1997
last revised June 21, 1998