0% found this document useful (0 votes)
75 views575 pages

Scheme and The Art of Programming

The document introduces the concepts of data and programming through the lens of the Scheme language, emphasizing the importance of abstraction and recursion in programming. It discusses the nature of programming as a craft, art, and literary endeavor, highlighting the significance of elegance and clarity in code. The text also outlines the components of a computer and the basics of how Scheme operates with symbols and numbers.

Uploaded by

최담종
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
75 views575 pages

Scheme and The Art of Programming

The document introduces the concepts of data and programming through the lens of the Scheme language, emphasizing the importance of abstraction and recursion in programming. It discusses the nature of programming as a craft, art, and literary endeavor, highlighting the significance of elegance and clarity in code. The text also outlines the components of a computer and the basics of how Scheme operates with symbols and numbers.

Uploaded by

최담종
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 575

Scheme and the Art

^of Programming

George Springer and

Daniel P. Friedman

'^d
Part 1

Data

Data are either individual units of information or collections of data. Before


the book really begins in earnest, we are introduced to a recursive charac-
terization of data. In Chapter 1, we study the way Scheme treats symbolic,
numerical, and logical data.
In Chapter 2, we build procedures for processing symbolic data. We can
think of the act of dining out as a procedure. We generally enter a restaurant,
read a menu, order some food, eat that food, pay a bill, and exit. We do
not think about the procedure for a particular restaurant, but we abstract
over all restaurants. A primary goal of Part 1 is to present you with enough
examples of how to abstract over data so that your procedures will be general.
While developing your intuition for handling symbolic data, we introduce
recursive procedures. Recursion is at the heart of Part 1. Not only are the
data described recursively, but also the procedures, which process the data,
are recursive.
In Chapter 3, we study numbers and operations over numbers. We intro-
duce iterative processes as a special case of recursive processes. We conclude
this chapter with the development of a rational number (fraction) abstract
data type. Much theme of this book is the understanding of program-
of the
ming with abstract types and this is the first such example.
In Chapter 4, we continue building your intuition about recursion and it-

eration. Here we combine the various data types into different structures and
characterize the procedures that process them.
We introduce local variables in Chapter 5. In order to package privately
computational objects that work together, we introduce lexical scope.
Finally, in Chapter 6, we show some of the advantages of displaying infor-
mation and entering data while a computation proceeds.

Data
Data and Operators

. . .it is not the thing done or made which is beautiful, but the doing. If we
appreciate the thing, it is because we relive the heady freedom of making it.

Beauty is the by-product of interest and pleasure in the choice of action.


Jacob Bronowski,
The Visionary Eye

Computing is an art form. Some programs are elegant, som,e are exquisite,
some are sparkling. My claim, is that it is possible to write grand programs,
noble programs, truly magnificent program,s.

Donald E. Knuth,
from an article by William Marling
in Case Alumnus

1.1 Introduction

Computer programming is many faceted.


It is engineering. Computer programs must be carefully designed. They
should be reliable and inexpensive to maintain. Like any other engineer-
ing discipline, computer programming has special challenges. The foremost
challenge is managing complexity. As programs grow larger, the number of
possible interactions between their pieces tends to grow much faster than the
volume of code. Abstraction is the primary technique for managing complex-
ity. An abstraction hides unnecessary detail and allows recurring patterns to

be expressed concisely. In this book we emphasize several powerful techniques


for building abstractions.

It is a craft. A
program made with craftsmanship is both more serviceable
and more satisfying. Programming requires proficiency born of practice (hence
the many exercises in this book!). It requires great dexterity, though of a
mental rather than a manual sort. As woodworkers enjoy working with their
hands and fine tools, so programmers enjoy exercising their minds and working
with a fine programming language.
It is an art. Fine programs are the result of more than routine engineering.

They require a refined intuition, based on a sense of style and aesthetics that
is both personal and practical. As an artistic medium, programming is highly
plastic, unconstrained by physical reality. In programming, perhaps more
than in other arts, less is more. Simplicity is nowhere more practical than in
programming, where the bane is complexity. When just the right abstraction
for a problem has been found, it may be a thing of beauty. We hope you take
pleasure in the programs of this book.
It is not a science, but it is based on one: computer science.Though
our primary concern in this book is with the techniques of programming, we
will have occasion to introduce a number of important scientific results. We
hope you find the language and style of this book to be vehicles for deeper
understanding and appreciation throughout your study of computer science.
It is a literary endeavor. Of course, programs must be understood by
computers, which requires mastery of a programming language. But that
is not enough! Programs must be analyzed to understand their behavior,
and most programs must be modified periodically to accommodate changing
needs. Thus it is essential that programs be intelligible by humans as well as
by computers. The challenge is to convey the necessary details without losing
sight of the overall structure. This in turn requires creative use of abstrac-

tions and a good sense of style — habits we attempt to instill by example in

this book.

But this book is ultimately about more than the craft of engineering artistic
and literate programs. Programming teaches an algorithmic (step by well-
specified step) approach to problem solving, which in turn encourages an
algorithmic approach to gaining knowledge. This view of the world is pro-
viding numerous fresh insights in fields as diverse as mathematics, biology,
and sociology, as well as providing tools that assist and extend our minds in

almost every field of study. Thus programming ability, like mathematical and
writing ability, is an asset of universal value.
Programming ability and literary ability have another thing in common.
An essay or short story can be correct grammatically and can convey the
information that the author intended and still not be a literary work of art.

Computer programming has its own aesthetic, and good programmers strive to

produce programs that evoke appreciative responses in their readers. Writing


such programs requires both inspiration and the application of craftsmanship
that employs a thorough command of the programming language and the
metaphors it can support.
There are many languages from which to choose when designing a course
to teach the principles of programming. Scheme was selected because it is
an expressive language that provides powerful abstraction mechanisms for ex-
pressing the solutions to computational problems. This facilitates the writing
of clear and satisfying programs. It is especially good as a vehicle for teaching

Data and Operators


programming because the student is not required to learn unnecessary rules
and prohibitions before being able to write meaningful programs.
The programming language LISP (which stands for List Processing) was
developed around 1960 by John McCarthy. (See McCarthy, 1960.) Scheme
was derived from LISP by Gerald Jay Sussman and Guy Lewis Steele Jr.
around 1975. (See Sussman and Steele, 1975.) A number of people have been
involved in the evolution of Scheme since its inception and these developers
of the language have published a series of reports describing the current state
of the language. For the first such report, see Steele and Sussman, 1978. The
third revised report appeared in 1986. (See Rees and dinger, 1986.) The
fourth revision is expected in 1989. There is also a working group preparing
for an IEEE Standard for Scheme. A number of books about Scheme have
appeared since then, including:

• Computer Programs by Abelson and Suss-


Structure and Interpretation of
man with Sussman, MIT Press and McGraw-Hill Book Company, 1985.
• The Little LISPer by Friedman and Felleisen, MIT Press, 1987 and SRA
Pergamon, 1989.
• The Scheme Programming Language, by Dybvig, Prentice-Hall, 1987.
• Programming in Scheme by Eisenberg, Scientific Press, 1988.

• An Introduction to Scheme by Smith, Prentice-Hall, 1988.

The following two publications are manuals for Scheme that accompany the
implementations of Scheme on microcomputers:

• Afac^cAeme-froo/smiM^'^, Semantic Microsystems, 1987.


• PC Scheme, by Texas Instruments, Scientific Press, 1988.

We encourage you to read them because each presents its own programming
philosophy. We are all using the same language, but we have somewhat dif-

ferent stories to tell.

As you read these pages, remember that you should care how elegant your
programs are. The task that confronts you is not only to learn a programming
language but to learn to think as a computer scientist and develop an aesthetic
about computer programs. Enjoy this as an opportunity to understand the
creative process better. Solve problems not only for their solutions but also
for an understanding of how the solutions were obtained.

1.2 The Computer


1.2 The Computer

We begin by briefly describing the components of a computer. At this stage,


it suffices to think of the computer as being composed of four components:

1. The input deuce, in this case the keyboard with the standard typewriter
keys and some additional ones. Each key can perform several functions.
On both the typewriter and the computer keyboard, we choose between
lower and upper case by depressing the Shift key. On the computer, we
can also hold down the Control (CTRL) key while pressing another key to
get another behavior, and on some computers, we can similarly hold down
the Alternate (ALT) key while pressing another key to get yet another
behavior. Finally, pressing and releasing the Escape (ESC) key before
pressing another key gives still another behavior. When a key is pressed,
the result is usually shown on the screen.

2. The processor, in which the computing is done. This contains the internal
memory of the computer, the arithmetic logic unit, and the registers where
the computations take place.

3. The output devices: the video monitor on which the interactive computing
is viewed, which we refer to as the screen, and the printer where printed
copy of the output is produced.

4. The external storage device. In microcomputers, this often consists of two


floppy disk drives. The user places diskettes into these drives and either
reads files from a diskette into the computer's internal memory or writes
from the internal memory to a file on a diskette. Many microcomputers
and all larger computers have an internal disk on which files can be stored
and accessed.

Implementations of Scheme are available on a wide variety of computers


ranging from larger mainframe computers that support many users to indi-
vidual workstations or personal computers.

1.3 Numbers and Symbols

In order to make a we must communicate with


computer do something for us.

the computer in a language that it "understands." The English language,


which we are using for our communication m this paragraph, makes use of
words and certain grammatical rules that enable us to combine words into

Data and Operators


sentences. The words themselves consist of certain strings of characters, that
is, characters written one after the other with no blank spaces between them.
The computer languages also have their analogs of words, which we call sym-
bols. The characters used to make up the symbols are the same characters on

a standard typewriter keyboard, with a few additions and deletions. We shall

generally use the letters of the alphabet, the digits from through 9, and some
of the other characters on the keyboard. A few of the other characters on the
keyboard have special meaning, just as certain characters like the period and
comma have special meaning in English. In Scheme, the characters

()[]{};."''# \

have special meaning and cannot appear in symbols. Similarly, the characters

+ - .

are used in numbers and may occur anywhere in a symbol except as the first

character. The following list contains examples of symbols in Scheme:

abed r cdr p2q4 bugs? one-two *nowft

Numbers are not considered to be symbols in Scheme; they form a separate


category. Thus, as you would expect, 10, -753, and 31 5 are Scheme numbers.
.

In the English language, not every combination of letters gives us a meaningful


word. We keep words that are meaningful in our minds or in a dictionary,
and when we see or hear a word, we
meaning in order to use it.
retrieve its

In much the same way, symbols may be assigned some meaning in Scheme. A
symbol used to represent some value is called a variable. The computer must
determine the meaning of each variable or number it is given. It recognizes
that the numbers have their usual meaning. Scheme also keeps the meaning
of certain variables that have been assigned values, and when it is given a
symbol, it checks to see if it is one of those that has been kept. If so, it can
use that meaning. Otherwise it tells us that the symbol has not yet been
given a meaning.
To carry the analogy with the English language a step further, words are
put together in sentences to express the thoughts you want to convey. The
Scheme analog of a sentence is an expression. An expression may consist of
a single symbol or number (or certain other items to be defined later), or a
list, which is defined to consist of a left parenthesis, followed by expressions
separated by blank spaces, and ending with a right parenthesis. We first

1.3 Numbers and Symbols 7


discuss the use of expressions involving symbols or numbers, and return to
discussing other types of data in Section 1.4.
When you turn on the computer and
call up Scheme, you usually get a

message what implementation of Scheme you are using. Then a prompt


telling

appears on the screen, prompting you to enter something. The nature of the
prompt depends on the implementation you are using. The prompt we use
in this book to simulate the output on the screen is a pair of square brackets
surrounding a number. Thus the first prompt will be

[1]

If you type a number after theprompt and then press the <RETURN> key
(sometimes referred to as the <ENTER> key),

[1] 7 <RETURN>

Scheme recognizes that the meaning of the character that you typed is the
number 7. We number 7
say that the value of the character you typed is the
or that what you type has been evaluated to give the number 7. Scheme then
writes the value of what you type at the beginning of the next line and moves
down one more line and prints the next prompt:

[1] 7 <RETURN>
7

[2]

Let us review what we have just seen. At the first prompt, you enter 7 and
press <RETURN>. In general, an expression (or a collection of such expressions)
you enter in response to the prompt and before pressing <RETURN> is called
a program. In this example. Scheme reads your program, evaluates it to the
number 7, prints the value 7 on the screen at the beginning of the next line,
and then prints the next prompt one line lower. Thus Scheme does three
things in succession: it reads, it evaluates, and it prints. We refer to this

sequence of events performed by Scheme as its read-eval-print loop. After


printing the prompt, Scheme waits for you to type the next program. In the
example, when you press <RETURN>, Scheme completes one cycle of the loop
and begins another.
What happens when a symbol is typed after the prompt? Suppose first that
you type the symbol ten and press <RETURN>. If Scheme has not previously
been given a meaning for the symbol ten, we say that ten has not been bound
to a value. In the evaluate phase of the read-eval-print loop, no value is found

Data and Operators


for ten, and a message is printed informing you that an error was maxie and
describing the nature of the error. For example,

[2] ten <RETURN>


Error: variable ten not bound.

(The actual message printed depends on the implementation of Scheme you


are using.) How then do we assign a meaning or value to a symbol? Suppose
we want to assign the value 10 to the symbol ten. For this purpose we use
a define expression. (A define expression is an example of a special form: a
form of expression identified by a special symbol called a keyword, which in
this case is define.) The define expression is entered after the next prompt
as follows:

[3] (define ten 10) <RETURN>

In this example, Scheme evaluates the third subexpression, which has the value
10, assigns that value to the symbol ten, and finally, in our implementation
of Scheme, prints the next prompt. Since the value returned by a define
expression is never used, that value is not prescribed in the specification of
the language. For convenience in writing this book, we opt to suppress the
value returned by a define expression.
Now let's see what happens when we enter the symbol ten:

[4] ten <RETURN>


10

This time, Scheme successfully evaluates the variable ten, so it prints the
value 10.
We have seen that a variable is a symbol to which a meaning (i.e., a value)
can be given. When a value is we say that the variable is
given to a variable,
bound to that value. In our previous example, the symbol ten is a variable
bound to the value 10. In general, if var represents a variable and expr
represents an expression whose value we would like to bind or assign to var,
we accomplish the assignment by writing

(define var expr)

The define expression is made up of a keyword, a variable name var, and an


expression expr.
Now let's suppose that ten is bound to 10 and we want Scheme to print not
the value 10 but instead to print the symbol ten. We want to have some way

1.3 Numbers and Symbols


Scheme not to evaluate ten but to print its literal value ten. The
of telling
mechanism that Scheme provides for doing this is called quoting the symbol.
We quote a symbol by enclosing in parentheses the word quote followed by
the symbol:

(quote symbol)

For example, you quote the symbol ten by writing (quote ten). If you type
(quote ten) and then <RETURN> in response to a Scheme prompt, you see

[5] (quote ten) <RETURN>


ten

From now on, we shall omit the <RETURN> notation. It is understood that
each line that we type must be followed by <RETURN>. We use the word enter
when we want to indicate that something is to be typed in response to the

Scheme prompt. The value that Scheme prints in response to what we enter is

said to be the value that the expression "evaluates to" or that is "returned."
For example, we could have said, "If the symbol ten is bound to 10, and you
enter (quote ten), then Scheme evaluates it to ten, while if you enter ten,
Scheme evaluates it to 10."
In all cases, whether a symbol is bound to some value or not, when a
quoted symbol is entered, the literal symbol is returned. Thus if we enter
(quote abc3). Scheme returns abc3. It is not necessary to quote numbers,
for the value of a number as an expression is the number itself.

[6] (quote abc3)


abc3
[7] (quote 12)
12

An object whose value same as the object itself is called a constant. At


is the
this point, the only constants we have seen are numbers.
It is somewhat inconvenient to have to type so much each time we want to

quote a symbol, so an abbreviation for the quoting process is also available

in Scheme. we need only place an apostrophe


In order to quote a symbol,
immediately before the symbol. Thus to quote the symbol ten, we simply
write 'ten. The apostrophe is referred to as "quote," and the expression
'ten is verbalized as "quote ten." Thus the responses to the prompts [6]
and [7] can also be made as follows:

10 Data and Operators


[6] 'abc3
abc3
[7] '12
12

We can also assign to a variable a value that is the literal value of a symbol.
For example, if we enter the following:^

[8] (define Robert 'Bob)

we bind the variable Robert to the symbol Bob. When we next enter Robert,
we get

[9] Robert
Bob

so that Scheme has evaluated Robert and returned the value Bob.
We have two types of data so far,
numbers and symbols. How are they used?
The use of numbers should be no surprise, since we usually think of doing
arithmetic operations on numbers to get answers to problems. We shall take
a brief look at how we do arithmetic in Scheme in this section and then return
for a more complete look at using numbers in Chapter 3. To perform the
arithmetic operations on numbers, Scheme uses prefix notation; the arithmetic
operator is placed to the left of the numbers it operates on. The numbers on
which it operates are called the operands of the operator. Furthermore, the
operator and its operands are enclosed in a pair of parentheses. Thus to add
the two numbers 3 and 4, we enter (+3 4) and Scheme evaluates it and
returns the answer 7. On the computer screen it looks like this:

[10] (+ 3 4)
7

^ We are mixing lower and uppercase letters in ovir symbols and showing that Scheme
returns the same mix of lower and uppercase letters as their literal values. Thus, if we
enter 'Bob, Scheme retvims Bob. An implementation of Scheme that preserves the case of
letters is called case preserving, and in this book, we are assuming that the implementation
is case preserving. There are some implementations that are not case preserving, which

means that the case is changed to either all lowercase or all uppercase letters. Thus, in
some implementations, all letters are returned in lowercase, and when we enter Bob, Scheme '

evaluates it to bob. Other implementations that are not case preserving return all uppercase
letters, so that if we enter 'Bob, Scheme evaluates it to BOB.

1.3 Numbers and Symbols 11


Multiplication is performed with the operator *, subtraction with — and,

division with /. How do we compute the arithmetical expression 3 — 5)?


x (12
In prefix notation, we place the multiplication operator * first followed by the
first number The second operand to the operator * is the difference between
3.

12 and 5, which itself is written as (- 12 5). Thus the whole arithmetic


expression is entered as

[11] (* 3 (- 12 5))
21
[12] (+ 2 (/ 30 15))
4

In general, Scheme uses this prefix notation whenever it applies any kind
of operator to its operands. We shall return to a more complete discussion
of numerical computations in Chapter 3. A number of experiments with
numerical operations are included in the exercises at the end of this section.
Insummary, a symbol can be bound to a value using a special form that
begins with the keyword define. When a variable that has been bound to
a value is entered in response to a Scheme prompt, its value is returned. If
we want Scheme to return the literal value of the symbol instead of the value
to which it is bound, we quote the variable. The value of a quoted symbol is
just the literal value of the symbol.
It is possible to keep a record of the session you have in Scheme. The
particular mechanism for doing so depends on the implementation of Scheme
you are using. If you are using a version of Scheme that uses the windowing
capability of the computer, you may be able to send what is in the window
to a file. In some implementations, it is possible to run Scheme in an editor
and use the saving capability of the editor to preserve what you want from
the session in a file. Some versions offer a transcript facility that you turn
on at the beginning of the session and give it a filename, and then turn off
at the end of the session. The session is then preserved in the named file.
The manual for Scheme you are using should identify the facility you have
the
available to save your Scheme sessions.
We strongly recommend that you try each of the things discussed in this
book at the computer to see how they work. Feel free to experiment with
variations on these ideas or anything else that occurs to you. You get a
much better feeling for computers and for Scheme if you "play around" at the
keyboard.

12 Data and Operators


2 1

Exercises

Exercise 1.1
Find out what method your implementation of Scheme has for recording your
Scheme session in a file. Bring up Scheme on the computer and record this
session in a file called "sessionl.rec." Enter each of the following to successive
prompts: 15, -200, 12345678901234, (quote alphabet -soup), 'alphabet-
soup, '
'alphabet-soup. (Note: Experiment with entering even larger posi-
tive and negative whole numbers and decimals and see what is returned.)

Exercise 1.2
Assume that the following definitions have been made in the given order:

(define big-number 10500900)


(define small-number 0.00000025)
(define Cheshire 'cat)
(define number 1 big-number)
(define number2 'big-number)

What values are returned when the following are entered in response to the
prompt?
a. big-number b. small-number
c. 'big-niunber d. Cheshire
e. 'Cheshire f. number 1
g. number h. ' number
Conduct the experiment on the computer in order to verify your answers.

Exercise 1.3
What is the result of entering each of the following expressions in response to
the Scheme prompt? Verify your answer by performing these experiments on
the computer.

a. (- 10 (- 8 (- 6 4)))

b. (/ 40 (* 5 20))

c. (/ 2 3)

d. (+ (* 0.1 20) (/ 4 -3))

Exercise I.4
Write the Scheme expressions that denote the same calculation as the following
arithmetic expressions. Verify your answers by conducting the appropriate
experiment on the computer.

1.3 Numbers and Symbols 13


a. (4x 7) -(13 + 5)

b. (3x(4 + (-5--3)))
c. (2.5^(5 X (1^10)))

d. 5 X ((537 X (98.3 + (375 - (2.5 x 153)))) + 255)

Exercise 1.5
If a, /?, and 7 are any three numbers, translate each of the following Scheme
expressions into the usual arithmetical expressions. For example:

(+ a (+ /9 7)) translates into a + (,3 + 7)

a. (+ a (- (+ /? 7) a))

b. (+ (* a /?) (* 7 /?))

c. (/ (- a /?) (- Q 7))

1.4 Constructing Lists

So we have seen two data types, symbols and numbers. Another important
far,

data type in Scheme is lists. We all use lists in our daily lives shopping lists, —
laundry lists, address lists, menus, schedules, and so forth. In computing, it

is also convenient to keep information in lists and to be able to manipulate


that information. This section shows how to build lists and how to perform
simple operations on lists. In Scheme, a list is denoted by a collection of items
enclosed by parentheses. For example, (1 2 3 4) is a list containing the four
numbers 1, 2, 3, and 4. A special list that we make frequent use of is the
empty list, which contains no items. We denote the empty list by ().
Scheme provides a procedure to build lists one element at a time. This
procedure is called cons, a shortening of "constructor." We refer to cons as a
constructor of lists. We now look at how cons works. We shall first perform
a number of experiments and then describe its general behavior. Suppose we
want to build a list that contains only the number 1. We enter the following:

[1] (cons 1 '())


(1)

We see from this example that we enclosed three things in parentheses: the
variable cons, the number 1, and the empty list '(). The first entry tells us

1.^ Data and Operators


.

the name we are applying, and the remaining two entries


of the procedure
tell what the procedure cons is operating on. The entries following the name
of the procedure are called the operands of the procedure. The values of
the operands are called the arguments of the procedure. In our case, the
first argument is the first item in the list we are constructing, and the second
argument is a list that contains the rest of the items in the list we are building.
Scheme first reads what we enter. In its evaluation phase, the operands are
evaluated, and the desired list is built. It then prints the list (1). (Note the
parallel between the application of cons and the application of the arithmetic
operations such as (+ 3 4). We again see that the operator is placed to the
left of the operands, using prefix notation.) Let us bind the variable Isl to
the list containing the number 1 by writing

[2] (define Isl (cons 1 '()))


[3] Isl
(1)

The define expression we entered at the prompt [2] binds the variable Isl
to the value obtained by evaluating the subexpression (cons 1 '()). That
subexpression evaluates to the list (1). Thus Isl is bound to the list (1).
Thus when the variable Isl is entered at the prompt [3], its value (1) is

returned.
We now create a list with 2 as its first element and the elements of Isl as
the rest of its elements. To accomplish this we write

[4] (cons 2 Isl)


(2 1)
[5] Isl
(1)

Once again, the two operands are evaluated —2 evaluates to itself and Isl
to the list ( 1 ) . Then a new list is formed having 2 as its first item and the
items of Isl as the rest of its items, giving us (2 1) . This is the value that is

returned. At prompt [5], we verify that Isl is unchanged. Let us next bind
the variable ls2 to a list like the one in [4]

[6] (define ls2 (cons 2 Isl))


[7] l82
(2 1)
[8] Isl
(1)

t.4 Constructing Lists 15


The expression entered at the prompt [9] binds the variable c to the literal
value of the symbol three. We can now create a list containing three as its

first element and the elements of ls2 as the rest of its elements by writing

[9] (define c 'three)


[10] (cons c ls2)
(three 2 1)

When we apply cons to its two operands, the operands are both evaluated.
The first operand, c, evaluates to three, and the second operand, ls2, evalu-
ates to the list (2 1). Then a new list is built with three as its first item and
the elements of the list (2 1) as the rest of its elements. The value (three
2 1) is returned.
Continuing our experiment, we bind the variable ls3 to the value of (cons
c ls2) using a define expression:

[11] (define ls3 (cons c ls2))

We now perform another experiment with cons. Let us build a list that
has as its first item the list ls2 and as the rest of its items the same items
as those in ls3. This is done by making ls2 the first operand and ls3 the
second operand of cons:

[12] (cons ls2 ls3)


((2 1) three 2 1)

[13] ls3
(three 2 1)

[14] (define ls4 (cons ls2 ls3))

The first operand of cons evaluates to the list (2 1), and the second operand
of cons evaluates to (three 2 1). Thus the procedure cons produces a new
list that has as its first item the list (2 1). followed by the elements in IsS.
This gives us the value that was returned by Scheme: ((2 1) three 2 1).

Notice that when ls3 was entered in response to the prompt [13] . (three 2
1), the original value of ls3, was returned, so cons did build a new list and
did not affect the list ls3.
We now in a position to summarize the facts that we observed in the
are
experiments. The procedure cons takes two operands. We apply the proce-
dure cons to these operands by enclosing the procedure name cons followed
by its two operands in parentheses. In general, a procedure name followed by
its operands, all enclosed in a pair of parentheses, is called an application, and

16 Data and Operators


we say that the operator is applied to its operands. When an application is

evaluated by Scheme, all of the expressions in the list are evaluated in some
unspecified order. The value of the first expression (the operator) informs
Scheme of the kind of computation that is to be made (in our case, cons in-
forms Scheme that a list is to be constructed). Then the computation defined
by the procedure (the value of the operator) is performed on the arguments,
which are the values of the operands. We assume for now that the second
operand of cons evaluates to a list (which may be the empty list). Then a
new list is created containing the value of the first operand as its first item
followed by all of the items in the list to which the second operand evaluated.
It is this new list that is returned as the value of the application. Since cons
first evaluates its operands, the lists contain only values. So far, these values
may be numbers, the literal value of symbols, and lists of these items. As
we progress through the chapters of this book, we shall encounter other data
types, all of which can be included in lists.

We have assumed in the discussion that the second operand of the cons
application evaluates to a list. This is the usual situation that we shall en-
counter, but it is also possible for the second argument to cons not to be a
list. We shall discuss this case in the next section. Furthermore, we see in ls4
that a list may contain in it other lists. We say that the inner list is nested
within the outer list. The nesting may be several levels deep, for a nested
list may itself contain nested lists. Suppose we have a given list. Items that
are not nested within lists contained in the given list are called the top-level
items of the given list. Thus, if the given list is ((a b (c d)) e (f g) h),
the top-level items are the list (a b (c d)), the symbol e, the list (1 g),
and the symbol h.

We can also build the list (2 1) in one step by applying cons twice as the
next experiment illustrates:

[15] (cons 2 (cons 1 '()))


(2 1)

To construct the list ((2 1) three 2 1), we could write

[16] (cons (cons 2 (cons 1 '())) (cons 'three (cons 2 (cons 1 '()))))
((2 1) three 2 1)

The second and third cons's build the list (2 1), and the fourth, fifth, and
sixth build the list (three 2 1). Then the first cons constructs the desired
list.

1.4 Constructing Lists 17


We
have used parentheses in writing several types of expressions in the —
application of a procedure to its arguments, in the special form with keyword
define, and in a list of values. When Scheme sees an expression enclosed
in parentheses, it assumes that the first item following the left parenthesis
evaluates to a procedure such as cons or is a keyword such as define. ^ It

then evaluates the expression according to what the first item tells it to do.

What happens when we enter an expression such as (2 1) in response to a


Scheme prompt?

[17] (2 1)

Error: bad procedure 2

This experiment shows that Scheme expected to see an application or special


form, and when the first item in the list is not an operator or a keyword, it

returned a message saying it detected an error. In this case it tried to treat


the list as an application but discovered as its first item the number 2, which
is not a procedure.
Is there some way to enter a list of items that is to be taken literally? The
answer is yes. Suppose we want to enter a list containing the following items:
three. 2. 1. We use the quote symbol (apostrophe) and place it in front of
the left parenthesis. This indicates that each of the items included in the
parentheses is to be taken with its literal value. Thus to get a list containing
the desired three items, we would enter '(three 2 1). The symbol three
should not be quoted within the parentheses since the outer quote already
indicates that it should be taken with its literal value. Let's look at some
more examples:

[18] '((2 1) three 2 1)


((2 1) three 2 1)
[19] '(a b (c (d e)))
(a b (c (d e)))
[20] (cons '(a b) ' (c (d e)))
((a b) c (d e))

We now have a way of indicating whether a list we enter consists of literal


values. If the expression beginning with a parenthesis is not quoted, Scheme

^ We forms in the coming chapters and then study their properties


shall use sever£il special
more Chapter 14. They are ceilled special because their operands are not evaluated
fvilly in
as in procedure applications. If (define ten 10) were evciluatedcis a procedure application,
the operands ten and 10 would first be evaluated, but since ten has not yet been bound,
an error would result. In this special form, the symbol ten is not evaluated.

18 Data and Operators


assumes that the expression is not a quoted list, and the first item in the list

is examined to determine the nature of the expression and the computation


that should follow. If the expression in parentheses is quoted, Scheme assumes
that each item is to be taken literally.

We have now seen several procedures: the arithmetic operators +, *, -, and


/, and the list-manipulating operator cons. Procedures form another type
of data in Scheme. We have now encountered four types of data: numbers,
symbols, lists, and procedures.

Exercises

Exercise 1.6
Using the symbols one and two and the procedure cons, we can construct the
list (one two) by typing (cons 'one (cons 'two '())). Using the symbols
one, two, three, and four and the procedure cons, construct the following
lists without using quoted lists (you may use quoted symbols and the empty
list):

a. (one two three four)

b. (one (two three four))

c. (one (two three) four)

d. ((one two) (three four))

e. (((one)))

Exercise 1.1
Consider a list Is containing n values. If a evaluates to any value, how many
values does the list obtained by evaluating (cons a Is) contain?

Exercise 1.8
What is the result of evaluating '(a 'b)? (Try it!) Explain this result.

1.5 Taking Lists Apart

We have seen how to build lists using the constructor cons. We now consider
how to take a list apart so that we can manipulate the pieces separately and
build new lists from old. We accomplish this decomposition of lists using two

1.5 Taking Lists Apart 19


selector procedures, cax and cdr.' If Is represents a nonempty list of items,
car applied to Is gives the first item in Is, while cdr applied to Is gives the
list consisting of all items in Is with the exception of its first item. Both car

and cdr take one operand that must evaluate to a nonempty list. Both car
and cdr are not defined on an empty list, and applying them to an empty list
produces an error.

Let's look at the behavior of the selector catr. When its argument is a
nonempty list, it returns the first top-level item in the list. Thus we have

[1] (car '(1 2 3 4))


1

It is rather space consuming to indicate what a procedure returns by repro-


ducing what is seen on the computer screen. We sheill adopt a more efficient
notation in which we express the above by

(car '(1 2 3 4)) => 1

The double arrow "^^" is read as "evaluates to" or "returns." Here are some
other examples of applying the procedure car (As in the previous section, ls4
is bound to the list ((2 1) three 2 1).)

(car * (a b c d)) =^ a
(car ls4) => (2 1)
(car '((1) (2) (3) (4)))=> (1)
(car '(ab (cd ef) gh)) => ab
(car '(((hen cow pig)))) ^^ ((hen cow pig))
(car •(())) =>

When the selector cdr is applied to an argument that is a nonempty list,

the list returned is obtained when the first item (the caur) of the eirgument
list is removed. Thus

(cdr '(1 2 3 4)) => (2 3 4)


(cdr ls4) =* (three 2 1)

' The symbol cdr is pronoiinced "coiild-er." The nsmies car and cdr had their origin in the
way the hst-processing language LISP wa^ originally implemented on the IBM 704, where
one could reference the "address" and "decrement" parts of a memory location. Thus car
is an acronym for "contents of address register," and cdr is an acronym for "contents of

decrement register."

20 Data and Operators


(cdr '(a (b c) (d e f))) =* ((b c) (d e f))
(cdr '((ant hill) (bee hive) (wasp nest)))
=* ((bee hive) (wasp nest))
(cdr '(!)) =>
(cdr '((1 2))) =* ()
(cdr '(())) =» ()

We now have three list-manipulating procedures: the constructor cons and


the two selectors car and cdr. By applying these in succession, we can do
almost anything we want with lists. For example, if we want to get the second
item in the list (a b c d), we first apply cdr to get (b c d) and then apply
car to the result to get b. We combine these applications of cdr and car into
one expression by writing

(car (cdr '(a b c d))) =* b

For the next example, let list-of-names be bound to the list ((Jane Doe)
(John Jones)). We look at how we retrieve Jane Doe's last name from this

list. If we first apply car to list-of -names, we get the list (Jeme Doe). We
now get the list (Doe) by applying cdr, and finally, we get Doe by applying
car. We want to emphasize the distinction between the list (Doe) containing
one item and the item Doe itself. All of these steps are combined in the

following expression:

(ccir (cdr (car list-of-names) ) ) => Doe

In this example, we see that the procedures car and cdr are applied in

succession a number of times. The successive applications of car's and cdr's


is facilitated by the use of the procedures caar, cadr, caddr, . .
.
, cddddr. The
number of a's and d's between the c and r tells us how many times we apply
car or cdr, respectively, in order from right to left. For example, (cadr '
(a
b c)) is equivalent to (car (cdr '(a b c))) and is b. Similarly, (caddr
'(a b c)) is equivalent to (car (cdr (cdr '(a b c)))) and is c. We can
put up to four letters (a's or d's) between the c and r. We make use of these
procedures in the next example.
Consider the following situation. We ask our helper to prepare a menu that
has on it the two items: chicken soup and ice cream. He prepares the menu
by using a define expression to bind the variable menu to the list (chicken
soup ice cream):

(define menu '(chicken soup ice cream))

1.5 Taking Lists Apart 21


We find this unsatisfactory and want to use the items in the list menu to
produce the list ((chicken soup) (ice cream)), which groups together the
related items. We build the new list one step at a time:

(car menu) =^ chicken


(cadr menu) =^ soup
(cons (cadr menu) '()) =* (soup)
(cons (car menu) (cons (cadr menu) '())) ^^ (chicken soup)
(cddr menu) =^ (ice cream)

We now have the two items that will make up our final list. We use cons to
build the final answer. We first use cons to build a list around the list (ice
cream) to get ((ice cream)) and then use cons again to build a list that
has (chicken soup) as its first item and (ice cream) as its second item.

(cons (cddr menu) '()) =^ ((ice cream))


(cons (cons (ceir menu) (cons (cadr menu) '())) (cons (cddr menu) '()))
=* ((chicken soup) (ice cream))

The shown here can be used to build and manipulate lists in just
process
about any way we want. As we learn more about Scheme, we shall discover
shortcuts that facilitate the manipulation of lists.

Up we have assumed that the second argument to cons is a list. If


to now,
it is not a list, we can still apply cons; the result, however, is not a list but
rather a dotted pair. A dotted pair is written as a pair of objects, separated by
a dot (or period) and enclosed by parentheses. The first object in the dotted
pair is the car of the dotted pair, and the second object in the dotted pair
is the cdr of the dotted pair. Thus (cons 'a 'b) =* (a . b),and (car
'(a . b)) => a, while (cdr '(a . b)) =* b. Much of the work in this
book involves lists, which are built out of dotted pairs. For example, '
(a .

()) => (a), and '


(a . (be)) => (a b c). Thus any item built with
the constructor cons is referred to as a pair.

Exercise

Exercise 1.9
If a and /? evaluate to any values, what is

a. (ccir (cons q /?))

b. (cdr (cons q /?))

22 Data and Operators


The procedures cons, car, and cdr do not alter their operands. Let us
demonstrate this with an experiment.

[1] (define a 10)


[2] (define Is-b '(20 30 40))
[3] (car Is-b)
20
[4] (cdr Is-b)
(30 40)
[5] (cons a Is-b)
(10 20 30 40)
[6] a
10
[7] Is-b
(20 30 40)

After all of these operations involving car, cdr, and cons, the values of the
operands a and Is-b stayed the same when they were entered in [6] and [7]
as they were when they were defined in the beginning.
So we have encountered three procedures car, cdr, and cons that
far, — —
help us manipulate lists and four procedures +, *, -, and / —
that allow us to —
operate on numbers. Another group of procedures, called predicates, applies
a test to their arguments and returns true or false depending on whether the
test is passed. Scheme uses #t to denote true and #f to denote false. The
value of #t is #t and the value of #f is #f so both of these are constants.* #t
,

and #f representing true and false, are known as boolean (or logical) values.
,

They form a separate type of data to give us five distinct types: numbers,
symbols, booleans, pairs (including lists), and procedures. More data types
will be introduced in later chapters. We now look at several predicates that
apply to these five data types.
The first predicate tests whether its argument is a number, and its name is

number?. Like most other predicates, the name ends with a question mark,
signaling that the procedure is a predicate. Thus if we apply the predicate
number? to some object, #t is returned if the object is a number, and otherwise
#f is returned. If we make the following definitions,

(define num 35.4)


(define twelve 'dozen)

* In some implementations of Scheme, the empty hst () is returned instead of tf to indicate


false.

1.5 Taking Lists Apart 23


.

we get the following results:

(number? -45.67) =» #t
(number? '3) =* #t
(number? num) =^ #t
(number? twelve) ^^ #f
(number? 'twelve) ==* #f
(number? (+2 3)) => #t
(number? #t) ==* #f
(number? (car '(15.3 -31.7))) =» #t
(number? (cdr '(15.3 -31.7))) => #f

In the last example, the operand evaluates to (-31.7), which is a list, not a
number.
The predicate symbol? tests whether its argument is a symbol. With the
definitions of num and twelve given above, we get the following results:

(symbol? 15) => #f


(symbol? num) ^^ #f
(symbol? 'num) =^ #t
(symbol? twelve) =^ #t
(symbol? 'twelve) ^^ #t
(symbol? #f) => #f
(symbol? (car '(banana creeim))) =^ #t
(symbol? (cdr ' (banaina creaun))) ^^ #f

In the Icist example, (cdr '


(beuieina cream) ) evaluates to a list, not a symbol.
There is also a predicate boolean? to test whether its argument is one of
the boolean values #t or #f

(boolean? «t) ==* «t


(boolean? (number? 'a)) =^ #t
(boolean? (cons 'a '())) => #f

A pair is an object built by the constructor cons, and the predicate pair?
tests whether its argument is a pair. For example, nonempty lists are con-
structed by cons, so they are pairs. We have

(pair? '(Ann Ben Carl)) ^ «t


(pair? '(!)) =^ #t
(pair? '()) => »f
(pair? '(())) => «t

24 Data and Operators


(pair? '(a (b c) d)) =» «t
(pair? (cons 'a '())) =* #t
(pair? (cons 3 4)) =» #t
(pair? 'pair) =* #f

There is also a predicate null? which tests whether its argument is the
empty list.

(null? '()) =* #t
(null? (cdr '(cat))) => «t
(null? (car '((ab)))) => #f

Exercises

Exercise 1.10
If the operands oc and evaluate to any values, what is

a. (symbol? (cons a /?))

b. (pair? (cons oc 0))

c. (null? (cons o; /?))

d. (null? (cdr (cons a '())))

Exercise 1.11
If a list Is contains only one item, what is (null? (cdr Is) )?

We have given tests to determine whether an object is a number, a symbol,


a boolean, or a list, but we have not given a test to determine whether it is

a procedure. There is also a predicate procedure? which tests whether its

argument is a procedure.

(procedure? cons) ==* #t


(procedure? +) ==* #t
(procedure? 'cons) ^^ #f
(procediire? 100) =» #f

At this point, we have introduced five data types: numbers, symbols,


booleans, pairs, and procedures. As we progress through the book, we shall
meet other data types, such as strings, characters, vectors, and streams. A
question that we often ask is whether two objects are the same. Scheme offers

t.5 Taking Lists Apart 25


several different predicates to test for the sameness of its arguments. Which
predicate you use depends upon the information you seek and the data type
of the objects. We list a number of these sameness predicates below and in-
troduce others as the need arises. When both objects are numbers, we use the
predicate = to test whether its arguments represent the same number. The
predicate = is used only to test the sameness of numbers. It is safe to use it

only on integers, since the representation of nonintegers in the computer can


lead to undesirable results.

(= 3 (/ 6 2)) =^ #t
(= (/ 12 2) (* 2 3)) => »t
(= (car '(-1 ten 543)) (/ -20 (• 4 5))) => #t
(= (* 2 100) 20) ==> #f

There is also a predicate eq? to test the sameness of symbols. If its operands
same symbol, #t is returned. For
evaluate to the this example, assume that
Garfield has been bound to 'cat.

(eq? 'cat 'cat) ^ #t


(eq? Garfield 'cat) ^ #t
(eq? Garfield Gcirf ield) =» #t
(eq? 'Garfield 'cat) ^ #f
(eq? (car '
(Garfield cat)) 'cat) =» #f
(eq? (car ' (Garfield cat)) 'Garfield) =^ «t

The predicate eq? returns #t if its two arguments are identical in all re-

spects; otherwise it returns #f . Symbols have the property that they are
identical if they are written with the same characters in the same order. Thus
we use eq? to test for the sameness of symbols. On the other hand, each appli-
cation of cons constructs a new and distinct pair. Two pairs constructed with
separate applications of cons will always test #f using eq? even if the pairs
they produce look alike. For example, let us make the following definitions:

[1] (define Is-a (cons 1 '(23)))


[2] (define Is-b (cons 1 '(23)))
[3] (define Is-c Is-a)

Then we have

26 Data and Operators


[4] (eq? (cons 1 '(2 3)) (cons 1 '(23)))
«f
[5] (eq? Is-a '(cons 1 '(23)))
#f
[6] (eq? Is-a Is-b)
ftf

[7] (eq? Is-a Is-c)


#t

In [4] , cons is applied twice to build two distinct pairs, so #f is returned


even though both of the pairs look alike as lists (1 2 3). In [5] , the variable
Is-a refers to the pair defined in [1] , which is distinct from the pair defined
by the cons in [5], so #f is returned. In [6], Is-b refers to the pair built

by the cons in [2] , which is distinct from that built in [1] , so eq? again
evaluates to #f . Finally, Is-c is defined to be the value of Is-a, which is the
pair built by the cons in [1] , so both Is-a and Is-c refer to the same pair,

and eq? evaluates to #t.


When we want to include numbers, symbols, and booleans in the types of

objects the predicate tests for sameness, we use the predicate eqv?. We shall
later see that eqv? also tests vectors, strings, and characters for sameness.

(eqv? (+ 2 3) (- 10 5)) => #t


(eqv? 5 6) =» «f
(eqv? 5 'five) =* #f
(eqv? 'cat 'cat) =* «t
(eqv? 'cat 'kitten) => «f
(eqv? (ccir '(a a a)) (car (cdr '(a a a)))) =* #t

We have not included lists among the data types we can test for sameness
using the predicates discussed. If we want a universal sameness predicate
that can be applied to test numbers, symbols, booleans, procedures, and lists

(and strings, characters, and vectors), we use the predicate equal?. In the
case of pairs constructed using separate applications of cons, equal? tests the
corresponding entries, and if they are the same, #t is returned. Thus equal?
tells us that the two lists (cons 'a '(be d)) and (cons 'a '(be d))are
the same, whereas eq? and eqv? claim that they are diff"erent.

(equal? 3 (/ 6 2)) => #t


(equal? 'cat 'cat) ==* #t
(equal? ' (a b c) (cons 'a ' (b c))) => #t
(equal? (cons 1 '(2 3)) (cons 1 '(2 3))) => «t
(equal? '(a (b c) d) '(a (b c) d)) => #t

1.5 Taking Lists Apart 27


(equal? '(a (b c)) '(a (c b))) =» #f
(equal? (cdr '(a c d) ) (cdr '(b c d))) =» #t

Now for the obvious question: How do we know which one to use? When
a predicate must first test to determine the type of its arguments, it is less

efficient than one designed specifically for the type of its arguments. Thus for
numbers, = is the most efficient sameness predicate. Similarly, for symbols,
eq? is the most efficient predicate. For testing only numbers or symbols, eqv?
is more efficient than equal?. When we know that we shall be using numbers
or symbols, then eqv? is the sameness predicate we use. When the discussion
is limited to numbers, we use =.

When we respond to a prompt with a number or a quoted symbol, we


have seen that the number or symbol is returned. Ifwe enter a symbol that
has been bound to a value, that value is returned. If we apply a procedure
such as car to a list (1 2 3) by entering (car '
(1 2 3)), the expression is

evaluated and the value 1 is returned and printed on the screen. On the other
hand, not every Scheme object is printable. If we enter only the name of a
procedure, such as car, the procedure, which is the value of car, is returned,
but not printed; instead a message is displayed, which indicates a procedure.
In this book, we indicate a procedure by printing angle brackets surrounding
the name of the procedure in italics. Thus, when we enter car, <car> is

displayed. In general, when we use <som€-symbol>, it denotes a procedure.


We now summarize our discussion of cons, car, cdr, and predicates by
writing some facts that apply to their use. The list is certainly not all inclusive,
and we recommend that you add your own entries to it to reinforce your
understanding of the use of predicates. Let a and /? be operands such that a
evaluates to any value and f3 evaluates to any nonempty list. We then have:

The number of items in (cons a /?) is one greater than the number of
items in /?.

(eq? a /?) => #t


implies
(eqv? a /?)==> #t
implies
(equal? a /?) =* #t

(eq? (cons a f5) (cons a (3)) ==> #f

(eqv? (cons a 0) (cons a /?)) =* #f


(equal? (cons a /3) (cons a /?)) =* #t

(boolean? (eqv? a /?) ) =* #t

Data and Operators


(null? (cdr (cons a '()))) => #t

(equal? (cons (car /?) (cdr /?)) /3) => #t


(equal? (car (cons a (3)) a) ^^ #t
(equal? (cdr (cons a (3)) (3) =i* #t
(null? 13) => #f
(pair? 13) => #t
(pair? (cons a (3)) =i> #t
(pair? (cons a\ Q2)) =^ #t

We have been introduced to five basic data types (numbers, symbols, bool-
and procedures), and we have seen a number of procedures to
eans, pairs,
manipulate and test the data. In Chapter 2 we shall develop the tools to
compute with lists, and in Chapter 3 we shall do the same for numbers.

Exercises

Exercise 1.12
Evaluate each of the following.

a. (cdr '((a (b c) d)))

b. (car (cdr (cdr '(a (b c) (d e)))))

c. (car (cdr '((1 2) (3 4) (5 6))))

d. (cdr (car '((1 2) (3 4) (5 6))))

e. (car (cdr (car '((cat dog hen)))))

f. (cadr '(a b c d))

g. (cadar '((a b) (c d) (e f)))

Exercise 1.13
We can extract the symbol a from the list (b (a c) d) using car and cdr
by going through the following steps:

(cdr '(b (a c) d)) => ((a c) d)


(car (cdr ' (b (a c) d))) => (a c)
(car (car (cdr ' (b (a c) d)))) ^^ a

For each of the following lists, write the expression using car and cdr that
extracts the symbol a:

1.5 Taking Lists Apart 29


4

a. (b c a d)

b. ((b a) (c d))

c. ((d c) (a) b)

d. (((a)))

Exercise 1.1
Decide whether the following expressions are true or false:

a. (symbol? (car ^(cat mouse)))

b. (symbol? (cdr '((cat mouse))))

c. (symbol? (cdr '(cat mouse)))

d. (pair? (cons 'hound '(dog)))

e. (pair? (car '(Cheshire cat)))

f. (pair? (cons '() '()))

Exercise 1.15
Decide whether the following expressions are true or false:

a. (eqv? (car '(a b)) (car (cdr ' (b a))))

b. (eqv? 'flea (car (cdr '(dog flea))))

c. (eq? (cons 'a ' (b c)) (cons 'a ' (b c)))

d. (eqv? (cons 'a ' (b c)) (cons 'a ' (b c)))

e. (equal? (cons 'a ' (b c)) (cons 'a ' (b c)))

f. (null? (cdr (cdr '((a b c) d))))

g. (null? (car '(())))

h. (null? (car '((()))))

30 Data and Operators


Procedures and Recursion

2.1 Overview

In Chapter 1 we used Scheme procedures such as those bound to the


several
numerical operators +, *, -, and /, the list-manipulating procedures bound to
cons, car, and cdr, and the predicates that test their arguments and return
#t or #f One of the advantages of using the programming language Scheme
.

is that the number of procedures provided by the language is relatively small,

so we do not have to learn to use many procedures in order to write Scheme


programs. Instead, Scheme makes it easy for us to define our own procedures
as we need them. In this chapter, we discuss how to define procedures to
manipulate lists. In Chapter 3, we shall see how to define procedures to do
numerical computations. In this chapter, we also discuss how a procedure
can call itself within its definition, a process called recursion. Finally, we
introduce an elementary tracing tool to help us in debugging programs.

2.2 Procedures

The notation f{x,y) is used in mathematics to denote a function; it has the


name / and hcis two variables, x and y. We call the values that are given to
the variables the arguments of the function. To each pair of arguments, the
function associates a corresponding value. In computing, we are concerned
with how that value is produced, and we speak about the sequence of com-
putational steps that we perform to get the value returned by the function as
an algorithm for computing the function's value. The way we implement the
algorithm on the computer to get the desired value is called a procedure for
computing the desired value. Iff is the name of the procedure with variables
X and y, we use a list version, (f x y), of the prefix notation f{x,y) used
in mathematics. In general, prefix notation places the procedure or function
name in front of the variables. In the list version of prefix notation, the whole
expression surrounded by parentheses, and within the parentheses, the name
is

of the procedure comes first, followed by the variables separated by spaces.


Although we used a procedure taking two arguments in this illustration, the
number of arguments depends on the procedure being used. For example, we
have already seen the procedure cons takes two arguments, and the procedure
car takes one.
Procedures such as those bound to the values of +, cons, car, cdr, null?,
eqv?, and symbol? are provided by the system as standard routines. It is

impossible for the system to provide all procedures needed. Therefore, it

is important to be able to define procedures as they are needed. Scheme


provides an elegant way of defining procedures based upon the lambda calculus
introduced by the logician Alonzo Church. (See Church, 1941.) We illustrate
this method with an example.
When we write (cons 19 '()), we get a list with one number in it, (19). If
we write (cons 'bit '()), we get a list with one symbol in it, namely (bit).
Now let's write a procedure of one variable that returns a list containing the
value given to that variable as its only element. We do it with a lambda
expression,

(leuabda (item) (cons item '()))

A lambda expression is an example of a special form: a form of expression


identified by a special symbol called a keyword, in this Ccise lambda.^
If the procedure defined by this lambda expression is applied to 19, the
parameter item, which is in the list following the keyword lambda, is assigned
(bound to) the value 19. Then the following subexpression (known as the
body of the lambda expression) is evaluated with the parameter item bound
to 19. The value of the body so obtained is returned as the value of the
application. In this case, it returns the value of (cons item '()), which is

(19). In summary, when a procedure that is the value of a lambda expression


is applied to some value, the parameter is bound to that value, and the body

^ Speciad forms look like applications but are not, and in order to recognize them, we have
to memorize the keywords, such as lambda and define. We shadl see other keywords later,
but the list of keywords we have to memorize is small.

S2 Procedures and Recursion


)

of the lambda expression is evaluated with this parameter binding. The value
of the body is returned as the value of the application of the procedure.
The lambda expression has the syntax

(Icimbda (parameteT . . . ) body)

The keyword lambda is followed by a list that contains the parameters. The
ellipsis (three dots) following parameter indicates that the list contains zero
or more parameters. The next subexpression is the body of the lambda expres-
sion. The value of a lambda expression is the procedure, which can be applied
to values appropriate for the evaluation of the body. These values must agree
in number with the number of parameters in the lambda expression's param-

eter list. When the procedure is applied, the parameters are bound to the
corresponding values, and the body is evaluated. The value of the body is
then the value of the application.
In general, when a procedure is applied, the syntax is

(operator operand . . .

where operator is a subexpression that evaluates to the procedure being ap-


plied, and the operands are subexpressions that evaluate to the arguments to
which the procedure is applied. We stress that the arguments are the values
of the operands. For example, in the application (* (+ 2 3) (-7 1)), the
operator * evaluates to the multiplication procedure, the two operands are
(+ 2 3) and (- 7 1), and the two arguments are 5 and 6. The value of the
application is then 30, the product of 5 and 6.

Thus to apply the procedure we defined above to build a list containing the
symbol bit, we enter

(dcimbda (item) (cons item '())) 'bit)

and we get as the result (bit). Similarly,

((lambda (item) (cons item '())) (* 5 6)) =J> (30)

It is awkward to write the whole expression

(lambda (item) (cons item '()))

each time we want to apply the procedure. We can avoid this by giving the
procedure a name and using that name in the procedure applications. This

2.2 Procedures S3
is done by choosing a name, say maike-list-of-one, for this procedure and
then defining make-list-of-one to have the desired procedure as its value.

We write

(define make-list-of-one (lambda (item) (cons item '())))

This is easier to read if we display the parts more clearly on separate lines as
follows:

(define make-list-of-one
(leimbda (item)
(cons item '())))

Scheme ignores any spaces in excess of the one space needed to separate
expressions. Scheme also treats <RETURN>'s as spaces until the one following
the last right parenthesis that is entered to close the first left parenthesis in
the expression. Thus Scheme reads the two ways of writing this definition
of make-list-of-one as the same Scheme expression. The indentation sets
off subexpressions, making the structure of the program easier to understand

at a glance. 2 To apply the procedure m«ike-list-of-one, we enter the


application

(make-list-of-one 'bit)

and (bit) is returned.


We have now written a program that builds a list containing one item.
Computer programs to perform various tasks are written by defining the ap-
propriate procedure to accomplish the desired tasks. As the tasks become
more complicated, there are usually different ways of defining the procedures
to achieve the desired results. It is the aim of this book to lead you through
a series of learning experiences that will prepare you not only to be able to
write such programs but to do so in a way that is efficient, elegant, and clear

to read.
A word is in order about the choice of names for procedures and parameters.
Since a symbol can have as many characters in it as we wish, programs will be
easier to read if we choose names that describe the procedure or parameter.

^ To make entering expressions some implementations of Scheme provide automatic


easier,
indenting eind peirenthesis matching. The automatic indenting places the cursor in the
proper position for the steirt of the next line, and the parenthesis matching indicates the
that a right p£irenthesis
left peu'enthesis is closing.

$4 Procedures and Recursion


Thus we used the name raeike-list-of -one for the procedure that converted
a value into a list containing the value. lambda expression in the
In the
definition of the procedure make-list-of-one, we selected the name item
for the parameter to indicate that it is expecting to be bound to the item that

is to be included in the list.

Now let's write a procedure called make-list-of-two that takes two ar-

guments and returns a list whose elements are those two arguments. The
definition is:

(define make-list-of-two ; This procedure creates


(lambda (iteml item2) ; a list of two items,
(cons iteml (make-list-of-one item2))))

The parameter list following the keyword leiinbda consists of two parameters,
iteml and item2. You may be wondering about the semicolons in the first and
second lines of the program and the statements following them. When Scheme
reads an expression, it ignores all semicolons and whatever follows them on a
line. This allows us to make remarks about the program so that the reader
looking at it will know the intent of the programmer. Such remarks are called
documeniaiion and can make understanding programs easier. By choosing
the names of variables carefully, you can reduce the amount of documentation
necessary to understand a program. The documentation can also precede or
follow the program if each line is preceded by a semicolon. In the programs
in this book, we try to select variable names that make such documentation
unnecessary. When we wish to make points of clarification, we shall state
them in the accompanying discussion.
We apply the procedure maike-list-of-two to the two symbols one and
two by writing

(mcJce-list-of-two 'one 'two) =* (one two)

When we defined the procedure make-list-of-two, we used the parameters


iteml and item2. When we applied the procedure meike-list-of-two, its

two arguments were the values of the operands 'one and 'two.
In Section 1.5, we saw how to take a list containing four items (menu was
bound to the list (chicken soup ice cream)) and build a new list containing
the same items but grouped into two lists, each containing two items. We can
use the procedure make-list-of-two to give us another way of doing that
grouping. We define a procedure called regroup that has as its parameter
list-of-4, which will be bound to a list of four items. It returns a list with
the items in list-of-4 regrouped into two lists of two items each. In the

2.2 Procedures 35
course of writing the definition of regroup, we shall find it clearer to make
use of certain other procedures, which express what we want to appear in the
list of the two items we create. We use these procedures in the definition of
regroup and then define them afterward. The order in which the definitions
are written does not matter, and it is often more convenient to use a procedure
in a definition where it is needed, and then to define it later. In the definition
that follows, we make use of two such helping procedures, first-group and
second-group.

(define regroup
(lambda (list-of-4)
(mcike-list-of-two
(first-group list-of-4)
(second-group li8t-of-4))))

The procedure meike-list-of-two is used to create a list of two items, the


first item being a list consisting of the first two items in list-of-4 and the
second consisting of the last two items in list-of-4. To construct the first

grouping, we use a helping procedure first-group that we define as:

(define first-group
(lambda (Is)
(meike-list-of-two (car Is) (cadr Is))))

We define the helping procedure second-group as:

(define second-group
(lambda (Is)
(cddr Is)))

When first-group is applied to list-of-4, the parameter Is is bound to

the list of four items and the helping procedure make-list-of-two is applied
to build the desired list consisting of the first two items in the list of four

items. Similarly, the helping procedure second-group produces the rest of

the list of four items following the first two, that is, the list consisting of the

last two items.


Now to get the new menu, we simply apply the procedure regroup to menu,

and we get the desired list:

(regroup menu) =* ((chicken soup) (ice cream))

36 Procedures and Recursion


What is gained by using these procedures over the method used in Chap-
ter 1 in which everything was expressed in terms of cons, car, cdr, and so
forth? The version in Chapter 1 is hard to understand when it is scanned, for
we have to pause to work out what the constructors and selectors are doing.
In the new version, you can look at the code for regroup and see immediately
that it is making a list of two items; the first group is again a list of two
items, the first two items in the list of four items, and the second group is a
list consisting of the remaining two items in the list of four items. By carefully
choosing the names of the procedures and parameters, we can make the pro-
grams easy to read and understand. In our case, the use of the three helping

procedures, maJte-list-of-two, first-group, and second-group, make the


program easier to understand. Often the helping procedures can be used in
many programs. In reality, helping procedures are ordinary procedures that
we happen to want to make use of in writing some program. Any procedure
can be used as a helping procedure.
We have defined procedures to build lists containing one item and two items.
Scheme provides a procedure list, which takes any number of arguments and
constructs a list containing those arguments. For example,

(list 'a 'b 'c 'd) => (a b c d)


(list '(1 2) '(3 4)) =* ((1 2) (3 4))
(list) =* ()

We shall see how list is defined in Chapter 7.

There are two styles of writing programs, top-down and bottom-up program-

ming. In both, we are looking for the solution of some problem and want to
write a procedure that returns the desired solution as its value. For now, we
refer to this as themain procedure. In top-down style, we first write the defi-
nition of themain procedure. The main procedure often uses certain helping
procedures, so we write the definitions of the helping procedures next. These
in turn may require other helping procedures, so we write those, and so on. In

bottom-up style, we first write the definitions of the helping procedures that
we anticipate using, and at the end, we write the main procedure. We shall
use both styles of programming in this book.
We summarize this discussion by observing that the value of a lambda
expression with the syntax

(lambda (parameter . . . ) body)

isa procedure. The ellipsis after parameter means that this is a list of zero or
more parameters. When the procedure is applied, the parameters are bound
to the arguments (i.e., the values of the operands), and the body is evaluated.

2.2 Procedures 37
)

We can give the procedure a name by using a define expression with the
structure

(define procedure-name lambda-expression)

where procedure-name is the variable used as the name of the procedure.'


We apply {call or invoke) such a named procedure by writing the application

{procedure-name operand . . .

where the number of operands matches the number of parameters in the def-
inition of the procedure. In general, when an application of the form

{operator operand . . . )

is evaluated, the operands and the operator are all evaluated in some un-
specified order. The operator must evaluate to a procedure. The values of
the operands are the arguments. The procedure binds the parameters to the
arguments and evaluates the body, the value of which is the value of the ap-
plication. Because the operands are first evaluated and it is their values, the
arguments, that the procedure receives, we say the operands are passed by
value to the procedure.

We have also encountered two expressions that are called special forms:
those with the keywords define and lambda. These expressions are not ap-
plications because not all the items in the expressions are evaluated initially.
For example, in a lambda expression, the parameter list is never evaluated
and its body is not evaluated initially. Most computer languages have some
keywords that have special meaning and cannot be used for other purposes.

In Scheme the number of such keywords for special forms is relatively small.
In Chapter 14, we shall see how we can add to Scheme our own special forms.

' Scheme also supports

(define {procedure-name parameter ...) body)

as a syntax for a define expression.

38 Procedures and Recursion


.

Exercises

When doing these exercises, you may find it convenient to save the defini-
tions of the procedures in a file. These procedures can then be used again.
They can be entered into Scheme from a file in which they were saved either
by using a transfer mechanism or by invoking a loading procedure. In some
implementations of Scheme, this is done with (load "filename'')

Exercise 2.1: second


Define a procedure called second that takes as its argument a list and that
returns the second item in the list. Assume that the list contains at least two
items.

Exercise 2.2: third


Define a procedure called third that takes as its argument a list and that
returns the third item in the list. Assume that the list contains at least three
items.

Exercise 2.3: f irsts-of-both


The procedure f irsts-of-both is defined as follows:

(define f irsts-of-both

(lambda (list-1 list-2)


(make-list-of-two (car list-1) (ceur list-2))))

Determine the value of the following expressions:

a. (f irsts-of-both '(1357) '(246))


b. (f irsts-of-both '((a b) (c d)) '((e f) (g h)))

Exercise 2.4-' juggle


Define a procedure juggle that rotates a three-element list. The procedure
juggle returns a list that is a rearrangement of the input list so that the
first element of this becomes the second, the second element becomes the
list

third, and the third element becomes the first. Test your procedure on:

(juggle '(jump quick spot)) ==* (spot jump quick)


(juggle '(dog bites mEm) ) =^> (man dog bites)

2.2 Procedures 39
Exercise 2.5: switch
Define a procedure switch that interchanges the and third elements of a
first

three-element list. Test your procedure on the examples given in the previous
exercise.

2.3 Conditional Expressions

Suppose we want to define a predicate that tests whether a value is a number,


a symbol, an empty list, or a pair, and returns a symbol indicating its type.
The structure of the test can be written in natural language as:

If the value is a pair, return the symbol pair.


If the value is an empty list, return the symbol empty-list.
If the value is a number, return the symbol niuaber.
If the value is a symbol, return the symbol symbol.
Otherwise, return the symbol some-other-type.

This description of the procedure using English gives a sequence of steps that
we follow to carry out the computation. Such a sequence of steps describing a
computation is called an algorithm. We
implement the kind of "case analysis"
given in tl s algorithm using a cond expression (the special form with keyword
cond). The keyword cond is derived from the word conditional. Using cond,
we write a procedure called type-ol that tests its argument and returns the
type of the item as described above:

(define type-of
(leUDbda (item)
(cond
((pair? item) 'pair)
((null? item) 'empty-list)
((number? item) 'niimber)
((symbol? item) 'symbol)
(else 'some-other-type))))

Let us analyze the cond expression. In this case, the cond expression has
five clauses, each represented by two expressions enclosed in parentheses. The
first clause, ( (pair? item) 'pair), has as its first expression (pair? item),
which is a boolean or logical expression with the value #t or #f depending on
whether the value bound to item is or is not a pair. We shall also refer to the

boolean expression as the condition. If the condition evaluates to true, then


the second expression in the clause (the consequent), 'pair, is evaluated and
pair is returned. If the condition in the first clause evaluates to false, the

40 Procedures and Recursion


condition in the second clause ((null? item) 'empty-list) is evaluated.
If one of the subsequent conditions is true, then its consequent is evaluated
and that value is returned. The last clause has the keyword else as its

first expression, and if all of the preceding conditions are false, the expression
following else is evaluated, and its value is returned. The expression following
else is referred to as the alternative.
In general, the syntax of a cond expression is

(cond
{conditiorii consequenti)
{condition2 con3equent2)

iconditioun consequentn)
(else alternative))

where for each k = l,...,n, the expressions iconditiouk consequentk) and


(else alternative) are called clauses. The conditiorik and consequentk^ for
k = l,...,n, and the alternative are expressions, and else is a keyword.
Each of the conditional parts of the clauses is evaluated in succession until
one is true, in which case the corresponding consequent is evaluated, and
the value of the cond expression is the same as the value of the consequent
corresponding to the true condition. If none of the conditions is true, the
cond expression has the same value as the alternative, which is in the last
cond clause, known as the else clause.*

Scheme has another way of handling conditional expressions that have only
two cases. We can also use the special form with keyword if. Suppose we
want to write a procedure car-if-pair that does the following:

If its argument is a pair, return the car of the pair.


Otherwise, return the argument.

Here is the procedure car-if-pair using cond:

(define car-if-pair
(lambda (item)
(cond
((pair? item) (car item))
(else item))))

* The else clause is optional. Hit is omitted and all of the conditions are false, then Scheme

does not specify the value that is returned as the value of the cond expression. We shall
avoid using cond expressions that return unspecified values.

2.3 Conditional Expressions 4.I


)

or using an if expression, it can be written as:

(define ceur-if-pair
(leunbda (item)
(if (pair? item)
(c2u: item)
item)))

In general, the syntax of an if expression is

(if condition consequent alternative)

or

(if condition consequent)

In the first case, if condition is true, the value of consequent is returned as


the value of the if expression; if condition is false, the value of alternative
is returned as the value of the if expression. In the second case, the alter-
native is not present. In this "one-armed if," if condition is true, the value

of consequent is returned as the value of the if expression. If it is false, an


unspecified value is returned.
If expressions can be nested, enabling us to write the procedure type-of
given above as follows:

(define type-of
(lambda (item)
(if (pair? item)
'pair
(if (null? item)
'empty-list
(if (niimber? item)
'number
(if (symbol? item)
'symbol
' some-other-type) ) ) ) )

Any cond expression can be written as nested if expressions, but as the num-
ber of cases increases, the nesting of the if expressions gets deeper, and the
meaning of the whole conditional expression is obscured. Thus, using a cond
expression is often advantageous when there are several cases.

42 Procedures and Recursion


. .

The use of conditional expressions with either if or cond depends upon first

evaluating a condition. The condition may be simple, such as (null? Is),


or it may involve something like testing whether Is is a pair and whether its

020" is some symbol such as cat. A condition that involves a combination


of two or more simple conditions is called a compound condition. We build
compound conditions by combining simple conditions with the logical compo-
sition operators amd, or, and not. The compound condition mentioned above

can be written using and as follows:

(and (pair? Is) (eq? (car Is) 'cat))

The syntax of each of these logical operators is given below:

(etnd expri expr2 ... exprn)


(or expri expr2 ... exprn)
(not expr)

The and expression evaluates each of the subexpressions expri, expr2, . .


.,

exprn in succession. If any one of them is false, it stops evaluating the rest
of the subexpressions, and the value of the and expression is #f . If all of the
subexpressions have true values, the value of the last subexpression is returned
as the value of the and expression.^
The or expression evaluates each of the subexpressions expri, expr2, ,
exprn in succession. If any one of them is true, it stops evaluating the rest
of the subexpressions, and the value of the or expression is the value of that
first true subexpression. If all of the subexpressions are false, the value of the
or expression is #f
The value of the not expression is #1 when expr has a true value, and it is

#t when expr is false.


We illustrate the use of and and or in the following examples:

(define s-eind-n-list?
(lambda (Is)
(euid (pair? Is)
(symbol? (car Is))
(pair? (cdr Is))
(number? (cadr Is)))))

^ Scheme has a convention of treating einy value that is not false as true. Thus (if 'cat
'kitten 'puppy) ^^ kitten, since the condition 'cat evaluates to cat, which is not false.
It is good programming style, however, for the conditions to be boolean expressions that

evaduate to either tt or tf

2.3 Conditional Expressions 4^


.

The predicate s-and-n-list? takes a list as its argument. The value of


the expression (s-aind-n-list? some-list) is #t if:

some-list is a pair,
and the first item in some-list is a symbol,
and the cdr of some-list is a pair,
and the second item in some-list is a number.

Otherwise, the value of (s-and-n-list? some-list) is #f . For example,

(s-and-n-list? '(a 1 b)) =* «t

while

(s-and-n-list? '(a b D) =» #f

The test to determine whether the list is a pair is necessary since we can only
take the Ceo: of a pair. If the list is empty, the evaluation of the car of the
list never takes place. The evaluation terminates on the first false value.

(define s-or-n-list?
(lambda (Is)
(and (pair? Is)
(or (symbol? (cau: Is))
(number? (car Is))))))

The predicate s-or-n-list? takes a list as its argument. The expression


(s-or-n-list? some-list) =^ #t if:

some-list is a pair,
and either the first item in some-list is a symbol or it is a number.

Otherwise (s-or-n-list? some-list) =* #f


There are occasions when we want to test whether a list contains precisely
one item, that is, whether the list is a singleton list. It is easy to define
a predicate singleton-list? that tests whether its argument is a pair and
whether it contains just one element. To test whether a pair contains just one
element, it is enough to test whether its cdr is empty. Thus we can write

Program 2.1 singleton-list?

(define singleton-list?
(lambda (Is)
(and (pair? Is) (null? (cdr Is)))))

44 Procedures and Recursion


This definition makes use of the fact that the empty list is not a pair. Thus
the nonempty list whose cdr is empty must contain just one item and is thus
a singleton list.

Exercises

Exercise 2.6
Assume that a, b, and c are expressions that evaluate to #t and that e and f
are expressions that evaluate to #f. Decide whether the following expressions
are true or false.

a. (and a (or be))


b. (or e (cmd (not f) a c))

c. (not (or (not a) (not b)))

d. (and (or a f) (not (or be)))

Exercise 2.1
Decide whether the following expressions are true or false if expr is some
boolean expression.

a. (or (symbol? expr) (not (symbol? expr)))

b. (amd (null? expr) (not (null? expr)))

c. (not (and (or expr #f) (not expr)))

d. (not (or expr #t))

Exercise 2.8
Decide whether the following expressions are true or false using s-cind-n-
list? as defined in this section.

a. (s-2uid-n-list? *(2 pair 12 dozen))

b. (s-and-n-list? '(b 4 u c a j))

c. (s-aoid-n-list? '(a ten))

d. (s-and-n-list? '(a))

Exercise 2.9
Decide whether the following expressions are true or false using s-or-n-list?
as defined in this section.

a. (s-or-n-list? '(b))

2.3 Conditional Expressions ^5


b. (s-or-n-list? '(c 2 m))

c. (s-or-n-list? '(10 10 10 10))

d. (s-or-n-list? '())

2.4 Recursion

We saw in Section 2.2 that certain procedures use other procedures as helping

procedures. In this section, we define procedures that use themselves as help-


ing procedures. When of the lambda
a procedure calls itself within the body
expression defining we say that the procedure is recursive. To introduce the
it.

idea of a recursive procedure, we set as our goal the definition of a procedure


last-item. that, when applied to a nonempty list, returns the last top-level
item in the list. Here are some examples of applications of last-item:

(last-itea '(12345)) =* 5

(last-item '(a b (c d))) =^ (c d)

(last-item '(cat)) => cat


(last-item '((cat))) =^ (cat)

It is a good idea to begin with the simplest cases of the arguments to which
the procedure is applied. In this case, the simplest nonempty list is a list

containing only one item. For example, if the list is (a), then the last item is

also the first item, and applying cair to this list produces the last item. This
would work with any list containing only one top-level item, for the car of the
list is both its first and its last toi>-level item. Let us use the variable Is as
the parameter in the definition of last-item. How can we test whether Is
contains only one top-level item? When Is contains only one top-level item,
its cdr empty list. Thus the boolean expression (null? (cdr Is))
is the
returns #t when — —
and indeed only when the nonempty list Is contains only
one top-level item. Thus, we may use a cond expression to test whether we
have the case of a one-item list and return the car of the list if that is the
case. We can then begin our program as follows:

(define last-item
(lambda (Is)
(cond
((null? (cdr Is)) (car Is))
... )))

46 Procedures and Recursion


If we now consider a list Is containing more than one top-level item, the cdr
of that list contains one fewer top-level items, but still includes the last item
of the original list. Each successive application of cdr reduces the number of
top-level items by one, until we finally have a list containing only one top-level
item, for which we have a solution. In this sense, application of cdr to the
list reduces the problem to a simpler case. This leads us to consider the list

obtained by evaluating (cdr Is),^ which contains all of the items of Is except
its first item. The last item in (cdr Is) is the same as the last item in Is. For
example, the list (a b c) and the list (b c), which is its cdr, have the same
Icist item, c. Thus if we call the procedure last-item as a helping procedure
to be applied to (cdr Is), we get the desired last item of the original list,

and that solves our problem. Thus to complete the definition of last-item,
we add the else clause to handle the case where the list contains more than
one item:

Progreun 2.2 last-item

(define last-item
(lambda (Is)
(cond
((null? (cdr Is)) (car Is))
(else (last-item (cdr 1 s))))))

To see that this docs define the procedure last-item so that it returns the
correct result for any nonempty list Is, we consider first a list (a) containing
only one item. Then the condition in the first cond clause is true, and (ceir

Is) does give us the last (which is also the first) item, a, in the list. Thus last-
item works on any list containing only one item. Now let's consider the case
in which Is is a list (a b) containing two items. Then its cdr, (b), contains
one item, so the procedure last-item does work on (cdr Is), allowing us to
use it as a helping procedure in the else clause to get the correct result. Thus

last-item solves the problem for any list of two items. Now we use the fact
that last-item works on the cdr of any three-item list to conclude that it

^ It is common practice, when the context is clear, not to include the phraise obtained by
evaluating. We say, "the hst (cdr Is)" instead of "the list obteuned by evaJuating (cdr
Is)" whenever the context makes it clear that we want the value of (cdr Is) rather thain
the litereJ hst whose first iten> is cdr euid whose second item is Is. When we weint the
Utereil list, eind the context is not cleair, we indicate so by quoting it.

2.4 Recursion 4^

works on the three-item list itself. We can continue this process of increasing
by one the number of items in the list indefinitely, showing that last-item
solves the problem for any list.

Since the procedure last-item called itself as a helping procedure, last-


item is a recursive procedure. Our strategy in general in designing a recursive
procedure on a list is first to identify the "simplest case" and write the expres-
sion that solves the problem for that case as the consequent in the first cond
clause. We call this simplest case the base case or terminattng condition. We
then identify a simplifying operation, which on repeated application to the
list produces the base case. Then in each of the other cases, we solve the
problem with some expression that calls the recursive procedure as a help-
ing procedure applied to the simplified list. In our example, the base case is

the list consisting of only one item. The simplifying operation is cdr, and in
the other cases, we see that the expression that solves the problem applies
last-item to the simplified list (cdr Is).
To give us a better intuition about how last-item works, we shall apply
last-item to the list (a b c). What is (last-item '(a b c))? We shall
walk through the evaluation of this expression. The parameter Is is bound
to the argument (a b c), and the cond expression is evaluated. In this case,
(cdr Is) is not empty, so the alternative in the else clause is evaluated.
This tells last-item to (cdr Is). Since (cdr Is) is (b c),
us to apply
we must evaluate (last-item (b c)). We thus bind the parameter Is to
'

the argument (be) and enter the cond expression. Once again, (cdr Is)
is not empty, so we evaluate the alternative in the else clause. This tells us

to apply last-item to (cdr Is), which now is (c). Thus we must evaluate
(last-item '
(c)). We now bind the parameter Is to the argument (c) and
enter the cond expression. This time (cdr '(c)) is the empty list. Thus the
consequent is evaluated to give (car '
(c) ) c as the value of the expression.

The recursion in the illustration stops when the list is simplified to the
base case. In that case, the condition in the first cond clause is true. We
call the condition used to stop the recursion the ierminaUng condition. In
our example, the terminating condition is (null? (cdr Is)). Generally,
whenever a recursive procedure is defined, a terminating condition must be
included so that the recursion will eventually stop. (In Chapter 15 on streams,
we shall see examples in which a terminating condition is not needed.) We
usually begin the definition of a recursive procedure by writing the terminating
condition as the first cond clause. We then proceed with the rest of the
definition.

In the preceding discussion we introduced the substitution model. Using the


substitution model, we can determine the value of an expression by substitut-

48 Procedures and Recursion


ing values for parameters. Through the first eight chapters, the substitution
model suffices. From Chapter 9 on, however, there will be times when the
substitution model does not work. From time to time, we use it to clar-
ify a computation; most of the time, however, we use the general approach:

the environment model. In that approach we just remember the bindings of


variables and avoid any substitutions.
Let us next define a procedure member? that decides for us whether its first

argument is equal? to one of the top-level items in the list that is its second
argument. For example,

1. (member? 'cat '(dog hen cat pig)) ==> #t


2. (member? 'fox '(dog hen cat pig)) =* #f

3. (member? 2 '(1 (2 3) 4)) =* #f


4. (member? ' (2 3) ' (1 (2 3) 4)) => #t

5. (member? 'cat '()) =* #f

In Example 3, 2 is not a top-level item in the list (1 (2 3) 4), so #f is

returned. We begin the definition of member? by determining the base case.


Regardless of what item is, if Is is the empty list, #f is returned. This is the
simplest case and will be taken as our base case. To test for the base case,

we use the predicate null? so the terminating condition is (null? Is). The
consequent for the terminating condition is #f . We can therefore begin the
definition of member? as a procedure having two parameters, item and Is:

(define member?
(leunbda (item Is)
(cond
((null? Is) «f)
... )))

Now given any list, what is the simplifying operation that simplifies Is to
the empty list? It is again the procedure cdr. Assume that Is is not empty.
If we know the value of (member? item (cdr Is) ) how do we get the value ,

for (member? item Is)? Well, when is the latter statement true? It is true
if either the first item in Is is the same as item or if item is a member of

the rest of the list following the first item. This can be written as the or
expression:

(or (equal? (car Is) item) (member? item (cdr Is)))

Thus in the case when Is is not empty, the above expression is true exactly

2.4 Recursion 4^
when the expression (member? item Is) is true. We then complete the defi-
nition of member? with

Program 2.3 member?

(define member?
(lambda (item Is)
(cond
((null? Is) «f)
(else (or (equal? (car Is) item)
(member' item (cdr Is)))))))
'

The procedure member? is recursive since it calls itself. Let us review the
reasoning used in the program for member?. If the terminating condition

(null? Is) is true, then item is not in Is, and the consequent is false. Oth-
erwise we look at the alternative, which is true if either item is the first item
in Is or if item is in (cdr Is) and is otherwise false.

When member? calls itself with argument (cdr Is), its parameter is bound
to the value of (cdr Is), which is a shorter list than the parameter's previous
binding to Is. In each successive recursive procedure call, the list is shorter,
and the process is guaranteed to stop because of the terminating condition
(null? Is).
In order to use a list as the first argument to member? (as in Example 4),
we used the predicate equaJ.? to make the sameness test in the else clause. If
we know that the items to which item is bound will always be symbols, we
can use eq? in place of equsd.?. The procedure so defined using eq? is named
memq? to distinguish it from member?, which is defined using equatl? for the
sameness test. Similarly, if we know that the items to which item is bound
will always be either symbols or numbers, we can use eqv? for the sameness

test and call the procedure so defined memv?.''


We have now defined the procedure last-item, which picks the last top-

level item out of a list, and the procedure member?, which tests whether ein

item is a top-level element in a given list. We continue illustrating how to


define recursive procedures with the definition of another useful procedure

^ Scheme provides the three procedures member, nemq, and memv, written without the ques-
tion mark. These behave somewhat differently from the ones we defined with the question
mark in that if item is not found, false is returned, but if item is found in Is, the subUst

whose car is item is returned. For example, (memq 'b '(a b c)) ^^ (b c).

50 Procedures and Recursion


for manipulating lists. The procedure remove-lst removes the first top-level
occurrence of a given item from a list of items. For example,

1. (remove-lst 'fox '(hen fox chick cock))


^* (hen chick cock)
2. (remove-lst 'fox '(hen fox chick fox cock))
=> (hen chick fox cock)
3. (remove-lst 'fox '(hen (fox chick) cock))
=* (hen (fox chick) cock)

4. (remove-lst 'fox '()) =*


5. (remove-lst '(1 2) '(1 2 (1 2) ((1 2))))
—> (1 2 ((1 2)))

In general, the procedure remove-lst takes two arguments, an element item


and a list Is. It builds a new list from Is with the first top-level occurrence of
item removed from it. We again begin looking at the simplest case, in which
Is the empty list. Since item does not occur at all in the empty list, the
is

listwe build is still the empty list. The test for the base case is then (null?
Is), and the value returned in its consequent is (). Thus the definition of the
procedure remove-lst begins with

(define remove-lst
(Isunbda (item Is)
(cond
((null? Is) '())
... )))

If Is is not empty, the procedure that simplifies it to the base case is again
cdr. If we already know (remove-lst item (cdr Is)), that is, if we have
a list first top-level occurrence of item removed from (cdr
consisting of the
Is), how do we build up a list that is obtained by removing the first top-level
occurrence of item in Is? There are two cases to consider. Let's first consider
the example in which we remove the first occurrence of a from the list (a b
c d). Since a is the first item in the list, we get the desired result by merely
taking the cdr of the original list. This is the first case we consider. If the first
top-level item in Is is the same as item, then we get the desired list by simply
using (cdr Is). This case can be added to the definition of remove-lst by
writing

2.4 Recursion 51
(define reaove-lst
(lambda (item Is)
(cond
((null? Is) '())
((equal? (car Is) item) (cdr Is))
... )))

The only case left to be considered is when Is is not empty, and its first

top-level item is not the same as item. Consider the example in which we
apply remove-lst to remove the letter c from the list (a b c d). The list

is not empty and its first item is not c. Thus the list we build begins with
a and continues with the items in (b d). But (b d) is just the list obtained
by removing c from (b c d). The final result is then (a b d). which was
obtained by building the list

(cons (car ' (a b c d)) (remove-lst 'c (cdr '(a b c d))))

In general, the list we are building now begins with the first element of Is
and has in it the elements of (cdr Is) with the first top-level occurrence of
item removed. But this is obtained when we cons* (car Is) onto (remove-
lst item (cdr Is) ) , so the final case is disposed of by adding the else clause
to the definition, which is given in Program 2.4.

Program 2.4 remove-lst

(define remove-lst
(lambda (item Is)
(cond
((null? Is) '())
((equal? (car Is) item) (cdr Is))
(else (cons (car Is) (remove-lst item (cdr Is)))))))

To get a better understanding of how recursion works, let's walk through


the evaluation of an application of the procedure remove-lst; for example

(remove-lst 'c '(abed))

* Scheme programmers use the verb cons, which has am infinitive "to cons", tenses "cons,
cons'd, has cons'd" participle "consing"
, , and conjugation "I cons, he conses, etc." We shall
make frequent use of these words.

52 Procedures and Recursion


Since the list (a b c d) is not empty and the first entry is not c, the alter-
native in the else clause is evaluated. This gives us

(cons 'a (remove-lst 'c '(bed)))

To get the value of this expression, we must evaluate the remove-lst subex-
pression. Once again, the list (b c d) is not empty, and the first item in the
list is not the same as c. Thus the alternative in the else clause is evaluated.
This gives us as the value of the whole expression above:

(cons 'a (cons 'b (remove-lst 'c '(c d))))

Once again, to get the value of this expression, we must evaluate the remove-
lst subexpression. Now the list (c d) is not empty, but its first item is the
same as c. Thus the condition in the second cond clause in the definition of
remove-lst is true and the value of its consequent is (d). Thus the above
expression has the value

(cons 'a (cons 'b '(d)))

which can be simplified to give the value

(a b d)

This is the value returned by the procedure call. In the next section, we shall
see how the computer can help us walk through a procedure application.
In order to be able to remove a sublist from a given list, as in Example
5, the predicate equal? was used to test for sameness in the second cond
clause. Ifwe know that all of the arguments to which item will be bound are
symbols, we can use eq? to test for sameness. The procedure defined using eq?
instead of equal? is named remq-lst. Similarly, if we restrict the arguments
to which item will be bound to symbols or numbers, we can use eqv? to test
for sameness in the second cond clause, and we name the procedure so defined
remv-lst.

Exercises

Exercise 2.10
Rewrite the definitions of the three procedures last-item, member? and
remove-lst with the cond expression replaced by if expressions.

2.4 Recursion 53
)

Exercise 2.11
The definition of member? given in this section uses an or expression in the
else clause. Rewrite the definition of member? so that each of the two subex-
pressions of the or expression is handled in a separate cond clause. Compare
the resulting definition with the definition of remove-lst.

Exercise 2.12
The following procedure, named mystery, takes as its argument a list that
contains at least two top-level items.

(define mystery
(Icunbda (Is)
(if (null? (cddr Is))
(cons (car Is) '
)

(cons (car Is) (mystery (cdr Is))))))

What is the value of (mystery '


(1 2 3 4 5) )? Describe the general behav-
ior of mystery. Suggest a good name for the procedure mystery.

Exercise 2.13: subst-lst


Define a procedure subst-lst that takes three parameters: an item new, an
item old, and a list of items Is. The procedure subst-lst looks for the first

top-level occurrence of the item old in Is and replaces it with the item new.
Test your procedure on:

(subst-lst 'dog 'cat '(my cat is clever))


^=* (my dog is clever)
(subst-lst 'b 'a ' (c a b a c)
^=> (c b b a c)

(subst-lst '(0) '() '((*) (1) () (2)))


=« ((0) (1) (•) (2))
(subst-lst 'two 'one '()) ^
In order to be able to include lists as possible arguments to which the param-
eters new and old are bound, use equal? to test for sameness. Also define
procedures substq-lst and substv-lst that use eq? and eqv? respectively,
instead of equal? to test for sameness.

54 Procedures and Recursion


Exercise 2.14: insert-right-lst
The procedure insert-right-lst is like remove-lst except that instead of
removing the item that it is searching for, it inserts a new item to its right.
For example,

(insert-right-lst 'not 'does '(my dog does have fleas))


=> (my dog does not have fleas)

The definition of insert-right-lst is

(define insert-right-lst
(lambda (new old Is)
(cond
((null? Is) '())
((equal? (car Is) old)
(cons old (cons new (cdr Is))))
(else (cons (car Is)
(insert-right-lst new old (cdr Is)))))))

Define a procedure insert-left-lst that is like insert-right-lst except


that instead of inserting a new item to the right of the item it is searching
for, it inserts it to its left. Test your procedure on

(insert-left-lst 'hot 'dogs '(I eat dogs))


^^ (I eat hot dogs)
(insert-left-lst 'fun 'games '(some fun))
=^ (some fiin)
(insert-left-lst 'a 'b '(a b c a b c))
^^ (a a b c a b c)
(insert-left-lst 'a 'b '()) => ()

Exercise 2.15: list-of -first-items


Define a procedure list-of-f irst-items that takes as its argument a list

composed of nonempty lists of items. Its value is a list composed of the first

top-level item in each of the sublists. Test your procedure on:

(list-of -first-items '((a) (b c d) (e f))) ^=> (a b e)


(list-of-first-items '((1 2 3) (4 5 6))) ==» (1 4)
(list-of-f irst-items '((one))) ^^ (one)
(list-of-first-items '()) =* ()

2.4 Recursion 55
Exercise 2.16: replace
Define a procedure replace that replaces each top-level item in a list of items
Is by a given item new-item. Test your procedure on:

(replace 'no '(sill you do ae a favor))


=^^ (no no no no no no)
(replace 'yes '(do you like ice creaa))
^^ (yes yes yes yes yes)
(replace 'shy '(not)) ^^ (shy)
(replace 'maybe '()) ^^ ()

Exercise 2.17: remove-2iid


Define a procedure reinove-2nd that removes the second occurrence of a given
item a from a list of items Is. You may use the procedure remove-lst in
defining reiBove-2iid. Test your procedure on:

(re2cve-2iid 'cat '(my cat loves cat food))


^^ (my cat loves food)
(reB0Te-2nd 'cat '(my cat loves food))
=* (my cat loves food)
(reBove-2nd 'cat '(my cat and your cat love cat food))
=^ (my ca- and your love ca- food)
(remove-2nd 'cat '()) () ^
Exercise 2.18: remove-last
Define a procedure remove-last that removes the last top-level occurrence
of a given element item in a list Is. Test your procedure on:

(remove-last 'a '(bananas)) =»> (b a n a n s)


(remove-last 'a '(banana)) ^^ (b a n a n)
(remove-last 'a '()) ^^

Exercise 2.19: sandwich- 1st


Define a procedure sandwich- 1st that takes two items, a and b. and a list

Is as its arguments. It replaces the first occxirrence of two successive b's in

Is with b a b. Test your procedure on:

(sandsich-lst 'mea- 'bread '(bread cheese bread bread))


=^> (bread cheese bread meat bread)
(sandwich-lst 'meat 'bread '(bread jam bread cheese bread))
=^ (bread jas bread cheese bread)
(sandHich-lst 'meax 'bread '()) =^

56 Procedures and Recursion


Exercise 2.20: list-of-symbols?
Define a procedure list-of-symbols? that tests whether the top-level items
in a given list Is are symbols. Write your definitions in three ways, first using
cond, then if, and finally and and or. Test your procedures with:

(list-of-symbols? '(one two three four five)) =^ #t


(list-of-symbols? '(cat dog (hen pig) cow)) =* #f
(list-of-symbols? (a b 3 4 d) ) =» #f
'

(list-of-symbols? '()) =J> #t

Exercise 2.21: all- same?


Define a procedure all-same? that takes a list Is as its argument and tests
whether all top-level elements of Is are the same. Test your procedure with:

(all-same? '(a a a a a)) => #t


(all-same? '(a b a b a b)) ^ #f
(all-same? '((a b) (a b) (a b))) => »t
(all-same? '(a)) => #t
(all-same? '()) ^ #t

2.5 Tracing and Debugging

We have now walked through several programs to understand their behavior.


We had to evaluate expressions ourselves and make decisions as to which
branches of conditional expressions to follow. The computer is able to do
both of these, so we can take advantage of its power to relieve us of this kind
of work. The tool we develop here enables us to walk through or, as it is

technically known, trace our programs. We can also use this tool to find and
correct errors in our programs, a process called debugging.
The computer can help us walk through or trace our programs if we make
use of a procedure writeln (read as "write-line") that prints its arguments
directly to the computer screen.Some Scheme implementations provide the
procedure writeln, and if make it available,
the one you are using does not
you can enter its simple definition.® The procedure writeln takes any number
of arguments. When we evaluate

® A more
complete discussion of writeln and related procedures that write to the screen
is 7. You may enter the definition of writeln given in Program 7.5 if
presented in Chapter
your implementation of Scheme does not provide it.

2.5 Tracing and Debugging 57


(writeln ezpri expr2 ... exprn)

the expressions expri expr2 . . exprn are all evaluated; then their values are
printed on the screen in order from left to right with no blank spaces between
them. When the last value is moves to the beginning of
printed, the cursor
the next line. Like every other procedure, writeln must return a value, but
we are not concerned with this value. In fact, different implementations of
Scheme may return different values. Since it is unspecified in Scheme what
value writeln returns, we shall assume in our implementation that the value
returned is not printed on the screen.
For example, if the variable Jack is bound to the value Jill and the variable
Punch is bound to the value Judy, the evaluation of (writeln Punch Jack)
will print

JudyJill

on the screen with no space between the words. If we evaluate the expression
(writeln 'Punch 'Jack), then the screen shows

PunchJack

We can control the spacing and print sentences on the screen if we use
another type of data called strings. A string is any sequence of keyboard
characters. In Scheme, a string is written as a sequence of characters enclosed
with double quotes: ". Thus "This is a string. " is an example of a string.

If we want to include a double quote or a backslash in a string, we must precede


it by a backslash. ^'^
Thus, we can write the string "He said \"Hello\".",
which hcis "Hello" within double quotes. If we evaluate the expression

(writeln "This is a string.")

then

This is a string.

appears on the screen. Note that the double quotes are not printed with the
string. Thus the evaluation of the expression

^° A cheu-acter, such as a backslash, which is used to change the normal meaning of what
follows it is referred to €is an escape character.

58 Procedures and Recursion


(writeln "He said \"Hello\".")

prints

He said "Hello".

A string is another example of a constant in Scheme. Thus if we enter a


string in response to a prompt, the string is returned, including the double
quotes.

[1] "This is a string."


"This is a string."

If we evaluate

(writeln "My friends Jack and " Jack ".")

we see on the screen:

My friends Jack emd Jill.

The first occurrence of Jack is in the string, so it is printed literally as Jack.


The second occurrence of Jack is not in a string, so it is evaluated, and its
value Jill is printed. This time we have a space between the words and and
Jill, since the blank space is included after the word eind in the string. The
la^t string in the writeln expression contains only the period.
The procedure writeln is usually evaluated as one of a sequence of ex-
pressions that are evaluated consecutively. This is accomplished by using the
special form with keyword begin. A begin expression has any number of
subexpressions following the keyword begin. Each of these subexpressions is

evaluated consecutively in the order that it appears and the value of the last

subexpression is returned as the value of the begin expression. For example,

[2] (begin
(writeln "The remove-lst expression")
(writeln "is applied to the list (1 2 3 4)")
(writeln "to build a new list without the number 2.")
(remove-lst 2 '(1 2 3 4)))
The remove-lst expression
is applied to the list (12 3 4)
to build a new list without the number 2.
(1 3 4)

2.5 Tracing and Debugging 59


When the preceding begin expression is evaluated, the four subexpressions are
evaluated consecutively. The first three are writeln expressions, which print
their arguments on the screen, with a new line starting after each writeln
expression is evaluated. The values returned by the writeln expressions are
ignored. The value of the last expression is the only value returned — that is

the(13 4) that appears on the last line.

We want to stress that what is printed on the screen is not the value of the
writeln expressions. Instead, what is printed on the screen is done as a side
effect. A side effect causes some change to take place (in this case, the change
was printing on the screen), but it is not a value that is returned. When using
a begin expression, all of the subexpressions before the last one are included
for their side effects and not for the values that they return. The value of
the last subexpression is the only one returned. Here is another example to
illustrate that only the value of the last subexpression is returned.

[3] (begin
(+ 3 4)
(- 5 11)
(* 10 10))
100

The values of the first two subexpressions are ignored. In this case, the first

two subexpressions did not produce any side effects, so although they were
evaluated,we do not see any evidence of it and there really was no point in

putting them there!


The syntajc of the begin expression is

(begin expri expr2 ... exprn)

where the expressions expri, expr2, ... exprn are evaluated in their given
order, and the value of the last one, expTn, is returned.
We now have all the tools we need to use writeln to help us walk through
an application of remove- 1st to remove the letter c from the list (a b c

d). We "wrap" a helping procedure entering around the condition of each


cond clause as we enter it and wrap a helping procedure leaving around the
consequent (or alternative) as we leave the cond clause. The definitions of
these helping procedures are given after the main program. The procedure
entering takes three arguments: the value of the condition, the value of Is,
and the identifying number of the cond clause: 1 for the first, 2 for the second,
and 3 for the last. It tells us, using a writeln statement, which cond clause we
are entering and the value of Is. The procedure leaving takes two arguments:

60 Procedure3 and Recursion


the value of the consequent (or alternative) and the identifying number of the
cond clause. It tells us which cond clause we are leaving and the value of the
consequent. When we run the program, we thus get a written record each
time we enter or leave a cond clause. Inserting such writeln expressions into
the definition of a procedure to study the evaluation of the procedure is one
way of tracing the procedure. Program 2.5 contains the code for the procedure
that traces remove-lst. The definition of the helping procedure entering is

in Program 2.6, and of the helping procedure leaving is in Program 2.7.

When we enter a cond clause, the condition is the entering expression whose
parameter test is bound to the value of the original condition of remove-lst.
If test is true, it writes the fact that we are entering the cond clause with
the appropriate identifying number and the current value of the variable Is.
In any event, test is returned as the value of the condition. If test is false,

the next cond clause is entered. If test is true, the consequent of that cond
clause is evaluated. If the else clause is entered, we use the quoted symbol
else as the first argument of entering. Scheme treats the symbol else as
true (since it is not false) so the alternative is evaluated.
The consequent (or alternative) in each cond clause of remove-lst-trace
is a leaving expression. It has the value of the original consequent (or alter-
native) of the cond clause of remove-lst as the binding of its first parameter,
result. When the leaving expression is evaluated, it tells us the identifying
number of the cond clause and the value to which result is bound. It then
returns result.
Now let's apply remove-lst-trace to see how this tracing information
helps us see what is happening during the evaluation.

[1] (remove-lst-trace 'c '(a b c d))


Entering cond-clause-3 with Is = (a b c d)
Entering cond-clause-3 with Is = (b c d)

Entering cond-clause-2 with Is = (c d)


Leaving cond-clause-2 with result = (d)
Leaving cond-clause-3 with result = (b d)
Leaving cond-clause-3 with result = (a b d)
(a b d)

This output tells us that we first entered the third cond clause with Is bound
to (a b c d). With this binding, the leaving expression in the alternative is

evaluated, so that its first operand

(cons 'a (remove-lst-trace 'c '(b c d))) (1)

2.5 Tracing and Debugging 61


Progrson 2.5 remove-lst-trace

(define remove-lst-trace
(lambda (item Is)
(cond
((entering (null? Is) Is 1)

(leaving '()!))
((entering (equal? (car Is) item) Is 2)
(leaving (cdr Is) 2))
((entering 'else Is 3)
(leaving
(cons (car Is) (remove-lst-trace item (cdr Is)))
3)))))

Program 2.6 entering

(define entering
(lambda (test input cond- clause -number)
(begin
(if test (writeln " Entering cond-clause-"
cond-clause-number " with Is = " input))
test)))

Program 2.7 leaving

(define leaving
(leuobda (result cond-clause-number)
(begin
(writeln "Leaving cond-clause-"
cond-clause-number " with result = " result)
result)))

62 Procedures and Recursion


is evaluated. Thus remove- Ist-trace is called again, and a is waiting to be
consed onto the value obtained before we can leave cond clause 3. The next
message on the screen tells us that we are entering the third cond expression
again with argument (b c d). This time, the alternative

(cons 'b (remove-lst-trace 'c '(c d))) (2)

is evaluated, and b is waiting to be consed onto its value before we can leave
cond clause 3. As before, remove-lst-trace is called again before the leaving
writeln expression is evaluated. This time, the first item in (c d) is same
the
as and we are told that we entered the second cond clause
c, with Is bound
to (c d). When we enter the consequent, the first operand in the leaving

expression evaluates to (d). Then the writeln expression prints on the screen
that we are leaving the second cond clause with the result bound to (d),
and the value (d) is returned.
Cons expression (2) is waiting for the value of the remove-lst-trace call,

and now that value is (d) . With this value, the cons expression in (2) evaluates
to (b d). We can now complete the evaluation of the leaving expression,
which tells us that we are leaving cond clause 3 with result bound to (b
d). But this is just the value that cons expression (1) is waiting for as the
value of remove-lst-trace invocation. Using the value (b d) as its last
its

argument, cons expression (1) evaluates to (a b d). It was the first operand
in the application of leaving in the third cond clause. Now that it has
been evaluated, the writeln expression writes its message, which says that
we are leaving cond clause 3 with result bound to (a b d). The leaving
invocation now returns the value to which result is bound, (a b d), and
that becomes the value of the original procedure call. The trace we made
here illustrates well the order in which we enter and leave the cond clauses.
We see that we do not leave the cond clause until a value is found for the
recursive invocation of remove-lst-trace, and the evaluation of the cons
expression can be completed.
In the previous example,we entered only the second and third cond clauses.
If we invoke remove-lst-trace to remove an item from a list that does not

contain it, we enter only the first and third cond clauses, eis the following trace
illustrates:

2.5 Tracing and Debugging 63


[2] (remove-lst-trace 'e ' (a b c d))
Entering cond-clause-3 with Is = (abed)
Entering cond-clause-3 with Is = (b c d)
Entering cond-clause-3 with Is = (c d)

Entering cond-clause-3 with Is = (d)


Entering cond-clause-1 with Is =
Leaving cond-clause-1 with result =
Leaving cond-clause-3 with result = (d)
Leaving cond-clause-3 with result = (c d)
Leaving cond-clause-3 with result = (b c d)

Leaving cond-clause-3 with result = (a b c d)


(abed)

Analyze the trace to be sure you can explain it in a manner similar to that
used in the previous example.
We have used writeln expressions to trace a program by printing certain
information about places in the program where the evaluation is being made
and the values of certain variables at that place. This helps us understand
how programs work. It is also an excellent tool for finding errors in programs.
If a program is not doing what you expect it to do, you can put a writeln
expression at certain places in the program where you think the error may be
and look compare them with what you expect at
at the values of variables to
that place. By studying these values, you can frequently pinpoint the source
of the error and make the appropriate changes to cause the program to work
correctly. When the program is corrected and runs as you want, the writeln
expressions used to locate the errors should be removed. Tracing a program
with the writeln expressions placed at strategic points is a helpful and often
used debugging tool.

Exercise

Exercise 2.22
In the first trace, the second and third cond clauses were entered. In the
second trace, the first and third cond clauses were entered. Can you give
a remove-lst-trace invocation that enters only the first and second cond
clauses? Explain.

64 Procedures and Recursion


The last example of recursion in this chapter is the procedure snapper,
which takes three arguments: an item x, an item y, and a list Is. It builds
a new list in which each top-level occurrence of x in Is is replaced by y, and
each top-level occurrence of y in Is is replaced by x. We are "swapping" x
and y in Is. For example,

(swapper 'cat 'dog '(my cat eats dog food))


^=> (my dog eats cat food)
(swapper 'John 'mary '(John loves mary)) ^^ (mary loves John)
(swapper 'a 'n '(b n a n a n)) =^ (banana)
(swapper 'a 'b ' (c (a b) d) ) =* (c (a b) d)
(swapper 'a 'b '()) =^> ()

In the fourth example, the a and b in the list are not at top level, so they are
not swapped.
In order to define swapper, we begin with an analysis of the base case.
What is the simplest case for this problem? If Is is empty, there is nothing
to swap and the empty list is returned. Thus we take cis the base case for Is
the empty list, and we begin the definition as follows:

(define swapper
(lambda (x y Is)
(cond
((null? Is) '())
... )))

A nonempty list is simplified to the base case using the simplifying oper-
ation cdr. What is returned if we invoke (swapper x y (cdr Is))? The
(cdr Is) with the items x and y interchanged. But this differs
result will be

from (snapper x y Is) only in that the first item in (swapper x y Is) is
missing. We will get (swapper x y Is) from (swapper x y (cdr Is)) by
consing the correct first item onto (swapper x y (cdr Is)). There are three
possibilities for this first item: it can be x, y, or neither. First, if (car Is) is

X, we should cons y onto (swapper x y (cdr Is)), so the next cond clause
in our definition can be added:

(define swapper ,

(leunbda (x y Is)
(cond
((null? Is) '())
((equal? (car Is) x)
(cons y (swapper x y (cdr Is))))
... )))

2.5 Tracing and Debugging 65


)

Program 2.8 swapper

(define swapper
(lanbda (x y Is)
(cond
((null? Is) '())
((equal? (car Is) x)
(cons y (swapper X y (cdr Is))))
((equal? (car Is) y)
(cons X (swapper X y (cdr Is))))
(else
(cons (car Is) (swapper x y (cdr Is)))))))

Second, if (car Is) is y, we should cons x onto (swapper x y (cdr Is)),


so the next cond clause can be added:

(define swapper
(lambda (x y Is)
(cond
((null? Is) '())
((equal? (car Is) x)
(cons y (swapper x y (cdr Is))))
((equal? (car Is) y)
(cons X (swapper x y (cdr Is))))
... )))

Finally, if (car Is) is neither x nor y, then we just cons (ceir Is) itself
onto (swapper x y (cdr Is)), giving us the else clause and completing the
definition given in Program 2.8.

If we invoke the procedure swapper with the arguments 'b, 'd, and '(a
b c d b). it should return the list (a d c b d) in which b and d have been
interchanged. Let's walk through the program to see how it constructs this
answer. In the first procedure call, Is is bound to (a b c d b). This list is

not empty, and its car is neither b nor d, so the else clause is evaluated and
gives as the answer the cons expression:

(cons 'a (swapper 'b 'd ' (b c d b) )

Let's refer to the value of this cons expression as answer- J , and that is the
value that we are looking for to solve the problem. At this point, however.

66 Procedures and Recursion


we have not yet evaluated the recursive invocation of swapper, so let's give
its value the name answer-2. We can now rewrite answer- 1 as

answer- 1 is: (cons 'a answer-2)


answer-2 is: (swapper 'b 'd '(bed b))
We see that answer- 1 is waiting for the value of answer-2, so we move on to
evaluating answer-2 and we shall return to get the value of answer-1 when
answer-2 is known.
To evaluate answer-2, we observe that the list (b c d b) begins with b, so
the second cond clause is the one with the true condition, and evaluating its

consequent gives us

answer-1 is: (cons 'a answer-2)


answer-2 is: (cons 'd answer-3)
answer- 3 is: (swapper 'b 'd '(c d b))
We still do not have a value for answer-3, so we once again set aside answer-2
until we have a value for answer-3. Note that we are making a table of these
various answers, with each successive entry placed below the preceding one.
We shall often refer to this table, so we give it the name return table.
To evaluate answer-3, we see that (c d b) is not empty, and does not
begin with b or d, so the alternative in the else clause is evaluated. We get
for answer-3

(cons 'c (swapper 'b 'd '(d b)))

and we give the invocation of swapper within answer-3 the name answer-4-
This gives us the return table:
answer-1 is: (cons 'a answer-2)
answer-2 is: (cons 'd answer-3)
answer-3 is: (cons 'c answer-^)
answer-4 is: (swapper 'b 'd ' (d b))

We have added answer-3 to our return table to wait until we have the value
of answer-4 •

For the invocation of swapper in answer-4 1


^^^ condition in the third cond
clause is true, so our return table now becomes
answer-1 is: (cons 'a answer-2)
answer-2 is: (cons 'd answer-3)
answer-3 is: (cons 'c answer-4)
answer-4 is: (cons 'b answer-5)
answer- 5 is: (swapper 'b 'c '(b))
We have added answer-4 to our return table to wait for a value for answer-5.

2.5 Tracing and Debugging 67


For the invocation of swapper in answer-5, the condition in the second
cond clause is true, so the return table now becomes
answer-} is: (cons 'a answer-2)
answer-2 is: (cons 'd answer-3)
answer-3 is: (cons 'c answer-4 )

answer-4 is: (cons 'b answer-5)


answer-5 is: (cons 'd answer-6)
answer- 6 is: (swapt>er 'h 'd '())

Once again we have added answer-5 to the return table to wait until we
have a value for answer-6. In the invocation of swapper in answer-6, the
terminating condition in the first cond clause is true, and the value () is

returned for answer-6.


What effect does this termination have on the return table? Although we
have a value for answer-6 the computation does not stop, for we have to get
,

the values of each of the waiting variables in our return table. Until now,
on each recursive invocation of swapper, a new row was added to the return
table waiting for a value. This time we got a value for answer-6 so we do ,

not have to add a row to the return table. Instead we replace the swapper
expression in the last row by its value ( ) . We can now work our way back up
the table one row at a time, replacing each variable on the right side by the
value it has on the next row below. We shall write these replacements in a

new table, starting with the value for answer-6.

answer-6 is: ()
answer-5 is: (d)
answer-4 is: (b d)
answer-3 is: ( [c b d)
answer-2 is: (d c b d)
answer- 1 is: (a d c b d)

The last row gives us the anticipated value for our invocation of swapper.
Let's take another look at the definition of the procedure swapper. In the
last three cond clauses, something is consed onto

(swapper x y (cdr Is))

What that something should be is determined by testing the value of (ceo-


Is). We can write a helping procedure swap-tester that makes the test and
returns the correct value to be consed onto

(swapper x y (cdr Is))

68 Procedures and Recursion


Assuming that we have such a test procedure, we can rewrite the definition
of swapper as follows:

(define swapper
(lambda (x y Is)
(cond
((null? Is) '())
(else (cons (swap-tester x y (car Is))
(swapper x y (cdr Is)))))))

We now define the helping procedure swap-tester to distinguish the three


cases for us:

(define swap-tester
(Icunbda (x y a)
(cond
((equal? a x) y)
((equal? a y) x)
(else a))))

When swap-tester is called within swapper, the arguments x, y, and icax


Is) are substituted for the parameters x, y and a, respectively, and swap-
tester returns the correct value to be consed onto

(swapper x y (cdr Is))

The use of such helping procedures often simplifies the writing and reading of
programs. We shall make frequent use of this technique.
We could also have achieved the same effect without using the helping
procedure swap-tester by using in swapper the cond expression of swap-
tester in place of calling swap-tester. This leads to another version of
swapper:

(define swapper
(lambda (x y Is)
(cond
((null? Is) '())
(else (cons (cond
((equal? (car Is) x) y)
((equal? (car Is) y) x)
(else (car Is)))
(swapper x y (cdr Is)))))))

2.5 Tracing and Debugging 69


In this section, we have seen how to use writeln expressions to trace or
debug a program. We have also seen how a return table is created when a
recursive procedure is evaluated.

Exercises

Exercise 2.23
Identify what is printed on the screen and what is returned in each of the
following:

a. (begin
(writeln "(* 3 4) = " (• 3 4))
(= ( 3 4) 12))

b. (begin
(writeln "(cons 'a '(be)) has the value " (cons 'a (b c))) '

(writeln "(cons 'a (b c)) has the value "


'
(a b c)) '

(writeln "(cons 'a (b c)) has the value (a b c)")


'

(cons 'a '(be)))

c. (begin
(writeln "Hello, how aure you?")
(writeln "Fine, thanX you. How are you? " 'Jack)
(writeln "Just great! It is good to see you again, " 'Jill)
"Good-bye. Have a nice day.")

Exercise 2.24-' describe


With describe defined as

(define describe
(lambda (s)
(cond
((null? s) (quote '()))
((nunber? s) s)
((synbol? s) (list 'quote s))
((pair? s) (list 'cons (describe (ceir s)) (describe (cdr s))))
(else s))))

evaluate each of the following expressions:

a. (describe 347)

b. (describe 'hello)

70 Procedures and Recursion


c. (describe '(12 button my shoe))

d. (describe ' (a (b c (d e) f g) h))

Describe what describe does in general.

Exercise 2.25
Write a trace similar to the one used in remove-lst-trace to trace the pro-
cedure swapper, showing the binding of the parameter Is each time the cond
expression is entered and whenever a cond clause is exited. Invoke the traced
procedure swapper-trace on the arguments b, d, and (a b c d b) used in
the example in this section.

Exercise 2.26
In the return table built for the invocation of swapper in this section, the
computation did not stop when the terminating condition was true and the
first cond clause returned ( ) . Instead, the variables in the table were evaluated
one by one until the value of the first was obtained to provide the value of
the original invocation. This program behaved in this way because after each
invocation of swapper, a cons still had to be completed. There was still

an operation to perform after swapper was invoked. Do a similar analysis,


building the return tables, on the two procedures last-item in Program 2.2
and member? in Program 2.3. In the first case, consider (last-item (a b '

c)), and in the second case, consider (member? 'c '(abed)). In these
two examples, there is no procedure waiting to be done after the recursive

invocations of the procedure. Such programs are called iterative. We shall


discuss the behavior of iterative programs more thoroughly in the chapter on
numerical recursion.

Exercise 2.27
Does the answer change if cond clause 2 and cond clause 3 are interchanged
in the definition of swapper? Does the same thing hold if cond clauses 1 and
2 are interchanged in swap-tester?

Exercise 2.28: tracing, test-tracing


A more generally applicable tracing tool than the procedure leaving given
in Program 2.7 is the procedure tracing defined by

(define tracing
(lambda (message result)
(begin
(writeln message result)
result)))

2.5 Tracing and Debugging 71


Similarly, the procedure test-tracing defined by

(define test-tracing
(lambda (test message input)
(begin
(if test (tracing message input))
test)))

is useful for tracing the test part of a conditional expression. Rewrite the
definition ofremove-lst-trace using test-tracing and tracing instead of
entering and leaving in such a way as to produce exactly the same output
as that generated using entering and leaving.

72 Procedures and Recursion


Data Abstraction and Numbers

3.1 Overview

Many of the procedures we studied in Chapters 1 and 2 operated on lists

and symbols. Another interesting area of applications deals with numerical


computations. In this chapter, we study procedures that perform arithmetic
operations on numbers. We also develop a program to do exact arithmetic us-
ing fractions instead of decimals, which are usually associated with computers.
This will provide our ^rst illustration of data abstraction.

3.2 Operations on Numbers

We two types of numbers, integers and real numbers. The


shall discuss inte-

gers are the usual positive and negative counting numbers and zero:

...,-4,-3,-2,-1,0,1,2,3.4,...
where, in this case, the ellipsis means that the list continues indefinitely in
both directions.
The set of real numbers includes both positive and negative decimal num-
bers and zero. For example, 34.56, —3.456, 0.00034, and 17.0 are all real

numbers. The integers are also considered to be numbers, so we may also


real

refer to the real number 5 or 5.0. We numbers


write real with up to fifteen
significant figures,^ so | will be written as 0.333333333333333 and | multi-
plied by 10000 is written as 3333.33333333333. When the decimal point moves

^ The number of significant figures represented is system dependent.


beyond the Scheme may switch over to scientific notation. For
fifteenth digit,
example, 0.333333333333333e45 represents the number one-third multiplied

by 10 raised to the forty-fifth power that is, by a one followed by forty-five
zeros. Let's look at some simpler examples of scientific notation. The number

2.735e2 is the same as 273.5, since the e2 means that 2.735 is multiplied by

a one followed by two zeros that is, by 100. Another way of saying this is
that the e2 means that the decimal point is moved two places to the right.
Another example is 2.735e-2, which is the same as 0.02735, since e-2 means
that 2.735 is multiplied by .01, or that the decimal point is moved two places
to the left.

We use two predicates, integer? and real?, to test the type of a number.
The expression (integer? mim) is true if num is an integer and false other-

wise. The expression (real? num) is true if num is any real number, including
integers, and is otherwise false. Three other useful predicates are zero?, pos-
itive?, and negative?, which make the obvious tests to see whether their
arguments are zero, positive, or negative, respectively.

The four basic arithmetic operations are given by the procedures associated
with the variables + for addition, - for subtraction, * for multiplication, and
/ for division. These are applied to numbers with applications, as were the
list operations in Chapter 2. For example, to add the two numbers 5 and 7,

we enter the expression (+ 5 7), and the answer 12 is returned. Similarly^

(- 4 32) ==> - 28

(* -15 -3) 45
(/ -15 -3) 5

(/ -16 -3) 5. 33333333333333

We recommend that you play around with these arithmetic operations on


various kinds of numbers and see what results appear.
In many programs, the successor of a given integer n is desired, and rather

than entering (+ n 1), we can use the successor procedure addl and write
(addl n). Thus

(addl 7) => 8
(addl -37) =* -36

^ When division is performed with two integers, some implementations of Scheme return
a fraction instead of a decimed. For example, (/ 2 3) is either displayed as 2/3 or as
0.66666666666666.

74 Data Abstraction and Numbers


Program 3.1 addl

(define addl
(IckBbda (n)
(+ n 1)))

Program 3.2 subl

(define subl
(lembda (n)
(- n 1)))

Not all implementations of Scheme provide the procedure addl, so we include


its definition in Program 3.1.

Similarly the predecessor procedure subl can be used to get the integer
that precedes a given integer. For example, instead of writing (- n 1), we
can write (subl n). Thus

(subl 7) => 6
(subl -37) =^ -38

The definition of subl is included in Program 3.2 in case the implementation


of Scheme you are using does not provide it.
There are many more Scheme procedures defined on numbers. We present
a brief list of these procedures in Figure 3.3 and make some short remarks
about each. When the objects to be tested for sameness may or may not be
numbers, eqv? and equal? both determine the type and apply the appropriate
sameness test. When it is known that the objects to be tested for sameness
are numbers, it is better to use =, which is specifically designed to apply to
numbers and should be used only to compare numbers. When testing for 0,

you should use zero?.


The computer's decimal representation 0.333333333333333 for the quo-
tient (/ 1 3) is not the same as the fraction 1/3 but is, rather, an approxi-

mation to it, the use of which is made necessary by the way real numbers are
represented in the computer. Thus we would not expect = to return true if
we test (/ 1 3) and 0.333333333333333. In general, because the internal
representation of certain numbers in the computer is only an approximation
to the actual number, we refer to such numbers as inexact numbers. We con-
sider numbers written with explicit use of a decimal point as being inexact,

3.2 Operations on Numbers 75


Expression Remarks
(= m n) Tests whether the exact numbers m and n are equal.
(< m n) Tests whether m is less than n.
«» m n) Tests whether m is less than or equal to n.
(> m n) Tests whether m is greater than n.

(>= m n) Tests whether m is greater than or equal to n.

(abs n) Gives the absolute value of n. (abs 5) ^^ 5 and

(abs -5) =^ 5.

(ceiling n) Gives the smciUest integer (inexact) which is > n.

(ceiling 5.3) =^ 6. (ceiling -5.3) - -5.

(floor n) Gives the largest integer (inexact) which is < n.

(floor 5.3) ^ 5. (floor -5.3) =» -6.

(round n) Rounds n to the nearest integer (inexact). If n is

exactly halfway between two integers, it rounds it

to the nearest even integer.

(truncate n) Gives the integer (inexact) obtained by chopping off

the decimal part of n.

(expt n /:) Raises n to the power k.

(sqrt n) The square root of n, n > 0.

(meuc n . . .) , (nin n . ..) The maximum <ind minimum of n . . ., respectively.

(exp n) , (log n) The exponential of n and logarithm of n to the base

e, respectively.

(sin n) , (cos n) The trigonometric sine and cosine, respectively, of n

(n in radians).

(asin n) , (acos n) The arc sine and arc cosine of n, respectively.

(tan n) The tangent of n (n in radians).

(atan n) The ajc tangent of n.

(quotient n k) The quotient of n divided by k.

(remainder n k) The remainder of n divided by k with the sign of the

dividend.

(modulo n k) The remainder of n divided by k with the sign of the

divisor.

Figure 3.3 Some of Scheme's mathematical operators

76 Data Abstraction and Numbers


for example, 3.25, —0.05. Integers, written without decimal points, are exact
numbers, and certain operations, such as + and *, preserve the exactness of
numbers. One should use only the predicate = to test for the sameness of
exact numbers.
We close this section with the definitions of several procedures that illustrate
the use of arithmetic operations in recursive programs. The first procedure
harmonic-sum sums the first n terms of the harmonic series, that is, it sums
the series of the form
1 1 1 1

2 3 n
Our strategy again is to simplify the problem by reducing the number of terms
n that are being summed. If n is zero, no terms are summed, and the sum is
zero. This will serve as the terminating condition for our recursion.

(define harmonic-sun
(lambda (n)
(cond
((zero? n) 0)
... )))

To make the recursive step, we observe that we get (harmonic-sum n) from


(hau:monic-sum (subi n)) by adding the nth term. For any positive n,
(harmonic-sum n) is the same as

(+ (/ 1 n) (harmonic-suffl (subl n)))

so we complete the definition with

Program 3.4 harmonic-sum

(define harmonic-sum
(lambda (n)
(cond
((zero? n) 0)
(else (+ (/ 1 n) (harmonic-sum (subl n)))))))

In programs dealing with numbers, it is often the case that the recursion is

accomplished by reducing the numerical argument each time the procedure


calls itself, and the smallest value of the numerical argument (in this caise, n is
zero) provides the terminating condition. Another simple illustration of this

3.2 Operations on Numbers 77


idea is the construction of a list containing a specified number of zeros. We
define list-of-zeros which has parameter n and builds a list contedning n
zeros. Its code is given in Program 3.5.

Progr2un 3.5 list-of-zeros

(define list-of-zeros
(laabda (n)
(cond
((zero? n) '())
(else (cons (list-of-zeros (subl n) ))))))

The procedure length takes as its argument a list of items Is and then
tells how many top-level items are in the list. For example,

(length '(a b c d e)) ^ 5


(length '(1 (2 3) (4 5 6))) =^ 3
(length '(one)) ^ 1

(length '()) =>

The procedure length is provided in all implementations of Scheme. We shall


show the definitions of many of the procedures provided by Scheme because
you will learn programming better by knowing how these basic procedures
are defined. When you test our definitions of these procedures, it is good
practice to use a different name for the procedure you define so that you do
not override the definition of the procedure provided by Scheme. Thus for

the procedure length, you can use the name =length= when you enter your
definition.

To define length, we use recursion. The base case is the empty list whose
length is zero, and the operation cdr is used to simplify longer lists. We begin
the definition with the terminating condition:

(define length
(laabda (Is)
(if (null? Is)

... )))

Suppose we know (length (cdr Is)); then we get (length Is) by simply
adding one to (length (cdr Is)). This recursive step completes the last

line of the definition:

78 Data Abstraction and Numbers


Program 3.6 length

(define length
(lambda (Is)
(if (null? Is)

(addl [length (cdr Is))))))

The next procedure we list-ref which takes as arguments a list


define is ,

of items Is and a (nonnegative) integer n and gives us the (n + l)st top-level


item in Is. For example

(list-ref '(a b c d e f) 3) ==> d


(list-ref '(a b c d e f) 0) =» a
(list-ref '(a b c) 3)
=> Error: list-ref: Index 3 out of zange for list (a b c)
(list-ref '((1 2) (3 4) (5 6)) 1) ==> (3 4)
(list-ref '() 0)
=* Error: list-ref: Index out of range for list ()

The number n is called the index of the item extracted from the list Is by
(list-ref Is n). If the index is greater than or equal to the length of the
list of items Is, an error is announced. Since the first item in the list Is has
index 0, we say that the indexing is zero based.
The strategy we use in this recursion is based on the observation that if we
are looking for the nth item in the list, that item becomes the (n — l)st item
in the cdr of the list. Thus we shall successively remove the first item from
the list and simultaneously reduce the index of the desired item by one. If

the index reaches zero and the list is not empty, the first item in the list is

the item returned. We can determine whether the list will not become empty
before or when the index becomes zero by testing whether the length of the
list is larger than the index. If that is not the case, we signal an error. This
enables us to write the first version of the definition of the Scheme procedure
list-ref as follows:

(define list-ref
(lambda (Is n)
(cond
(«= (length Is) n)
(error "list-ref: Index" n "out of range for list" Is))
((zero? n) (car Is))
(else (list-ref (cdr Is) (subl n))))))

3.2 Operations on Numbers 79


The procedure error employed here to signal an error uses a procedure sim-
ilar to writeln to print its arguments on the screen and then returns to
the Scheme prompt. Most Scheme systems provide an error procedure. A
definition of the procedure error is given in Chapter 7.

In the program for list-ref the test for whether the length of Is is less
,

than or equal to n is made on each recursive call. However, if the test is false
on the first call, it will remain false in each successive recursive call, since

both the length of the and the index are reduced by one in each successive
list

call. It would be a much better program if the test were made only once, and

if it were false, then a helping procedure would be called that produces the
desired item. We give such a definition next.

(define list-ref
(lambda (Is n)
(cond
(«= (length Is) n)
(error "list-ref: Index" n "out of range for list" Is))
(else (list-ref -helper Is n)))))

with the helping procedure defined as

(define list-ref-helper
(lambda (Is n)
(if (zero? n)
(car Is)
(list-ref-helper (cdr Is) (subl n)))))

In general, it is good practice to avoid redundant computations when recur-


sive calls are made. The use of a helping procedure, as illustrated in this
definition of list-ref, is a way of avoiding this kind of inefficiency. Once it

is established that the length of the list is greater than the index, the helping
procedure does the rest of the computation to find the item without calling the
procedure list-ref again and determining the length of each Is repeatedly.
Another approach to defining list-ref derives from observing that if, dur-
ing the recursive calls, the list Is becomes empty while the index n is non-

negative, the index must have been too large for the list. Thus the program
can be written as:

80 Data Abstraction and Numbers


Progrcun 3.7 list-ref

(define list-ref
(lambda (Is n)
(cond
((null? Is)
(error "list-ref: Index" n "out of range for list" Is))
((zero? n) (car Is))
(else (list-ref (cdr Is) (subl n))))))

Exercises

We refer to a list of numbers as an n-iuple. Thus (1 3 5 7), (-1.3 2.5),


(3), and () are examples of n-tuples. The numbers in an n-tuple are called
components. In Exercises 3.1-3.4, you are asked to define several procedures
on n-tuples. In all of the exercises in this section, you may use procedures
you have already defined as helping procedures.

Exercise 3.1: sum


Define a procedure sum that finds the sum of the components of an n-tuple.
Test your procedure on:

(sum '(12 3 4 5)) —» 15


(sub '(6)) »^ 6
(sub '()) -
Exercise 3.2: pairwise-sum
Define a procedure pairvise-sum that takes two n-tuples of the same length,

ntpl-1 and ntpl-2, as arguments and produces a new n-tuple whose compo-
nents are the sum of the corresponding components of ntpl-1 and ntpl-2.
Test your procedure on:

(pairwise-sum ' (1 3 2) » (4 -1 2)) »=* (5 2 4)


(pairwise-sum '(3.2 1.5) '(6.0 -2.5)) => (9.2 -1.0)
(pairwise-sum '(7) '(11)) —^ (18)
(pairwise-sum '() '()) ^*
pairwise-product that produces an
In an analogous way, define a procedure
n-tuple whose components are the products of the corresponding components
of ntpl-1 and ntpl-2.

S.2 Operations on Numbers 81


Exercise 3.3: dot-product
Define a procedure dot -product that takes two n-tuples of the same length,
multiplies the corresponding components, and adds the resulting products.
This exercise can be done either directly or by using the procedures defined
in Exercises 3.1 and 3.2. Consider the advantages and disadvantages of each
approach. Test your procedure on:

(dot-product '(3 4 -1) '(1 -2 -3)) =» -2


(dot-product '(0.003 0.035) '(8 2)) =^ 0.094
(dot-product '(5.3e4) '(2.0e-3)) =S> 106.0
(dot-product () '()) =>'

Exercise 3.4-' mult-by-n


Define a procedure mult-by-n that takes a number num and an n-tuple ntpl
as arguments and multiplies each component of ntpl by num. Test your pro-
cedure on:

(mult-by-n 3 (1 2 ' 3 4 5)) ==> (3 6 9 12 15)


(mult-by-n (1 3 ' 5 7 9 11) ) => (000000)
(mult-by-n -7 '()) => ()

Exercise 3.5: index


Define a procedure index that has two arguments, an item a and a list of
items Is, and returns the index of a in Is, that is, the zero-based location of
a in Is. If the item is not in the list, the procedure returns -1. Test your
procedure on:

(index 3 '(12345 6))=>2


(index 'so '(do re me fa so la ti do)) =^ 4
(index 'a '
(b c d e)) -1 =>
(index 'cat '())=> -1

Exercise 3.6: make-list


Define a procedure make-list that takes as arguments a nonnegative integer

num and an item a and returns a list of num elements, each of which is a. Test
your procedure on:

(make-list 5 'no) =^ (no no no no no)


(meJce-list 1 'maybe) ==* (maybe)
(make-list 'yes) ^^ ()
(length (make-list 7 'any)) ==* 7
(all-same? (make-list 100 'any)) ^ «t

8t Data Abstraction and Numbers


Exercise 3.7: count-background
Define a procedure count -background that takes an item a and a list of items
Is as arguments and returns the number of items in Is that are not equal?
to a. Test your procedure on:

(count-background 'blue '(red white blue yellow blue red)) =» 4


( count -backgroiind 'red '(white blue green)) =^ 3
(count -background 'white '()) ^^

Exercise 3.8: list-front


Define a procedure list-front that takes as arguments a list of items Is and
a nonnegative integer num and returns the first num top-level items in Is. If
num is larger than the number of top-level items in Is, an error is signaled.
Test your procedure on:

(list-front '
(a b c d e f g) 4) =^ (abed)
(list-front (a b c) 4)
' ^^
Error: length of (a b c) is less than 4.
(list-front (a b c d e f g) 0) => ()
'

(list-front '() 3) ^^ Error: length of () is less than 3.

Exercise 3.9: wrapa


Define a procedure wrapa that takes as arguments an item a and a nonnegative
integer num and wraps num sets of parentheses around the item a. Test your
procedure on:

(wrapa 'gift 1) => (gift)


(wrapa 'semdwich 2) ^^
((sandwich))
(wrapa 'prisoner 5) =^* (((((prisoner)))))
(wrapa 'moon 0) ^^ moon

Exercise 3.10: multiple?


Define a predicate multiple? that takes as arguments two integers ra and n
and returns #t if m is an integer multiple of n. (Hint: Use remainder.) Test
your procedure on:

(multiple? 7 2) => «f
(multiple? 9 3) =^ #t
(multiple? 5 0) =* #f
(multiple? 20) => #t
(multiple? 17 1) => #t
(multiple? 0) => #t

3.2 Operations on Numbers 83


,

Exercise 3.11: sum-ol-odds


It can be shown^ that the sum of the first n odd numbers is equal to n^ . For
example,
1 + 3 + 5 + 7= 16 = 4^

Write a procedure sum-of-odds that sums the first n odd integers. Test your
procedure by evaluating it for all values of n from 1 to 10 to see that each is

the perfect square of the number of terms.

Exercise 3.12: n-tuple->integer


Define a procedure n-tuple->integer that converts a nonempty n-tuple of
digits into the number having those digits. Test your program on the following:

(n-tuple->integer '(3 1 4 6)) =* 3146


(n-tuple->integer '(0)) -^
(n-tuple->integer '()) ""^ Error: bad arg\ment () to n-tuple->integer
( (n-tuple->integer '(1 2 3)) (n-tuple->integer '(3 2 1))) «- 444

Exercise 3.13
If Is is a list of length 1000, how much "cdring" in Is is necessary in each
of the three programs for list-ref presented in this section in order to find
(list-rel Is 4)? Which of the three programs is most efficient?

3.3 Exact Arithmetic and Data Abstraction

The numbers discussed above were either integers for which the arithmetic
operations of +, -, and * give exact results or are inexact numbers, which may
be rounded decimal representations and for which the arithmetic operations
give approximations. For division, even if the numerator and denominator
are exact integers, the result may be an approximation; for example, (/ 1 3)
might return the inexact number 0.33333333333333, which is not i. It is

possible to do arithmetic with fractions (rational numbers) and get answers


as exact fractions when arithmetic operations are performed. In this section,
we shall develop such an exact arithmetic.

^ Let S = 1 + 3 + 5+ t-(2n-l). We get the szmie sum if we aAd the numbers in reverse
order, so 5 = (2n - 1) + (2n - 3) H h 3 + 1. Adding the first terms of eeich sum, we get
2n. Adding the second terms of each sima, we get 2n and in general adding corresponding
terms in the two svuns, we get the seune siun, 2n. There ese n such corresponding pairs of
terms, so 25 = n(2n) and S = v?

84 Data Abstraction and Numbers


Recall that a fraction (or rational number) j is composed of two integers: a
is its numerator, and 6, which must be different from zero, is its denominator.
For the moment, we do not concern ourselves with how the rational number or
fraction is We shall come back to that later in this section. For
represented.
the time being, we use the fact that a rational number has a numerator and
a denominator and cissume that we have access to these two parts by means
of two procedures niunr and denr. Thus if rtl represents a rational number,
then (numr rtl) is its numerator and (dear rtl) its denominator. These
are called the two selector procedures for rational numbers, just as cslt and
cdr were the two selector procedures for lists. We shall also assume that we
have a constructor procedure that reassembles the rational number from its

numerator and denominator. We call this constructor procedure njJce-ratl


because it builds (or makes) a rational number from its parts. Thus for a
rational number rtl, the expression

(eJte-ratl (nvimr rtl) (denr rtl))

is just the rationalnumber rtl again. For example, (make-ratl 3 6) is the


rational number with numerator 3 and denominator 5.
With these selector and constructor procedures, we proceed to build up the
arithmetic of rational numbers without concerning ourselves with the repre-
sentation of the rational numbers. We begin with the definition of a predicate
rzero?, which tests whether a rational number rtl is equal to zero. We use
the fact that a rational number is equal to zero only when its numerator is

equal to zero. Thus we have:

Program 3.8 rzero?

(define rzero?
(lambda (rtl)
(zero? (nuMr rtl))))

Now we recall how two fractions are combined by the various arithmetic
operations. For example, the sum of the fractions j and ^ has the numerator
{a*d) + (fe*c) and the denominator b*d. Thus if x and y are two rational
numbers, we define the sum procedure, say r+, Program 3.9. The first
in

argument to maJte-ratl is the numerator of the sum, and the second argument
to maike-ratl is the denominator of the sum.

3.8 Exact Arithmetic and Data Abstraction 85


Program 3.9 r+

(define r+
(laabda (x y)
(ake-ratl
(+ (* (numr x) (denr y)) (* (n\i«r y) (denr x)))
(* (denr x) (denr y)))))

Since the product of two fractions j and | is the fraction having numerator
a*c and denominator b*d, we can define the product procedure r* for rational
numbers as follows:

Progreun 3.10 r*

(define r*
(lasbda (x y)
(eJce-ratl
(* (nxoar x) (numr y))
(* (denr x) (denr y)))))

Similarly, the difference procedure r- is defined by

Program 3.11 r-

(define r-
(laabda (x y)
(ake-ratl
(- (* (n\i«r x) (denr y)) (* (numr y) (denr x)))
(* (denr x) (denr y)))))

Ifwe invert a nonzero rational number j, we get ^. The procedure r invert in


Program 3.12 carries out this operation We now define the division operator
.

r/ in Program 3.13. The definition of r/ reflects the familiar rule, "invert the
divisor and multiply."

Another useful procedure is the predicate r= that tests whether two rational
numbers are equal. Two rationalnumbers j and | are equal if ad = 6c. Thus
we can write the definition of r= given in Program 3.14.

86 Data Abstraction and Numbers


Program 3.12 rinvert

(define rinvert
(lambda (rtl)
(if (rzero? rtl)
"
(error "rinvert: Cannot invert rtl)
(sJce-ratl (denr rtl) (nuar rtl)))))

Program 3.13 r/

(define r/
(laabda (x y)
(r* X (rinvert y))))

Progr€m[i 3.14 r=

(define r«
(laabda (x y)
( ( (niiar x) (denr y)) ( (nuar y) (denr x)))))

Program 3.15 rpositive?

(define rpositive?
(laabda (rtl)
(or (and (positive? (niuu: rtl)) (positive? (denr rtl)))
(and (negative? (nuBr rtl)) (negative? (denr rtl))))))

We can similarly define a predicate rpositive? by using the fact that a


number j is positive
rational if a and 6 are both positive or both negative.
Thus we get Program 3.15.
The predicate r> tests whether a rational number x is greater than a ra-
tional number y by testing whether their difference is positive. This leads to
Program 3.16. The definition of the predicate r<, which tests whether x is less
than y is obtained by interchanging x and y in the last line of the definition

of r>.
Many other familiar procedures can be built up in terms of these, and
we can go on to develop an extensive arithmetic for rational numbers using

S.S Exact Arithmetic and Data Abstraction 87


Program 3.16 r>

(define r>
(lambda (x y)
(rpositive? (r- x y))))

Program 3.17 meix

(define BcLX

(lanbda (x y)
(if (> X y)
X
y)))

Program 3.18 maix

(define max
(lanbda (x y)
(if (r> X y)
X
y)))

what we have constructed up to this point. For example, we can define the
procedure nneix, which selects the larger of its two arguments, or its second
argument if they are equal. Before defining rmaa, we show in Program 3.17

how Scheme procedure max for two numbers can be defined. Similarly, we
the
can define rmajc as shown in Program 3.18.

The definition of the procedure nnin, which returns the smaller of its two
arguments or the second if they are equal, is obtained from the definition
of nnaix by changing the r> to r<. We are now in a position to make an
important observation. When two procedure definitions are as similar as
nnajc and nnin, we could have written one definition from which both could
be obtained by peissing the predicate r> or r< as an argument to the procedure.
To demonstrate how this is done, let us use the parameter pred to stand for
either of these predicates. Then we define the procedure extreme-vailue in
Program 3.19.

88 Data Abstraction and Numbers


Program 3.19 extreme-value

(define extreme -value


(lambda (pred X y)
(if (pred x y)
X
y)))

Now we can simply write

(define rmax
(lambda (x y)
(extreme-value r> x y)))

and

(define rmin
(leunbda (x y)
(extreme-value r< x y)))

We get as a bonus the fact that max and min can also be obtained from
extreme- value, for if x and y are real numbers, we can write

(define msix

(laimbda (x y)
(extreme-value > x y)))

and for min we have

(define min
(lambda (x y)
(extreme- Veil ue < x y)))

The predicates that were parsed as arguments to the procedure extreme-


value are procedures themselves. The ability to pass procedures as arguments
to other procedures is a powerful tool in Scheme, and we shall make use of it
many times. In Chapter 7, when we talk about procedures that return other
procedures, we shall see a better way of writing these definitions.
We can also define a procedure rprint that prints the results of our calcu-
lations in the familiar form as a fraction by using the procedure writeln.

3.3 Exact Arithmetic and Data Abstraction 89


Program 3.20 rprint

(define rprint
(lambda (rtl)
(writeln (nuar rtl) "/" (denr rtl))))

Thus if rtl represents the fraction |, (rprint rtl) displays 2/3.


We have gone a long way in our exact arithmetic using the selector proce-
dures numr and denr and the constructor procedure make-ratl without ever
saying what they are. Using the arithmetic operations for the rationals r+,

r*, r-, and r/ and the other procedures that we have defined, we can write
many complicated programs using exact arithmetic on rational numbers. If
someone were to give us the three procedures numr, denr, and make-ratl, we
would not have to know how they are defined in order to use them, and other
procedures depending on them, in programs we write.
If. in the course of writing a program, we need a rational number with
numerator 2 and denominator 3, we simply write (make-ratl 2 3) for that
number. Thus we were writing a rational number package for someone else
if

to use, all we would have to provide the user with are the procedures numr,
denr, make-ratl, and the other procedures defined in terms of these and the
user can compute with the package without ever being concerned about how
the procedures numr, denr, and msike-ratl themselves are defined. We have
treated the rational numbers as abstract data. We are now free to choose any
representation of the rational numbers we wish and to define the selectors and
constructor procedures appropriately for the data representation we choose.
What is we are free to change the
especially nice about this approach is that
data representation any time we wish, and when we only redefine the three
procedures numr, denr. and make-ratl, the rest of the procedures we have
written still work and do not have to be changed in any way. That is the
power of abstraction.
So we have been able to write all of our procedures, but we have not
far,

been able to test them because we have not been given the constructor and
the selectors. We have reached the point where we choose a representation
of the rational numbers and define the selector and constructor procedures.
For the firstmethod of defining them, let us take the representation of the
rational number with numerator a and denominator b to be (list a b),
where b is never to be zero. We can then define the selectors numr and denr
for the rational number rtl and the constructor make-ratl for the integers
intl and int2 asshown in Program 3.21. With these definitions, all of the

90 Data Abstractton and Numbers


Program 3.21 numr, denr, meike-ratl

(define numr
(lambda (rtl)
(car rtl)))

(define denr
(lambda (rtl)
(cadr rtl)))

(define make-ratl
(lambda (intl int2)
(if (zero? int2)
(error "make-ratl: The denominator cannot be zero.")
(list intl int2))))

procedures we previously defined can be used with no modifications to make


up our package for rational arithmetic.

To find the denominator of a rational number using the given list represen-
tation, we have to take the car of the cdr of the list representing the number.
It is possible to have a representation that makes the denominator operation
more efficient and uses less we use a dotted pair to represent
storage space if

the rational number. Thus the rational number that has numerator a and
denominator b is represented by the dotted pair (a b). Then the selectors
.

are

(define numr
(lambda (rtl)
(car rtl)))

(define denr
(lambda (rtl)
(cdr rtl)))

and the constructor is

(define make-ratl
(lambda (intl int2)
(if (zero? int2)
(error "make-ratl: The denominator cannot be zero.")
(cons intl int2))))

3.3 Exact Arithmetic and Data Abstraction 91


Another natural representation to consider for the rational number with nu-
merator 3 and denominator 4 is the symbol 3/4. We do not have the tools to
define the selectors and the constructor for this representation yet. A knowl-
edge of how to operate with string and character data types is necessary to
do so. However, each of the possibilities for representing the rational numbers
gives rise to different definitions of the procedures numr, denr, and make-ratl,
but none of the other procedures defined in our rational arithmetic package
has to be changed in any way. They are all representation independent. If you
want to change representations, only the constructor and selector procedures
would have to be changed: the rest of the procedures would still be valid with
no alterations. We defined only the three procedures numr, denr. and make-
ratl in terms of the data objects (lists or dotted pairs in the examples) used
by the computer; the rest of the procedures were defined in terms of these se-

lectorand constructor procedures with no reference to the data objects. Since


the data objects were not specified in advance, we treat the data abstractly
and develop the rest of the procedures using the abstract data. We can then
specify concrete realizations of the data objects (or data structures) to run
the procedures. This is data abstraction.

Exercises

Use the procedures defined in this chapter for rational arithmetic in defining

the following procedures.

Exercise 3.14- rminus


Define a procedure rminus that takes a rational number as its argument and
returns the negative of that number.

Exercise 3.15: same-sign?


Consider this definition of rpositive?:

(define rpositive?
(lambda (rtl)
(same-sign? (numr rtl) (denr rtl))))

Define same-sign? so that rpositive? is correct.

Exercise 3.16: rabs


Define a procedure rabs that takes a rational number and returns its absolute
value.

92 Data Abstraction and Numbers


Exercise 3.17: make-ratl
Scheme has a procedure gcd that takes as arguments two integers and returns
their greatest common divisor, that is, the largest positive integer, which
divides into the two given integers. For example.

(gcd 8 12) =* 4
(gcd 8 -12) ==^ 4
(gcd 5) ^^ ^ i

Write the definition of the procedure make-ratl so that (make-ratl a b) is

a list (p q) in which p/q = a/b and p/q is reduced to lowest terms (so that
1 is the greatest common divisor of p and q) and in which q is positive. Test
your procedure with:

(make-ratl 24 30) =» (4 5)
(make-ratl -10 15) => (-2 3)
(make-ratl 8 -10) =^ (-4 5)
(make-ratl -6 -9) =^ (2 3)
(make-ratl 8) =^ (0 1)

Using this version of make-ratl ensures that the internal representation of


each rational number is unique.

3.3 Exact Arithmetic and Data Abstraction 93


Data Driven Recursion

4.1 Overview

In this chapter, we continue our study of recursion over the top-level items in
lists. Then we make the extension to recursion over the items in the nested
sublists as well, giving us tree recursion. In certain of our computations, a
return table is built while operations that have yet to be performed wait for
recursive procedure calls to return values. We discuss another way of doing
the computations, called iteration, in which there are no operations waiting
for procedure calls to return values, and hence no return table need be con-
structed. The factorial procedure and Fibonacci sequences are introduced. To
compare the efficiency of various methods for computing them, we investigate
the growth of execution time as the argument grows, demonstrating linear
and exponential growth rates.

4.2 Flat Recursion

We begin with three more examples of recursive procedures, with the recursion
being done over the top-level items in lists. In our examples of recursion
involving lists, we made the recursive step by applying the procedure to the
cdr of the list. The car of the list was then treated as a unit, which is why the
recursion was over the top-level items in the list. We refer to a recursion over

the top-level items of a list as aflat recursion, and we say that the procedure
so defined is flatly recursive or simply a flat procedure.

The first procedure we define is the two-argument version of the Scheme


procedure append, which has as parameters two lists, Isl and ls2 and builds
a list that consists of the top-level items in Isl followed by the top-level items
in ls2. We say that we are appending ls2 to (the end of) Isl. For example,

(append '(a b c) ' (c d)) ^ (a b c c d)


(append ' () ' (a b c) ) ^ (a b c)

We define append using recursion on the first list. Isl. Cdring on Isl ulti-
mately produces the base case in which Isl is empty. In the base case, when
Isl is empty. Is2 is returned. Thus we can begin the definition with the base
case:

(define append
(laabda (Isl ls2)
(if (null? Isl)
ls2
... )))

To express (append Isl ls2) in terms of (append (cdr Isl) ls2). observe
that (append (cdr Isl) ls2) differs from (append Isl ls2) only in the

absence of the first top>-level item in Isl. For example, if Isl is (a b c) and
ls2 is (d e), then (append (cdr Isl) ls2) gives us (b c d e). and only
(car Isl) remains to be included. Thus when Isl is not empty, (append
Isl ls2) is the same as (cons (car Isl) (append (cdr Isl) ls2)). We
can therefore complete the definition of append.

Program 4.1 append

(define append
(lambda (Isl ls2)
(if (null? Isl)
Is2
(cons 'car la 1)
( (append (cdr Issi) l82)))))

Another procedure often used is the Scheme procedure reverse, which


takes a list as its argument and builds a list consisting of the top-level items
in its argument list taken in reverse order. For example,

(reverse '
(1 2 3 4 5) ) =* (5 4 3 2 1)

(reverse '((1 2) (3 4) (5 6))) =^ ((5 6) (3 4) (1 2))

96 Data Drtien Recursion


We again use recursion and look at what reverse does to the cdr of the list

Is. In the first example above,

(reverse ' (2 3 4 5)) =^ (5 4 3 2)

To get reverse of (1 2 3 4 5) from (5 4 3 we must put the 1 into the


2),
last position in the list. We can do this with the procedure append if we make
the 1 into a list (1) and then append (1) to the end of (5 4 3 2). This is

the key to writing the definition of the procedure reverse.


We take theempty list as the base case and note that if we reverse the
items in the empty list, we still have the empty list. Thus we can begin the
definition with the terminating condition, which says that if the list is empty,
the empty list is returned.

(define reverse
(lambda (Is)
(if (null? Is)
'()

... )))

To get (reverse Is) from (reverse (cdr Is)), we must append the list

that is the value of (reverse (cdr Is)) to the front of the list that is the
value of (list (car Is)). We then complete the definition with

Program 4.2 reverse

(define reverse
(leunbda (Is)
(if (null? Is)
'()

(append (reverse (cdr Is)) (list (car Is))))))

A list of numbers (or n-tuple) is said to be sorted in increasing order if each


number in the list is less than or equal to the number following it in the list.

For example, (2.3 4.7 5 8.1) is sorted in increasing order. If we have two
lists, each sorted in increasing order, we can merge them into a single list in

increasing order. For example, if the list given above is merged with the list

(1.7 4.7), we get the list (1.7 2.3 4.7 4.7 5 8.1).
Let us now write a procedure merge, which takes two n-tuples, sorted-
ntpll and sorted-ntpl2, which have already been sorted in increasing order.

4.2 Flat Recursion 97


) ) )

and builds the list obtained by merging them into one sorted n-tuple. If either

list is empty, merge returns the other list. Otherwise we compare the car of
the lists and cons the smaller one onto the list obtained by merging the rest

of the two lists. This analysis leads to the following definition:

Program 4.3 merge

(define merge
(lambda (sorted-ntpll sorted-ntpl2)
(cond
((null? sorted-ntpll) sorted-ntpl2)
((null? sorted-ntpl2) sorted-ntpll)
((< (car sorted-ntpll) (car sorted-ntpl2))
(cons (car sorted-ntpll)
(merge (cdr sorted-ntpll) sorted-ntpl2))
(else (cons (car sorted-ntpl2)
(merge sorted-ntpll (cdr sorted-ntpl2) )) ) ))

We shall use merge in Chapter 10 when we discuss the sorting of lists.

The definition of reverse used the procedure append, which was defined
earlier. It does not matter which was defined first, as long as both are defined
when the procedure reverse is invoked.
The test of whether a nonnegative integer is even or odd gives us another

good example of one procedure using another in its definition. There are many
more direct ways of defining the predicates even? and odd?, but the one we
present now was chosen because it illustrates how each of two procedures
invokes the other in its definition. We use the fact that an integer is even if

its predecessor is odd and odd if its predecessor is even. Starting with any
nonnegative integer, reducing it successively by 1 will eventually bring it to
0. which is even. This analysis leads us to the following definitions:

Program 4.4 even?

(define even?
(lambda (int)
(if (zero'? int)
ft
(odd? (subl int ) ) ) )

98 Data Dnven Recursion


and

Program 4.5 odd?

(define odd?
(lanbda (int)
(if (zero? int)
«f
(even? (subl int)))))

In the definition of the procedure even?, the procedure odd? is called, and in

the definition of odd?, the procedure even? is called. This is an example of


mutual recursion in which each procedure calls the other. The two procedures
are said to be mutually recursive.
The procedure remove-lst defined in Chapter 2 removed the first top-level
occurrence of an item from a list of items. Let us now define a procedure
remove that removes all top-level occurrences of item from a list Is. As
before, the recursion will be flat, but now we continue the recursion until all

top-level occurrences of item have been removed from Is. The base condition
is (null? Is), and when it is true, the empty list is returned. Thus we begin
our definition with:

(define remove
(leunbda (iten Is)
(cond
((null? Is) '())
... )))

Next, if Is is not empty, (remove item (cdr Is))is exactly the same as

(remove item Is) when the first item in Is is item, for that item is removed.
On the other hand, when the first item in Is is not item, then we must cons
it onto (remove item (cdr Is)) in order to get (remove item Is). Thus

we complete the definition, which is presented in Program 4.6.


The definition of remove differs from that of remove-lst in the middle
clause of the cond expression. In remove-lst the recursion stopped when the
first occurrence of item was found, whereas in remove the recursion continues.

This difference is typical of what we see if we compare the definitions of


procedures that stop after the first occurrence of an item to those that continue
to the end of the list.The procedure remove uses equal? to test for sameness.
You could write a version named remq that uses eq? to test for sameness and

4.2 Flat Recursion 99


Program 4.6 remove

(define reaove
(lambda (it en Is
(cond
((null? Is) '())
((equal? (car Is) item) (remove item (cdr Is)))
(else (cons (ceir Is) (remove item (cdr Is)))))))

a version named remv that uses eqv? to test for sameness. The exercises
contain other procedures involving flat recursion that go to the end of the
lists instead of stopping after the first occurrence of a given item.

Exercises

Exercise 4'^-' insert-left


Define a procedure insert-left with parameters new, old, and Is that builds
a list obtained by inserting the item new to the left of each top-level occurrence
of the item old in the list Is. Test your procedure on:

(insert-left 'z 'a '(abaca)) ^^ (z a b z a c z a)


(insert-left 1 '(0 1 1)) ^(001001)
(insert-left 'dog 'cat '(my dog is fun)) ^=* (my dog is fun)
(insert-left 'two 'one '()) () ^
Exercise 4-^' insert -right
Define a procedure insert-right with parameters new, old. and Is that
builds a list obtained by inserting the item new to the right of each top-level
occurrence of the item old in the list Is. Test your procedure on:

(insert-right 'z 'a '(abaca)) =^ (a z b a z c a z)


(insert-right 1 '(0 1 D) ==* (0 1 1 0)

(insert-right 'dog 'cat '(my dog is fun)) =^ (my dog is fun)


(insert-right 'two 'one '()) =* ()

Exercise 4-3: subst


Define a procedure subst with parameters new, old. and Is that builds a list

obtained by replacing each top-level occurrence of the item old in the list Is
by the item new. Test your procedure on:

100 Data Driven Recursion


(subst 'z 'a '(abaca)) => (z b z c z)
(subst 1 '(01 D) =^ (0 0)
(subst 'dog 'cat '(my dog is fun)) =^ (my dog is fun)
(subst 'two 'one '()) =^ ()

Exercise 4-4' deepen-l


Define a procedure deepen-1 with parameter Is that wraps a pair of paren-
theses around each top-level item in Is. Test your procedure on:

(deepen-1 '(abed)) => ((a) (b) (c) (d))


(deepen-1 '((a b) (c (d e)) f)) => (((a b)) ((c (d e))) (f))
(deepen-1 '()) => ()

4.3 Deep Recursion

In this section, we consider recursion over all the sublists of a list. We say
that the sublist (b c) is nested in the list (a (b c)). It is convenient to have
some way of describing how deep the nesting is. If an item is not enclosed by
parentheses, that item has nesting level 0. For example, the item bird has
nesting level 0. The elements of a list such as (a b c) have nesting level 1.

Thus b has nesting level 1 while the whole list (a b c) has nesting level 0.
Then each additional layer of parentheses adds 1 to the nesting level, so that
the nesting level of the item c in (a (b (c d))) is 3. The objects in the list
that have nesting level 1 are the top-level objects of the list. The top-level
objects in the list (a (b c) (d (e f ))) are a, (b c), and (d (e f )).
We define a procedure count-all with parameter Is that counts those
items in the list Is that are not pairs. For example

1. (count-all '((a b) c ((d (e))))) => 6


2. (count-all '(() ())) =^ 3
3. (count-all '((()))) => 1

4. (count-all '()) =*•

To simplify our discussion, we use the adjective atomic to describe an item


that is not a pair. In this case, all of the atomic items in the list were counted,
not just the top-level items. Since the empty list is not a pair, the empty lists

that are included as items within the lists of Examples 1,2, and 3 are counted
as atomic items in the lists.

4-S Deep Recursion 101


The base case for the recursion is the empty Ust, for in that case, count-all
returns zero. Thus the definition begins with:

(define count-all
(lambda (Is)
(cond
((null? Is) 0)
... )))

If Is is not empty, we proceed eis we did in our previous examples and consider

how we can get (count-all Is) from (count-all (cdr Is)). The two
diff'er by the number of atomic items in (ceu: Is). If (car Is) is atomic,
then (count-all Is) has a value that is just one greater than the value of
(count-all (cdr Is)). Thus we can continue the definition with:

(define count-all
(lambda (Is)
(cond
((null? Is) 0)
((not (pair? (car Is))) (addl (count-all (cdr Is))))
... )))

When (car Is) is a pair (as is the case in Examples 1 and 3), we must count
the atomic items in (.cax Is) and add that amount to the value of (count-
all (cdr Is)) to get the value of (count-all Is). Thus we complete the
definition with:

Program 4.7 count-all

(define count-all
(lambda (Is)
(cond
((null? Is) 0)
((not (pair? (car Is))) (addl (count-all (cdr Is))))
(else (+ (count-all (car Is)) (count-all (cdr Is)))))))

In fact, we can combine the last two cond clauses if we write the definition as

follows:

102 Data Driven Recursion


(define count-all
(leunbda (Is)
(cond
((null? Is) 0)
(else (+ (if (pair? (car Is))
(count-all (car Is))
1)
(count-all (cdr Is)))))))

The recursion described differs from flat recursion in that when the car of the
list is a pair, we apply the procedure being defined both to the car and to the
cdr of the list. In flat recursion, the procedure being defined was applied only
to the cdr of the list. When the recursion is over all of the atomic items of a
list, so that in the recursive step the procedure is applied to the car of the
list and to the cdr of the list, we call it a deep recursion. A procedure defined
using a deep recursion will be referred to as a deeply recursive procedure or
simply a deep procedure to distinguish it from a flat procedure. Deep recursion
is also called tree recursion.
Before leaving the definition of count-all, we should observe that we could
have avoided the use of the not in the second cond clause by changing the
order in which we considered the last two cases. That would give us the
definition:

(define count-all
(lambda (Is)
(cond
((null? Is) 0)
((pair? (car Is))
(+ (count-all (car Is)) (count-all (cdr Is))))
(else (+ 1 (count-all (cdr Is)))))))

Many of the flat procedures defined earlier have analogs that are deep pro-
cedures. To we consider the procedure remove-all, which is
illustrate this,

analogous to remove. The procedure remove-all removes all occurrences of


an item item from a list Is. For example,

(remove-all 'a '((a b (c a)) (b (a c) a))) => ((b (c)) (b (c)))

The base case is the and when Is is empty, the empty


empty list, list is

returned. Thus we begin the definition of remove-all with:

4-3 Deep Recursion 103


(define remove-all
(lambda (item Is)
(cond
((null? Is) '())
... )))

We next express (remove-all item Is) in terms of (remove-all item


(cdr Is)). If (equal? (car Is) item) returns true, then (remove-all

item Is) is the same as (remove-all item (cdr Is)), and we have:

(define remove-all
(Icifflbda (item Is)
(cond
((null? Is) '())
((equal? (car Is) item) (remove-all item (cdr Is)))
... )))

If (cax Is) is a pair that is not the same as item, then we remove all occur-
rences of item from (car Is) and cons the result onto (remove-all item
(cdr Is)). Thus,

(define remove-all
(lambda (item Is)
(cond
((null? Is) '())
((equal? (car Is) item) (remove-all item (cdr Is)))
((pair? (ccir Is))
(cons (remove-eill item (ceir Is)) (remove-all item (cdr Is))))
... )))

Finally, if (car Is) is atomic and is not the same as item, we must cons it

back onto (remove-all item (cdr Is)) in order to get (remove-all item
Is). Wewrap up the definition in Program 4.8. We can combine the last two
cond clauses if we rewrite the definition as follows:

(define remove-all
(leimbda (item Is)
(cond
((null? Is) '())
((equal? (c«ur Is) item) (remove-all item (cdr Is)))
(else (cons (if (pair? (ceir Is))
(remove-all item (car Is))
(car Is))
(remove-all item (cdr Is)))))))

104 Data Driven Recursion


Program 4.8 remove-all

(define remove-all
(laj&bda (item Is)
(cond
((null? Is) '())
((equal? (car Is) item) (remove-all item (cdr Is)))
((pair? (car Is))
(cons (remove-all item (car Is)) (remove-all item (cdr Is))))
(else (cons (car Is) (remove-all item (cdr Is)))))))

we again see that when (car Is) is a pair not equal to item,
In this example,
the procedure remove-all is applied recursively to both the car and the
cdr of Is. Thus remove-all displays this characteristic behavior of deeply
recursive procedures.

We used equal? to test for sameness in the definition of remove-all. If

the arguments to which item is bound we can use eq? to


are always symbols,
test for sameness. In this case, we know that the item that
is the same as the

symbol we are removing is never a pair, so it is expedient to test for pair?


first. We can write the definition of remq-all as shown in Program 4.9. We

can similarly define remv-all, which uses eqv? in place of eq?.

Program 4.9 remq-all

(define remq-all
(leifflbda (symbl Isi)

(cond
((null? Is) •())
((pair? (car Is))
(cons (remq--all symbl (car Is)) (remq-all symbl (cdr Is.))))
((eq? (car Ifi) symbl) (remq -all symbl (cdr Is)))
(else (cons [car Is) (remq-.all s ymbl (cdr Is)))))))

When the flat procedure reverse is applied to a list, we get a new list with
the top-level objects in reverse order. Thus,

(reverse '(a (b c) (d (e f)))) => ((d (e f)) (b c) a)

We can also define a procedure reverse-all that not only reverses the order

4'3 Deep Recursion 105


of the top-level objects in the list but also reverses the order of the objects at
each nesting level with the sublists. We would then have:

(reverse-all ' (a (b c) (d (e f ))))=* (((f e) d) (c b) a)

For the base case, the list is empty, and (reverse-all '()) returns the
empty list. Thus the definition begins with:

(define reverse-zdl
(lambda (Is)
(cond
((null? Is) '())
... )))

To carry out the recursion, we build (reverse-all Is) from (reverse-all


(cdr Is)). In the latter, all of the elements of (reverse-all (cdr Is))
are already in the correct order. We have to see how to include the items of
(car Is). If (cax Is) is a pair, we have to reverse its elements and place
them at the end of (reverse-all (cdr Is)) with the procedure append.
Thus we have:

(define reverse-all
(lambda (Is)
(cond
((null? Is) '())
((pair? (ceur Is))
(append (reverse-cill (cdr Is))
(list (reverse-all (cau: Is)))))
... )))

In the remaining case, (car Is) is not a pair, so we merely place it at the

end of (reverse-all (cdr Is)).

(define reverse-all
(lambda (Is)
(cond
((null? Is) '())
((pair? (car Is))
(append (reverse-all (cdr Is))
(list (reverse-all (car Is)))))
(else
(append (reverse-adl (cdr Is))
(list (car Is)))))))

106 Data Driven Recursion


Once again, in this recursion we see the typical form of a deep recursion. We
applied reverse-all to both the car and the cdr of the list in the second
cond clause.
It is instructive to look back at this definition of reverse-all and observe
the similarity between the two alternatives that begin with append in the
last two cond clauses. They differ only in the application of reverse-all
to (car Is) in the last line. Because of this similarity,
we can combine the
two append expressions into one expression by putting the conditional branch
after (reverse-all (cdr Is) ) We get the following version of the definition
.

of reverse-all:

Program 4.10 reverse-all

(define reverse-all
(lambda (Is)
(if (null? Is)
'0
(append (reverse- all (cdr Is))
(list (if (pair? (car Is))
(reverse-all (car Is))
(car Is)))))))

In this section, we have seen how to write deeply recursive procedures.


These have the characteristic property that a recursive step applies the pro-
cedure being defined to both the car and the cdr of the list.

Exercises

Exercise 4-5: subst-all, substq-all


Define a procedure subst-all with call structure (subst-all new old Is)
that replaces each occurrence of the item old in a list Is with the item new.
Test your procedure on:

(subst-all 'z 'a '(a (b (a c)) (a (d a))))


=> (z (b (z c)) (z (d z)))
(subst-all '(1) '(((1) (0)))) => ((0 (0)))
(subst-all 'one 'two '()) =>
Also define a procedure substq-all in which the parameters new and old are
only bound to symbols, so that eq? can be used for the sameness test.

4-3 Deep Recursion 107


Exercise 4-6: insert-left-all
Define a procedure insert-left-all with call structure (insert-left-all
new old Is) that inserts the item new to the left of each occurrence of the
item old in the list Is. Test your procedure on:

(insert-left-all 'z 'a '(a ( (b a) ((a (c))))))


=> (z a ((b z a) ((z a (c)))))
(insert-left-all 'z 'a '(((a)))) ==> (((z a)))
(insert-left-all 'z 'a '()) =^

Exercise 4-7: sum-all


Define a procedure sum-all that finds the sum of the numbers in a list that

may contain nested sublists of numbers. Test your procedure on:

(sum-all '((1 3) (5 7) (9 11))) => 36


(sum-all '(1 (3 (5 (7 (9)))))) => 25
(sum-all '()) =>

4.4 Tree Representation of Lists

There is a convenient way of thinking of a list graphically as a tree that has


its root at the top and grows by branching downward. The original list is

a node that is located at the root. Each top-level object in the list forms a
new node connected to the root node by a branch. Each sublist itself then

becomes the root of a subtree, and the tree grows downward. For example,
the tree representing the list (a (b c d) ((e f) g) ) is given in Figure 4.11.
Each item or sublist of the original list is a node of this tree. Each sublist is

itself the root of a subtree of the original tree. Thus ((e f) g) corresponds
to the subtree given in Figure 4.12.
An item at the lower end of a branch that is not the top end of another
branch is called a leaf of the tree. We can readily see how deeply an item
is nested in the list by looking at its nesting level in the tree. For example,
in Figure 4.11, the leaf a is at nesting level 1 and the leaf e at nesting level

3. We say that the depth of a list is the maximum of the nesting levels of all

of its items. The list (a (b c d) ((e f) g)) has depth 3. With the tree
growing downward, we can say that the depth of a list is the nesting level of
its lowest leaves.
To traverse a tree, that is, to move down the tree from one node to another,
we use the procedures cau: and cdr. Taking the car of a list corresponds to

108 Data Driven Recursion


(a (b c d) ((e f) g))

((e f) g)

Figure 4.11 Tree representation of the list (a (b c d) ((e f) g))

((e f) g)

Figure 4.12 The subtree ((e f) g)

moving down one node on the leftmost branch of the tree. Taking the cdr
of a list corresponds to considering the tree that when the leftmost
is left

branch is omitted. Thus when taking the car, we move down one level on
the tree. When taking the cdr, we stay at the same level of the tree. With
an appropriate sequence of car and cdr applications, we can reach any node
of a tree. For example, in the tree in Figure 4.11, the node (e f) is reached
using caaddr.
We define a procedure depth that takes item as its argument and returns
its depth. The item may be either atomic or a list. If item is atomic, we

4-4 Tree Representation of Lists 109


Program 4.13 depth

(define depth
(lambda (item)
(if (not (pair? item))

(max (addl (depth (car item))) (depth (cdr item))))))

assign it depth 0. Since the empty list is atomic, it also has depth 0. We take
as the base case for the recursive definition the test (not (pair? item)), for
that corresponds to being at a leaf of the tree. We begin the definition of
depth with:

(define depth
(lambda (item)
(if (not (pair? item))

... )))

The depth of the whole tree is the larger of the depth of its leftmost branch
and the depth of the rest of its branches. Taking the cair of the list moves
down one node on the leftmost branch, so that the depth of the whole leftmost
branch is one greater than the depth of (car item). The depth of the rest

of the branches is just the depth of (cdr item). This gives us the definition
displayed in Program 4.13.
The procedure depth gives us the maximum number of levels in a tree
representing its argument. We next define a procedure that gives us a list of
the leaves on the tree as a list of atomic items, where each leaf is raised out
of its sublist to be at top level. We call this procedure flatten. When we
apply it to the ((e f ) g)), we get (a b c d e f g). The
list (a (b c d)
parameter of the procedure flatten will be Is. The base case is the empty
list, which flattens into itself. Thus we begin the definition of flatten with:

(define flatten
(leunbda (Is)
(cond
((null? Is) '())
... )))

When Is is not empty, we build (flatten Is) from (flatten (cdr Is))
by first determining whether (car Is) is a pair. If it is, we flatten (car Is)

110 Data Driven Recursion


and append the already flattened (flatten (cdr Is)) to it to get (flatten
Is). This gives us

(define flatten
(lEunbda (Is)
(cond
((null? Is) '())
((pair? (car Is))
(append (flatten (car Is)) (flatten (cdr Is))))
... )))

In the remaining case, (car Is) is atomic, so we cons it onto (flatten


(cdr Is)), and we complete the definition with

Program 4.14 flatten

(define flatten
(lambda (Is)
(cond
((null? Is) '())
((pair? (car Is))
(append (flatten (car Is)) (flatt<an (cdr Is))))
(else (cons (car ]-s) (flatt<sn (cdr Is)))))))

We have discussed flat and deep recursion. A flat recursion is over the
top-level items of a list. This is equivalent to a recursion over the nodes of
the corresponding tree, which are one level below the root. A deep recursion
is over all of the items in the list. This is equivalent to a recursion over the
leaves of the corresponding tree. That is why deep recursion is also referred
to as tree recursion.
We conclude this section with an example of a procedure that removes an
item from a list but only the first (leftmost) occurrence of that item in the
list. Let us name the procedure remove-leftmost and look at a couple of

examples.

1. (remove-leftmost 'b '(a (b c) (c (b a))))


=> (a (c) (c (b a)))

2. (remove-leftmost ' (c d) '((a (b c)) ((c d) e)))


=J> ((a (b c)) (e))

4-4 Tree Representation of Lists 111


In Example 1, the first b that occurs in (b c) is removed, but the second b
that occurs in (c (b a) ) is not removed. We denote the item to be removed
by item and the list by Is. The base case is again the empty list. When Is

is empty, the empty list is returned. Thus we begin the definition with the
terminating condition:

(define remove-leftmost
(lambda (item Is)
(cond
((null? Is) '())
... )))

In order to take care of arguments like that in Example 2, we use equal? as


the sameness predicate. If (car Is) is the same as item, the answer is (cdr
Is), so we continue the definition with:

(define remove-leftmost
(lambda (item Is)
(cond
((null? Is) '())
((equal? (car Is) item) (cdr Is))
... )))

If (car Is) is atomic and is not the same cis item, the answer is obtained
by consing (car Is) to the list obtained by removing the leftmost item from
(cdr Is). Thus we get:

(define remove-leftmost
(lambda (item Is)
(cond
((null? Is) '())
((equal? (car Is) item) (cdr Is))
((not (pair? Is))
(cons (car Is) (remove-leftmost item (cdr Is))))
... )))

We still have the case in which (car Is) is a nonempty list not equal to
item. If we analyze the recursion by looking at

(remove-leftmost item (cdr Is))

we see that we get a list with the first occurrence of item removed; but we
do not know whether this Weis the first occurrence of item in Is. We want to

112 Data Driven Recursion


Program 4.15 remove-leftmost

(define remove-leftmost
(lambda (item Is)
(cond
((null? Is) '())
((equal? (car Is) item) (cdr Is))
((not (pair? (car Is)))
(cons (car Is) (remove--leftmost item (cdr Is))))
( (member-all? item (car Is))
(cons (remove-leftmost item (car Is)) (cdr Is)))
(else (cons (car Is) (remove--leftmost item (cdr Is.)))))))

Program 4.16 member-all?

(define member-all?
(lambda (item Is)
(if (null? Is)
#f
(or (equal? (ceo- Is) item'
(and (not (pair? (car Is)))
(member-all'' item (cdr Is)))
(and (pair? (car Is))
(or (member--all? item (car Ie.))
(member--all? item (cdr Is.))))))))

remove only the first occurrence of item in Is, and its first occurrence may
not be in (cdr Is). In order to use this kind of argument, we must first

check to see whether the first occurrence of item in Is is in (car Is). We


do that with the helping procedure member-all?, a deeply recursive version
of member?, that we define after this definition. If item is in (car Is), we
cons (remove-leftmost item (car Is)) onto (cdr Is) to get the answer.
Otherwise, we cons (car Is) onto (remove-leftmost item (cdr Is)) to
get the answer. Thus we complete the definition as shown in Program 4.15.
The definition of member-all? is presented in Program 4.16.

A look at the definition of remove-leftmost reveals that the consequent in


the third cond clause and the alternative in the else clause are the same. We
can eliminate the repetition by interchanging the order of the tests we make.
The new version is given in Program 4.17.

4-4 Tree Representation of Lists 113


Program 4.17 remove-leftmost

(define remove-leftmost
(lambda (item Is)
(cond
((null? Is) '())
((equal? (car Is) item) (cdr Is))
((and (pair? (car Is)) (member-all? item (car Is)))
(cons (remove-leftmost item (car Is)) (cdr Is)))
(else (cons (car Is) (remove--leftmost item (cdr Is)))))))

The recursion in the procedure remove-leftmost differs from the list re-

cursions done earlier in that we have to test whether item is in the car of the

list before proceeding to build the answer. This means cdring through the
car of the list twice in some cases. We shall return to the consideration of
remove-leftmost in Chapter 5, where a definition is presented that avoids
this double cdring. We have now seen various examples of both flat and deep
(tree) recursions.

Exercises

Exercise 4-8: count -parens-all


Write the definition of a procedure count-parens-all that takes a list as its
argument and counts the number of opening and closing parentheses in the
list. Test your procedure on:

(count-parens-all '()) ^^ 2
(count -parens -all '((a b) c)) ^^ 4
(count-parens-all '(((a () b) c) () ((d) e))) =» 14

Exercise 4-9: count -background-all


Define a procedure count-background-all that takes as its arguments item
and a list Is and returns the number of items in Is that are not the same
as item. Use the appropriate sameness predicate for the data shown in the

examples. Test your procedure on:

(count-background-all 'a '((a) b (c a) d)) ^^ 3


(count-background-all 'a ( ( ( (b (((a)) c))))))
' => 2

(count-background-all 'b '()) ==*

114 Data Driven Recursion


Program 4.18 fact

(define fact
(lambda (n)
(if (zero? n)
1

(* n (fact (subl n))))))

Exercise 4-10: leftmost


Define a procedure leftmost that takes a nonempty list as its argument and
returns the leftmost atomic item in the list. Test your procedure on:

(leftmost '((a b) (c (d e)))) ==> a


(leftmost '((((c ((e f) g) h))))) => c
(leftmost '(() a)) => ()

Exercise 4-1 1- rightmost


Define a procedure rightmost that takes a nonempty list as its argument and
returns the rightmost atomic item in the list. Test your procedure on:

(rightmost '((a b) (d (c d (f (g h) i) m n) u) v))


(rightmost '((((((b (c)))))))) =* c
^ v

(rightmost '(a ())) => ()

4.5 Numerical Recursion and Iteration

Recursion can also be used in numerical calculations. We consider several


examples in this section. We begin with the procedure fact, which takes
a nonnegative integer n cis its parameter and returns its factorial — that is,

the number multiplied successively by all the positive integers less than that
number. For example, (fact 5) has the value 5x4x3x2x1 = 120. We
derive this procedure using much the same kind
we used with
of reasoning as
lists, but instead of using cdr to reduce the size of the argument, we use subl.

Eventually the successive applications of subl to the argument will reduce it


to 0. We use the convention that the factorial of is 1, so that (fact 0) is

1. The recursive step in this case is done by considering (fact (subl n)),
which gives us the successive products of all of the positive integers less than
n. To get (fact n) from (fact (subl n)), all we have to do is multiply it

by n. From this, we get the definition for fact in Program 4.18.

4-5 Numerical Recursion and Iteration 115


When the procedure fact is applied to a number, say 3, a return table
is built muchsame as the one that was built for the procedure swapper
the
in Chapter 2. The value of (fact 3) is denoted by answer-1. It is 3 times
(fact 2) so the evaluation of answer-1 must wait until answer-2 is evaluated,
,

where answer-2 is (fact 2). Thus the first two rows of the return table are:

answer-1 is (* 3 answer-2)
answer-2 is (fact 2)

When we evaluate (fact 2), the return table becomes

answer-1 is (* 3 answer-2)
answer-2 is (* 2 answer-3)
answer-3 is (fact 1)

When we evaluate (fact 1), the return table becomes

answer-1 is (* 3 answer-2)
answer-2 is (* 2 answer-3)
answer-3 is (* 1 answer-^)
answer-4 is (fact 0)

where (fact 0) is 1. Now that we have found that answer-4 is 1, we work our
way up the table, replacing each answer on the right side by the value obtained
for it in the row below. This process is known as backward substitution. This
gives us:

answer-4 " 1

answer-3 is 1

answer- 2 is 2
answer- 1 is 6

so (fact 3) is 6. In finding (fact 3), the return table has four rows. In the
last row, the value of the variable on the left was obtained directly from the
terminating condition of the program. Then each of the other three variables
on the right was computed with a multiplication, so there were three multi-
plications required to complete the computation of (fact 3). The building
up of the return table and the subsequent backward substitution may be
summarized in the following:

116 Data Driven Recursion


(fiact 3)
(* 3 (fad 2)) ;

(* 3 (fact 1)))
(* 2 :

(* 3 (* 1 (fact 0))))
(* 2
(* 3 (* 2 (* 1 1)))
(* 3 (* 2 D)
(* 3 2)
6

In general, to find the factorial of the number n, there would be n + 1

invocations of procedure fact. Thus the return table has n+ 1 rows. In the
last row, the value on the right is found to be 1 — the value returned when the
terminating condition is true. In each of the other n rows of the return table,
a multiplication is performed to find the value on the right, making a total of
n multiplications to complete the computation.

We observed that a return table is constructed when we compute the fac-


torial using the recursive procedure fact. When the terminating condition
becomes backward substitution must be performed on the return
true, the
table to get the answer. When the computation requires the construction of
a return table and backward substitution to get the answer, we say that the
computation is using a recursive process. We now look at another way of
defining a procedure to compute the factorial of a number that does not build
a return table. Instead, at each recursive invocation of the procedure, the
computations are performed without having to wait for other needed values,
and when the terminating condition is true, the answer is already computed
and is returned. In general, when the computer carries out a computation
without building a return table, so that backward substitution is not neces-
sary, the computational process is called an iterative process.

We have seen that in programs like the one written for fact, there is an
operation waiting for the value returned by the recursive procedure call. The
computational process so defined is not implemented as an iterative process.
On the other hand, we saw several iterative procedures, such as member?, in
which no operations waited for values returned by the recursive procedure
calls. some programming language implementations, when an iterative
In
procedure is executed, it is still possible that a return table is built up and later
reduced by backward substitution. However, in Scheme, when a procedure is
intended to be iterative, the computation is always implemented in such a
way that no return table is needed.

To implement the computation of the factorial procedure as an iterative


process, we define a procedure named fact-it that has two parameters: n,

4.5 Numerical Recursion and Iteration 117


which is the integer whose factorial we are computing, and ace, another in-
teger, called an accumulator, which stores the answer at each step. Here is

how it works in computing the factorial of 3. Initially, n is bound to 3 and


ace is bound to 1. On each recursive invocation of fact -it, n is reduced by
1, and aec is replaced by its old value multiplied by the previous value of n.
When the base case (zero? n) is true, aee is equal to the answer 6. This is

illustrated in the following table. The initial values of n and aee are in the
first row. The entries in the first column decrease by 1 while each entry in the
second column is computed by multiplying the two entries in the preceding
row.

n aee
3 1

2 3

1 6
6

To define fact-it, we begin with the base case for which n is zero. When
(zero? n) is true, the accumulator has the answer, so aee is returned. Thus
we begin the definition with:

(define fact-it
(lanbda (n ace)
(if (zero? n)
ace
... )))

If n is not zero, we call faet-it with n reduced by one and the accumulator
multiplied by n, so the definition is completed with:

Program 4.19 fact-it

(define fact -it


(lambda (n ace)
(if (zero? n)
ace
(fact-it (subl n) (* aec n)))))

Let's walk through an invocation (fact-it 3 1), writing the successive


recursive invocations of faet-it, and finally writing the value 6 that is re-

turned:

118 Data Driven Recursion


(fact-it 3 1)
(fact-it 2 3)
(fact-it 1 6)
(fact-it 6)
6

In this computation, no return table is built up waiting


uncomputed values
for
to be returned. The accumulator is bound to the answer when the terminating
condition is true, and the answer is returned without any backward substitu-
tion. The fact that there is no waiting operation on each recursive invocation

of fact-it is seen when we look at the last line of the definition. After the
procedure call, there is no further operation to be done. Compare this last
line with the last line,

(* n (fact (subl n)))

in the definition of fact. We see that after the procedure fact is called, the
result must still be multiplied by n. When fact-it is called, no additional
operations are performed on the result. Thus fact-it runs as an iterative
process, but fact does not. When we trace this iterative procedure, we see
that the computation does not build up a return table of operations waiting
for values to be returned.

If we count the number of times we call the procedure fact-it and the

number of multiplications, we see that the total number of multiplications


is the same for the procedures fact-it and fact. However, the backward
substitution in the return table, which is built up when evaluating fact,
requires more memory space than is needed when evaluating the iterative
fact-it, which needs no return table. In the next section, we look at another
example, the computation of the Fibonacci numbers, where the difference is

more dramatic.
To compute the we invoke (fact -it 3 1). If we do not like
factorial of 3,
to write the extra argument for the accumulator, we can define an iterative
version of fact that takes only one argument by writing

(define fact
(lambda (n)
(fact-it n 1)))

4-5 Numerical Recursion and Iteration 119


Exercises

Exercise 4-i2
Enter the procedure fact into the computer and compute (fact n) for n =
10, 20, 30, 40, 50 and 100. You will have an opportunity to observe how
the implementation of Scheme you are using displays large numbers.

Exercise 4-i3
What happens when you invoke (fact 3,5)?

Exercise 4-^4' harmonic-suin-it


Define an iterative procedure barmonic-sum-it that sums the first n terms
of the harmonic series

, 1111
Test your procedure by summing the harmonic series for 10 terms, 100 terms,
1000 terms, and 10,000 terms. It can be shown that

11 1 , ,11 1

23 n - ° - 23 n-1
where logn is the natural logarithm of n. Using the Scheme procedure log,
verify this inequality for the values of the sums computed above.

4.6 Analyzing the Fibonacci Algorithm

The following problem appeared in a textbook written in 1202 by the Italian


mathematician Leonardo of Pisa, who was the son of Bonacci, so his nickname,
taken from "filius Bonacci," became Fibonacci. How many pairs of rabbits are
born of one pair in a year? It was eissumed that every month a pair of rabbits
produces another pair and that rabbits begin to bear young two months after
theirown birth.
The sequence of numbers that give the number of pairs of rabbits each
month is 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377. This tells us that
at the end of one month, the first pair had a pair of offsprings, so we have
two At the end of two months, only one pair is old enough to have
pairs.

offsprings, so we have three pairs. At the end of three months, the first pair
of offsprings is old enough to bear young, so this time we get two new pairs,

120 Data Driven Recursion


and we have we continue in this way, we generate
five pairs altogether. If

the sequence given above. Observe that each number in the sequence is the
sum of the two numbers preceding it. It has become customary to begin the
sequence with 0, 1, and use the algorithm that says that the next number
is always the sum of the preceding two numbers. The nth number in this
sequence is called the nth Fibonacci number.
We now define a procedure fib that takes a nonnegative integer n as its

parameter and returns the Fibonacci number corresponding to n. We have


(fib 0) is 0, (fib 1) is 1, (fib 2) is 1, (fib 3) is 2, and in general, for

n > 1, (fib n) is the sum of (fib (- n 1)) and (fib (- n 2)). We now
use this last recursive condition to define the procedure fib in Program 4.20.

Program 4.20 fib

(define fib
(lambda (n)
(if « n 2)
n
(+ (fib (- n D) (fib (- n 2))))))

(fib 4)

(fib 3) (fib 2)

(fib 2) (fib 1) (fib 1) (fib 0)

(fib 1) (fib 0)

Figure 4.21 Recursion tree for (fib 4)

To trace how (fib 4) is evaluated, we make a tree (Figure 4.21) in which

4-6 Analyzing the Fibonacci Algorithm 121


the root is labeled (lib 4). This is evaluated by adding (lib 3) and (lib
2), so our tree will have two branches, one going to a node (lib 3) and
the other to a node (lib 2). Each of these gives rise to two branches, (lib
3) giving rise to branches to the nodes (lib 2) and (lib 1), and (lib 2)
giving rise to branches to the nodes (lib 1) and (lib 0). This continues
until all of the leaves are either (lib 1) or (lib 0), which are known to be
1 and 0, respectively. This tree is an example of a binary tree because each
node that is not a leaf has at most two branches going down from it.

From Figure 4.21, we see that each node corresponds to a procedure call that
is made in evaluating (lib 4). In this case, there are nine procedure calls.
Each branch point (a node from which two branches originate) corresponds
we can build a
to an addition, so there are four additions. In a similar way,
recursion tree for (lib 5), and we will have fifteen nodes and seven branch
points, hence fifteen procedure calls and seven additions. We suggest that you
draw the recursion trees for (lib 5) and for (lib 6) to see how large they
are and count the number of procedure calls and additions. It is not difficult

to see from the trees that if (calls-lib n) tells how many procedure calls
there are in computing (lib n) and (adds -lib n) tells how many additions
there are in computing (lib n), then these procedures satisfy the relations

(calls-lib 0) la 1

(calls-fib 1) is 1

(calls-fib n) is (addl (+ (calls-fib (- n 1)) (calls-fib (- n.2))))

and

(adds-f ib 0) is

(adds-fib 1) ts

(adds-f ib n) ts (addl (+ (adds-fib (- n 1)) (adds-fib (- n 2))))

We get Table 4.22 for these quantities.

n
(fib n)
012345678
1 1 2 3 8 13 21
5 34 55
9 10

(calls-fib n) 1 1 3 5 9 15 25 41 67 109 177


(adds-fib n) 1 2 4 7 12 20 33 54 88

Table 4.22 Count of procedure calls and additions

The number of procedure calls and the number of additions increase so


rapidly because in each procedure call, lib calls itself twice. This leads to

122 Data Driven Recursion


accl acc2
1
1 1
1 2
2 3
3 5
5 8
8 13

Table 4.23 Accumulator values for the iterative Fibonacci procedure

inefficiency since the same fib is called with the same arguments a number
of times, so that the different recursive calls repeat each other's work. In the
tree shown in Figure 4.21, (fib 2) is invoked twice and (fib 1) is invoked
three times. We next look at an iterative method for computing the Fibonacci
numbers.
A clue to how to set up an iterative process for computing the Fibonacci
numbers is found by observing that it takes the previous two numbers to
compute the next number in the sequence. Thus we have to store two jiumbers
at each step. We begin by storing the first two Fibonacci numbers, and 1

in accumulators, which we call accl and acc2. Thus at the start,

accl acc2
1

At each step, accl holds the current Fibonacci number and acc2 holds the
next one. Thus we can describe the algorithm that takes us from one step to
the next as follows:

1. The new value of accl is the same as the previous value of acc2.

2. The new value for acc2 is the sum of previous values of accl and acc2.

We apply these rules to extend the table to show the next six steps, as dis-

played in Table 4.23.


We are now ready to define a procedure fib- it that takes three arguments,
a nonnegative integer and the two accumulators, accl and acc2, and re-
n,

turns the Fibonacci number corresponding to n. There are two ways that we
can use the algorithm given to write the code. In the first method, we can
use the value stored in accl (initially 0) to give us the answer. In that case,
one iteration of the algorithm gives us (fib 1), two iterations give us (fib
2), and in general n iterations give us (fib n) for any positive n. In the

4.6 Analyzing the Fibonacci Algorithm 123


Program 4.24 fib-it

(define fib-it
(lambda (n accl acc2)
(if (= n 1)
acc2
(fib-it (subl n) acc2 (+ accl acc2)))))

second method, we can use the value stored in acc2 (initially l) to give us

the answer. In this case, one iteration of the algorithm gives us (fib 2), two
iterations give us (fib 3), and in general, (n— 1) iterations give us (fib n).
The second method is more efficient for getting the value of (fib n). We opt
to implement the second method.
Our iterative procedure fib-it takes three parameters: the positive integer
n and the two accumulators accl and acc2. To implement the algorithm
stated above, we successively replace acc2 by the sum of accl and acc2,
and replace accl by the previous value of acc2. Then to compute the nth
Fibonacci number, we must repeat the process (n — 1) times. We use the
variable n as a counter and reduce it by one on each pass. When n reaches 1,
the accumulator acc2 contains the answer. This leads to the definition given
in Program 4.24.

Let's walk through (fib-it 6 1) to see how this works. On successive


passes through the program, the following procedure calls are made:

(fib-it 6 1)
(fib-it 5 1 1)
(fib-it 4 1 2)
(fib-it 3 2 3)
(fib-it 2 3 5)
(fib-it 1 5 8)
8

and the answer is the final value of acc2, which is 8. To compute the sixth
Fibonacci number, we only make six procedure calls and 5 additions. In
general, to compute the nth Fibonacci number, we make n procedure calls
and do n — 1 additions. This is a noticeable improvement over the number
of procedure calls and additions when fib is invoked. The iterative version,

fib- it, is certainly more efficient and saves a considerable amount of time
in computing the Fibonacci numbers. The ordinary recursive version, fib, is
less efficient but it does have the advantage of being easier to define directly
in terms of the rule that defines the Fibonacci numbers.

124 Data Driven Recuraion


Again, we do not want to include the initial values of the accumulators
if

in each procedure call, we can define the iterative version of fib as

(define fib
(lambda (n)
(if (zero? n)

(fib-it n 1))))

We have seen that some methods of evaluating a given expression may take
more resources than other methods. The study of the efficiency of various
algorithms is called the analysis of algorithms. Let us denote the total re-
sources used in computing an expression that depends on an argument n to
be (res n). In our discussion, fib depended on the argument n, and we can
define as the resources used the sum of (calls-fib n) and (adds-^fib n).
Inspection of the table for (calls-fib n) shows that the following relation
exists between (calls-fib n) and (fib n):

(calls-fib n) = (addl (* 2 (subl (fib (addl n)))))

Similarly, (adds-f ib n) and (fib n) are related by

(adds-fib n) = (subl (fib (addl n)))

so that

(res n) = (addl (* 3 (subl (fib (addl n)))))

We now derive an estimate for (fib n). If you prefer, you can skip to
the formula for (fib n) given at the end of the derivation. We use the
fact that if a procedure satisfies the Fibonacci recurrence relation F{n) =
F{n — 1) + F{n - 2) and the initial conditions F(0) = and F{1) = 1, then
F[n) = (fib n) for all n. We begin by making a rather arbitrary assumption:
that F[n) gets large like some number a raised to the nth power. We then look
for restrictions that can be placed on the number a in order for the function

a" to satisfy the Fibonacci recurrence relation. If we are lucky enough to find
such conditions that determine a, we have solved the problem of finding a
formula for F{n). Substitution of a" into the recurrence relation gives us

„n— 1
^n
a == a + a„n—2
I

4-6 Analyzing the Fibonacci Algorithm 125


.

and dividing through by a" ^ gives us the simple relation

a^ =0 + 1

This quadratic equation has the positive root


(l + v/5)
a

and the negative root


(l-v/5)
'= 2

which are approximately 1.618 and —0.618, respectively.


It is easily verified that since both a" and 6" satisfy the Fibonacci recurrence
relation, then for any pair of numbers A and 5, the sum F(n) = Aa" + 56"
also satisfies the same recurrence relation. We thus try to find values of A
and B so that F(0) = and F(l) = 1. The constants A and B will now be
evaluated from the fact that

/'(0) = =A+5
F(l) = l = .4a + 56

We find that A — -B = l/\/E and that with these values of A and B, F{n)
and (lib n) are the same for n = and n = 1, and that they both satisfy
the Fibonacci recurrence relation for all n. This means that they are the same
for £dl n, and we have

Thus (lib n) is somewhat less than 1.7", and (res n) is somewhat less than
3(1.7").
In general, we say that the procedure (res n) is of order 0{f{n)) for some
function / of n if there is a constant K such that (res n) < Kf{n) when n
is sufficiently large. we can say (res n) = 0(1.7") and since it
In our case,
grows like the nth power of a number greater than 1, we say that (res n)
has exponential order when computing (lib n)
On the other hand, the operation count (res n) for computing (lib-it
n 1) is 2n - 1, which is simply 0{n). Here the n does not appear in an
exponent, but rather (res n) is simply a constant times n. We say that in this
case, (res n) has linear order. Thus the time required to compute (lib n)
grows exponentially with n, while the time required to compute (lib- it n

126 Data Driven Recursion


Program 4.25 reverse-it

(define reverse-it
(lambda (Is ace)
(if (null? Is)
ace
(reverse-it (cdr Is) (cons (cEir Is) ace)))))

1) grows linearly with n. We have seen what a dramatic difference this


makes.
In our two examples of iterative programs, we used procedures defined on
numbers. It is also possible to use similar methods to write iterative versions
of some of the list-processing procedures we considered earlier. For example,
consider the procedure reverse, which takes a list of items Is and returns
a list with the items in reverse order. We can write an iterative version
reverse-it that takes two arguments, a list of items Is and an accumulator
ace, which is initialized to be the empty list. The code for reverse-it is
given in Program 4.25. We now can obtain the procedure reverse by writing

(define reverse
(lambda (Is)
(reverse-it Is '())))

We leave it as an exercise to compare this iterative version with the earlier


recursive version of reverse. If we actually walk through each version with a
simple example, we see that the accumulator already is the answer when Is is

empty, whereas in the recursive version, we still have to use backward substi-
tution in a return table to get the answer. Furthermore the iterative version
does not use the helping procedure append. Generally, iterative versions tend
to require more arguments.

Exercises

Exercise 4-15
Rewrite the recursive version of the procedure fib with the line

(writeln "n = " n)

inserted just below the line (Isimbda (n). Then compute (fib 4) and com-
pare the results with the tree in Figure 4.21. Also compute (fib 5) and (fib
6) and observe how the number of recursive calls to fib increases.

4-6 Analyzing the Fibonacci Algorithm 127


Exercise 4- 16
Rewrite the iterative version of the procedure fib-it with the line

(writeln "n = " n ", accl = " accl ", acc2 = " acc2)

inserted just below the line

(lambda (n accl acc2)

Compute (fib-it 4 and compare the output with the output for (fib
1)

4) in the preceding exercise. Do the same for (fib-it 5 1) and (fib-it

6 1).

Exercise 4^7: calls-fib, adds-fib


Write the definitions of the procedures calls-fib and adds-fib discussed in

this section. Test your procedures on the values given in Table 4.22. Also
evaluate each of these procedures for larger values of n to get an idea of their

rates of growth.

Exercise 4-18: length-it


Write an iterative version length-it of the procedure length, that computes
the length of a list.

Exercise 4-19: mk-asc-list-of-ints, mk-desc-list-of-ints


Write an iterative procedure mk-asc-list-of-ints that, for any integer n,

produces a list n in ascending order. Then write an


of the integers from 1 to
iterative procedure mk-desc-list-of-ints that, for any integer n, produces
a list of integers from n to 1 in descending order.

Exercise 4-20: occurs, occurs-it


Define both recursive and iterative versions of a procedure occurs that counts
the number of times an item occurs at the top level in a list. Call the iterative
version occurs-it. Test your procedures by counting how many times the
item a occurs at top level in each of the following lists:

(a b a c a d)
(b c a (b a) c a)
(b (c d))

128 Data Driven Recursion


Locally Defined Procedures

5.1 Overview

When we bind a variable to some value using define, we are able to use that
variable to represent the value to which bound either directly in response
it is

to a Scheme prompt or within a program that we are writing. Does this


mean that we have to think of new names for every variable we use when we
write many programs? No. Scheme gives us a mechanism for limiting where
bindings are in effect. In this chapter, we look at ways of binding variables
so that the binding holds only within a program or part of a program. The
main tools for doing this are two special forms with keywords let and letrec.
After introducing them, we use them to implement polynomials as a data type
in Scheme. We then apply the polynomial methods we develop to a discussion

of binary numbers, which form the basis of machine computation.

5.2 Let and Letrec

You may have wondered how Scheme knows what value to associate with
various occurrences of a variable. When some value is assigned to a variable,
we may think of that information being stored in a table with two columns: the
left one for variable names and the right one for the associated values. Such

a table is called an environment. A number of variables (such as those bound


to procedures) like +, , catx, and cons are predefined. These definitions are

kept in a table which we call the initial global environment. This initial global
environment is in place whenever you start up Scheme. When a given variable
is encountered in an expression, Scheme looks through its environment to see
if bound to a value. Naturally, the variable + is bound
the variable has been
to the arithmetic operation we usually associate with the addition procedure,
and so on.
In addition to having the predefined Scheme variables, we have seen how
to use define to bind a variable to a desired value. The expression (define
var val) binds the variable var to the value val. We can again think of the
variables we define ourselves as being placed in a table which we call the user
global environment and when a variable is encountered in an expression, the
global environment (which includes both the user and initial global environ-
ments) is scanned to see if that variable is bound to a value. If a binding
cannot be found, a message is written saying that the variable is unbound in

the current environment. The user global environment remains in effect until
the user exits from Scheme.
Variables are also used as parameters to procedures that are defined by a
lambda expression. For example, in the lambda expression

(lambda (x y) (+ x y))

the variables x and y occurring in the body (+ x y) of the lambda expression


are locally bound (or lambda bound) in the expression (+ x y) since the x and

y occur in the list of parameters of that lambda expression. If we apply the


procedure, which is the value of this lambda expression, to the arguments 2
and 3, as in

(danbda (x y) (+ x y)) 2 3)

we can think of a new table being made, called a local environment, which
is associated with this procedure call. In this local environment, x is locally
bound to 2 and y is bound
locally to 3. Then substituting 2 for x and 3 for

y gives (+ x y) the value 5, and

((lambda (x y) (+ x y)) 2 3)

returns the value 5.

A variable occurring in a lambda expression that is not lambda bound by


that expression is called free in that expression. If we consider the expression

(lambda (f y) (f a (f y z)))

the variables f and y are lambda bound in the expression, and the variables
a and z are free in the expression. When the application

130 Locally Defined Procedures


((lambda (f y) (f a (f y z))) cons 3)

is evaluated, the operator (which is the lambda expression) and its two oper-
ands are first evaluated. When the lambda expression is evaluated, bindings
are found for the free variables in a nonlocal environment. Then, with these
bindings for the free variables, the body of the lambda expression is evaluated
with 1 bound to the procedure, which is the value of cons, and y bound to
3. If either of the free variables is not bound in a nonlocal environment, a
message to that effect appears when the application is made. On the other
hand, if a is bound to 1 and z is bound to (4) in a nonlocal environment,
then this application evaluates to (1 3 4).
We used the term nonlocal environment in the previous paragraph when
we referred to the bindings of the free variables in the body of a lambda
expression. Those bindings may be found in the global environment or in a
local environment for another lambda expression. This is illustrated by the
following example:

((lambda (x)
((lambda (y)
(- X y))

15))
20)

The variable x is free in the body of the inner lambda expression, but its

binding is found in the local environment for the outer lambda expression.
The value of the expression is 5.

In the example

(lambda (x y) (+ x y))

the local bindings hold only in the body (+ x y) of the lambda expression,
and when we leave the body, we can for the moment think of the local en-
vironment as being discarded. The expression (+ x y) is said to be in the
y). In general, an expression is said to
scope of the variable x (and also of
be in the scope of a variable x if that expression is in the body of a lambda
expression in which x occurs in the list of parameters.
By looking at a Scheme program, one can tell whether a given expression is

in the body of some lambda expression and determine whether the variables
in that expression are lambda bound. A language in which the scope of the
variables can be determined by looking only at the programs is called lexically
scoped. Scheme is such a language.

5.2 Let and Letrec ISl


Scheme provides several other ways of making these local bindings for vari-
ables, although we shall later see that these are all ultimately related to
lambda bindings. The two that we discuss here are let expressions and le-
trec expressions. To bind the variable var to the value of an expression val

in the expression body, we use a let expression (which is a special form with

keyword let) with the syntax:

(let i(var val)) body)

To make several such local bindings in the expression body, say vari is to

be bound to vali var2 to val2


,
vavn , , to vain ,
we write

(let ((vari vali) (var2 va/2) . . . (varn vain)) body)

The scope of each of the variables vari, var2, . , vaVn is only body within
the let expression. For example, the expression

(let ((a 2) (b 3))


(+ a b))

returns 5. bound to 2 and b


Here a is is bound to 3 when the body (+ a b)
is evaluated. Another example is

(let ((a +) (b 3))


(a 2 b))

returns 5, since a is bound to the procedure associated with + and b is bound


to 3. Similarly, in the expression

(let ((add2 (lambda (x) (+ x 2)))


(b (* 3 (/ 2 12))))
(/ b (add2 b)))

the variable add2 is bound to the procedure to which (lambda (x) (+ x 2))
evaluates, which increases its argument by 2, and b is bound to 0.5, and the
whole expression returns 0.2.

The local binding always takes precedence over the global or other nonlocal
bindings, as illustrated by the following sample computation:

132 Locally Defined Procedures


[1] (define a 5) [5] (let ((a 5))
[2] (addl a) (begin
6 (writeln (addl a))
[3] (let ((a 3)) (let ((a 3))
(addl a)) (sriteln (addl a)))
4 (addl a)))
[4] (addl a) 6
6 4
6

The define expression makes a binding of a to 5. When a is encountered in


(addl a) in [2] , its value is found in the global environment and 6 is returned.
In [3] , a is locally bound to 3, and the expression (addl a) is evaluated with
this local binding to give the value 4. The scope of the variable a in the let
expression is only the body of the let expression. Thus in [4] , the value of
the variable a in (addl a) is again found in the global environment, where
a is bound to 5, so the value returned for (addl a) is 6. In [5], we see a
version of the same computation in which no global bindings of a are made,
but here the local binding takes precedence over the nonlocal bindings.
We get a better understanding of the meaning of the let expression

(let ((a 2) (b 3))


(+ab))

when we realize that it is equivalent to an application of a lambda expression:

((lambda (a b) (+ a b)) 2 3)

To evaluate this application, we first bind a to 2 and b to 3 in a local envi-


ronment and then evaluate (+ a b) in this local environment to get 5.

In general, the let expression

(let iivari vali) ivar2 ^0/2) ... ivarn vain)) body)

is equivalent to the following application of a lambda expression:

((lambda ivari var2 ... varn) body) vali va/2 ... vain)

From this representation, we see that any free variable appearing in the
operands vali, va/2, . ., vain is looked up in a nonlocal environment. For
example, let's consider

5.2 Let and Letrec 133


[1] (define a 10) [4] (let ((a 10) (b 2))

[2] (define b 2) (let ((a (+ a 5)))

[3] (let ((a (+ a 5))) (* a b)))

( a b)) 30
30

In this example, a is bound bound globally


globally to 10 in Cl], and b is

to 2 in [2]. Then in [3], the expression (+ a 5) The


is first evaluated.^
variable a is free in the expression (+ a 5), so the value to which a is bound
must be looked up in the nonlocal (here global) environment. There we find
that a is bound to 10, so (+ a 5) is 15. The next step is to make a local
environment where a is bound to 15. We are now ready to evaluate the body
of the let expression (* a b). We first try to look up the values of a and b
in the local environment. We find that a is locally bound to 15, but b is not

found there. We
must then look in the nonlocal (here global) environment,
and there we find that b is bound to 2. With these values, (* a b) is 30, so
the let expression has the value 30. In [4] we see a similar program in which
,

the free variables are looked up in a nonlocal but not global environment.
Looking back at the let expressions, we see how the lexical scoping helps us
decide which environment (local or nonlocal) to use to look up each variable.
It is important to keep track of which environment to use in evaluating an
expression, for if we do not do so, we might be surprised by the results. Here
is an interesting example:

[1] (define addb


(let ((b 100))
(Icunbda (x)
(+ X b))))
[2] (let ((b 10))
(addb 25))
125

Because b is bound to 10 in [2] and (addb 25) is the body of the let expres-
sion with this local environment, one might be tempted to say that the answer
in [2] should have been 35 instead of 125. In [1] , however, the lambda ex-
pression falls within the scope of the let expression in which b is bound to

^ The symbol +is also free in (+ a 5 ) and its value is found in the initi£d global environment
,

to be the addition operator. The number 5 eveduates to itself. Simileirly, the symbol » is free
in the body, eind its value is found in the initial globed environment to be the multiplication
operator.

134 Locally Defined Procedures


100. This is the binding that is "remembered" by the lambda expression, and
when it is later applied to the argument 25, the binding of 100 to b is used
and the answer is 125.
Let's look at [1] again. The variable addb is bound to the value of the
lambda expression, thereby defining addb to be a procedure. The value of
this lambda expression must keep track of three things as it "waits" to be

applied: (1) the list of parameters, which is (x), (2) the body of the lambda
expression, which is (+ x b), and (3) the nonlocal environment in which the
free variable b is bound, which is the environment created by the let expression
in which b is bound to 100. The value of a lambda expression is a procedure
(also called a closure), which consists of the three parts just described. In
general, the value of any lambda expression is a procedure (or closure) that
consists of (1) the list of parameters (which follows the keyword lambda), (2)
the body of the lambda expression, and (3) the environment in which the
free variables in thebody are bound at the time the lambda expression is
evaluated. When the procedure is applied, its parameters are bound to its
arguments, and the body is evaluated, with the free variables looked up in
the environment stored in the closure. Thus in [2], (addb 25) produces the
value 125 because the addb is bound to the procedure in which b is bound to
100.
Consider the following nested let expressions:

(let ((b 2))


(let ((add2 (lambda (x) (+ x b)))
(b 0.5))
(/ b (add2 b))))

The first let expression sets up a local environment that we call Environment 1

(Figure 5.1).

m
Figure 5.1 Environment 1

The inner let we call


expression sets up another local environment, which
Environment 2. The first entry in this environment is add2, which is bound
to the value of (lambda (x) (+ x b)). The x in (+ x b) is lambda bound
in that lambda expression, and the value of b can be found in Environment 1.

5.2 Let and Letrec 1S5


But the inner let expression is in the body of the first let expression, so

Environment 1 is in effect and we find that the value associated with b in

Environment 1 is 2. Thus we have Environment 2 (Figure 5.2).

add2 Procedure (x) (+ I b) Environment 1

0.5

Figure 5.2 Environment 2

All of the variables in the expression to which add2 is bound are either
bound in that expression itself (a^ was x) or are bound outside of the let
expression (as was b). We are now ready to evaluate the expression (/ b
(add2 b)). In which environment do we look up b? We always search the
environments from the innermost let or lambda expression's environment out-
ward, so we search Environment 2 first, finding that b is bound to 0.5. Thus
the whole expression is (/ 0.5 2.5), which evaluates to 0.2.
As an example of how let is used in the definitions of procedures, we
reconsider the definition of the procedure remove-leftmost, which was given
in Program 4.15. Recall that our objective is to produce a list the same
as the list Is except that it has removed from it the leftmost occurrence
of item. In the base case, when Is is empty, the answer is the empty list.

If (car Is) is equal to item, (car Is) is the leftmost occurrence of item
and the answer is (cdr Is). If neither of the cases is true, there are two
possibilities: either (car Is) is a pair, or it is not a pair. If it is a pair,
we want to determine whether it contains item. In Program 4.15, we used
member-all? to determine this. Another way is to check whether (car Is)
changes when we remove the leftmost occurrence of item from it. If so, then
item must belong to (car Is), in which case the answer is

(cons (remove-leftmost item (car Is)) (cdr Is))

But if we use this approach, we have to evaluate

(remove-leftmost item (car Is))

twice, once when making the test and again when doing the consing. To avoid
the repeated evaluations of the same thing, we use a let expression to bind a
variable, say r em-list, to the value of

136 Locally Defined Procedures


(remove-leftmost item (cju: Is))

and use rem-list each time the value of this expression is needed. Here is

the new code for remove-leftmost:

Program 5.3 remove-leftmost

(define remove-leftmost
(lambda (item Is)
(cond
((null? Is) '())
((equal? (car Is) item) (cdr Is))
((pair? (car Is))
(let ((rem-list (remove-leftmost item (car Is))))
(cons rem-list (cond
((equal? (ceir Is) rem-list)
(remove--leftmost item (cdr Is )))
(else (cdr Is))))))
(else (cons (ecu: '.

Ls) (remove--leftmost item (cdr Is )))))))

In a let expression

(let ((war val)) body)

any variables that occur in val and are not bound in the expression val itself

must be bound outside the let expression (i.e., in a nonlocal environment), for
in evaluating val, Scheme looks outside the let expression to find the bindings

of any free variables occurring in val. Thus

(let ((fact (lambda (n)


(if (zero? n)
1

(* n (fact (subl n) ))))))


(fact 4))

will return a message that fact is unbound. You should try entering this code
to become familiar with the messages that your system returns. This message
refers to the fact occurring in the lambda expression (written here in italics),

5.2 Let and Letrec 137


which is not bound outside of the let expression. ^ Thus
we want if to use a

recursive definition in the "val" part of a let-like expression, we have to avoid

the problem of unbound variables that we encountered in the above example.


We can avoid this difficulty by using a letrec expression (a special form with
keyword letrec) instead of a let expression to make the local binding when
recursion is desired.
The syntax for letrec is the same as that for let:

(letrec ((.vari vali) (vor2 ^0^3) ••• (.varn vain)) body)

but now any of the variables vari, var2, • • • , varn can appear in any of the
expressions vali , va/2 , • • • , vain , and refer to the locally defined variables

uori, var2, . . •, varn, so that recursion is possible in the definitions of these


variables. The scope of the variables vari, var2, . .
.
, varn now includes vali,
val2, . . ., vain, as well as body. Thus,

(letrec ((fact (leunbda (n)


(if (zero? n)
1

(* n (fact (subl n) ))))))


(fact 4))

has the value 24.


We can also have mutual recursion in a letrec expression, as the next ex-
ample illustrates:

(letrec ((even? (lambda (x)


(or (zero? x) (odd? (subl x)))))
(odd? (lambda (x)
(and (not (zero? x)) (even? (subl x))))))
(odd? 17))

has the value #t.


In Program we take another look at the iterative version of the factorial
5.4
procedure discussed in Program 4.19, this time written with letrec. Here we
are able to define the procedure fact with parameter n and define the iterative
helping procedure fact-it within the letrec expression. This enables us to

Ifwe call (fact 0), the value 1 is returned, since the consequent of the if expression is
true and the alternative, in which the call to fact is made, is not evaluated. In this case
no error message would result.

138 Locally Defined Procedures


Program 5.4 fact

(define fact
(lambda (n)
(letrec ((fact-it
(lambda (k ace)
(if (zero? k)
ace
(fact-it (subl k) (* k ace))))))
(fact-it n 1))))

Program 5.5 swapper

(define swapper
(lambda (x y Is)
(letrec
((swap
(lambda (Is*)
(eond
((null? Is*) '())
((equal? (car Is*) x) (eons y (swap (cdr Is*))))
((equal? (ceir Is*) y) (eons X (swap (cdr Is*))))
(else (cons (<:ar Is*) (swap (cdr Isi'))))))))
(swap Is))))

define an iterative version of fact without having to use a globally defined


helping procedure. There is an advantage to keeping the number of globally
defined procedures small to avoid name clashes. Otherwise you might forget
that you used a name for something else earlier and assign that name again.
The more convenient way of writing code for
letrec expression provides a

procedures that take several arguments, many of which stay the same through-
out the program. For example, consider the procedure swapper defined in
Program 2.8, which has three parameters, x, y, and Is, where x and y are
items and Is is a list. Then (swapper x y Is) produces a new list in which
x's and y's are interchanged. Note that in Program 2.8 each time we invoked
swapper recursively, we had to rewrite the variables x and y. We can avoid
this rewriting if we use letrec to define a local procedure, say swap, which

takes only one formal argument, say Is*, and rewrite the definition of the
procedure swapper as shown in Program 5.5.

5.2 Let and Letrec 139


The parameter to swap is Is*, and when the locally defined procedure

swap is called in the leist line of the code, its argument is Is, which is lambda
bound in the outer lambda expression. We could just as well use the variable
Is instead of Is* as the parameter in swap since the lexical scoping specifies
which binding is in effect. When we
swapper recursively in the old code,
call

we write all three arguments, whereas when we call swap recursively in the
new code, we must write only one argument. This makes the writing of the
program more convenient and may make the code itself more readable.
In this section, we have seen how to bind variables locally to procedures
using the special forms with keywords let and letrec. We use these impor-
tant tools extensively in writing programs that are more efficient and easier
to understand.

Exercises

Exercise 5.1
Find the value of each of the following expressions, writing the local environ-
ments for each of the nested let expressions. Draw arrows from each variable
to the parameter to which it is bound in a lambda or let expression. Also
draw an arrow from the parameter to the value to which it is bound.
a. (let ((a 5))
(let ((fun (lambda (i) (max x a))))
(let ((a 10)
(x 20))
(fun 1))))

b. (let ((a 1) (b 2))


(let ((b 3) (c (+ a b)))
(let ((b 5))
(cons a (cons b (cons c '()))))))

Exercise 5.2
Find the value of each of the following letrec expressions:

a. (letrec
((loop
(lambda (n k)
(cond
((zero? k) n)
(« n k) (loop k n))
(else (loop k (remainder n k) ))))))
(loop 9 12))

140 Locally Defined Procedures


b. (letrec
((loop
(leifflbda (n)
(if (zero? n)

(+ (remainder n 10) (loop (quotient n 10)))))))


(loop 1234))

Exercise 5.3
Write the two expressions in Parts a and b of Exercise 5.1 as nested lambda
expressions without using any let expressions.

Exercise 5.4
Find the value of the following letrec expression.

(letrec ((mystery
(lambda (tuple odds evens)
(if (null? tuple)
(append odds evens)
(let ((next-int (car tuple)))
(if (odd? next-int)
(mystery (cdr tuple)
(cons next-int odds) evens)
(mystery (cdr tuple)
odds (cons next-int evens))))))))
(mystery '
(3 16 4 7 9 12 24) ' '
()))

Exercise 5.5
We define a procedure mystery as follows:

(define mystery
(leUDbda (n)
(letrec
( (mystery-helper
(lambda (n s)
(cond
((zero? n) (list s))
(else
(append
(mystery-helper (subl n) (cons s))
(mystery-helper (subl n) (cons 1 s) )))))))
(mystery-helper n '()))))

What is returned when (mystery 4) is invoked? Describe what is returned


when mystery is invoked with an arbitrary positive integer.

5.2 Let and Letrec 141


Exercise 5.6: insert-left-all
Rewrite the definition of the procedure insert-left-all (See Exercise 4.6.)
using a locally defined procedure that takes the list Is as its only argument.

Exercise 5.7: fib


As Program 5.4 for fact, write an iterative
in definition of fib using fib-it
(See Program 4.24.) as a local procedure.

Exercise 5.8: list-ref


Program 3.7 is a good definition of list-ref. Unfortunately, the informa-
tion displayed upon encountering a reference out of range is not as complete
as we might expect. In the definitions of list-ref, which precede it, how-
ever, adequate information is displayed. Rewrite Program 3.7, using a letrec
expression, so that adequate information is displayed.

5.3 Symbolic Manipulation of Polynomials

One of the eidvantages of a list-processing language like Scheme is its con-


venience for manipulating symbols in addition to doing the usual numericeil
calculations. We illustrate this feature by showing how to develop a sym-
bolic algebra of polynomials. By a symbolic algebra we mean a program that
represents the items under discussion as certain combinations of symbols and
then performs operations on these items as symbols rather than as numerical
values.
We begin by reviewing what meant by a polynomial. An expression 5z^
is

is referred to as a term in which


5 is the coefficient and the exponent 4 is

the degree. In general, a term is an expression of the form a^x^ where the ,

coefficient ajk is a real number emd the degree A; is a nonnegative integer. The

symbol x is treated algebraically as if it were a real number. Thus we may


add two terms of the same degree, as illustrated by 5z^ -\- 3z^ = 8z^. In
general, the sum of two terms of like degree is a term of the same degree with
coefficient that is the sum of the coefficients of the two terms. This rule is

expressed in symbols by

akx^ + hkx^ = (ajt + bk)x^

A term can also be multiplied by a real number, as illustrated by 7(5z^) =


35z^. In general, when we multiply the term akX^ by the reaJ number c, the
product is a term that has coefficient cajt and the same degree; thus

c(ajkz*) = (caifc)z*

14s Locally Defined Procedures


We may also multiply two terms using the following rule: the product of two
terms is a term with degree equal to the sum of the degrees of the two terms
and with coefficient equal to the product of the coefficients of the two terms.
This is expressed symbolically by

Here is how it looks in a numerical example: {3x^){7x^) = 21x^. It is custom-


ary to write a term of degree by writing only its coefficient. The term aix^
of degree 1 is usually written as aix, omitting the exponent 1 on x. Thus
3a;° is written as 3, and 5x^ is written as 5x. Terms of positive degree with
coefficients are generally omitted.
Two terms of like degree can be added to produce a term of the same
degree, but two terms of different degrees do not produce a term when added.
Instead, we can only indicate the addition by placing a plus sign between
the two terms. A polynomial is a sum of a finite number of terms, usually
arranged in order of decreasing degree. The degree of the polynomial is the
maaimum of the degrees of its terms. Thus the polynomial Sx"* + bx^ + 12
has degree 4, and the terms of degree 3 and 1 have coefficient and are not
written. In general, a polynomial of degree n is of the form

anX^ + an-ix^~ + • • •
+ a2X + aix + ao

where the coefficients at, for k — 0, . . .,n denote real numbers. The sum of
two polynomials is the polynomial obtained by adding all of the terms of both
polynomials, using the rule given above for adding terms of like degree. Thus
the sum of

3x^ + 5x^ + 12 and 7x^ + Gx'' - x^ + llx - 15

is the polynomial
7x^ + 9x'^ + 4x2 + llx-3
The term of highest degree in a polynomial is known as its leading term, and
the coefficient of the leading term is known as its leading coefficient. The
leading term of 3x'* + 5x'^ + 12 is 3x'*, and its leading coefficient is 3.

Our goal is to write programs that produce the sum and product of poly-
nomials. We saw the definition of the sum in the discussion above, and later
we shall define the product. As in our development of exact arithmetic in
Chapter 3, we again assume that certain constructor and selector procedures

for polynomials have been predefined and return to their definition later in
this section when we consider the actual representation of the polynomials in

5.3 Symbolic Manipulation of Polynomials 143


the computer. We proceed to describe what these selector and constructor
procedures do when applied to a polynomial.
There are three selector procedures: degree, leading-coef and rest-of- ,

poly. If poly is a polynomial, then (degree poly) is the degree of poly and
(leading-coef poly) is the leading coefficient of poly. There is a zero poly-
nomial, the-zero-poly, which has degree zero and leading coefficient zero.
Finally, (rest-of-poly poly) is the polynomial obtained from a polynomial
of positive degree, poly, when its leading term is removed. If poly is of degree
zero, (rest-of-poly poly) is the-zero-poly.
The constructor procedure is called poly-cons. If n is a nonnegative inte-
ger, a is a real number, and p is the-zero-poly or a polynomial of degree

less than n, then (poly-cons n a p) is the polynomial obtained by adding


the leading term ax^ to the polynomial p. In particular, for any polynomial
poly, the value of

(poly-cons (degree poly)


(leading-coef poly)
(rest-of-poly poly))

is poly. We shall adopt another convention, which says that a polynomial of


positive degree cannot have zero as its leading coefficient. Thus if poly has
degree less than n for some positive n, then (poly-cons n poly) evaluates
to poly.
Using these procedures, we proceed to develop our symbolic algebra of
polynomials. We begin by devising a test to see if a polynomial is the-zero-
poly. All we have to ask is whether both its degree and leading coefficient
are zero. Thus we define zero-poly? in Program 5.6.

Program 5.6 zero-poly?

(define zero-poly?
(lambda (poly)
(and (zero? (degree poly)) (zero? (leading-coef poly)))))

Program 5.7 shows how we build a term having degree deg and coefficient
coef The term so defined is itself a polynomial of degree deg consisting of
.

only one term. A polynomial consisting of one term is also referred to as a


monomial. If we are given a polynomial poly, we get its leading term by
applying the procedure leading-term, which we define in Program 5.8 using
make-term.

144 Locally Defined Procedures


Program 5.7 meOte-term

(define make-term
(leunbda (deg coef)
(poly-cons deg coef the-zero-poly)))

Progrcon 5.8 leading-term

(define leading-term
(launbda (poly)
(maJce-term (degree poly) (leading-coef poly))))

We next define the procedure p+ such that if polyl and poly2 are two
polynomials, then (p+ polyl poly2) is the sum of polyl and poly2. Let us
recall that the sum of two terms bx'' and ex'' of the same degree fc is a term
(6 + c)x* also of degree k. The sum of two polynomials is then the polynomial
obtained by adding the terms of like degree in the two polynomials. Our
algorithm for adding the two polynomials polyl and poly2 is:

• If polyl is the-zero-poly, their sum is poly2; if poly2 is the-zero-poly,


their sum is polyl.
• If the degree of polyl is greater than the degree of poly2, their sum is a
polynomial that has the same leading term as polyl, and the rest of their

sum is the sum of (rest-of-poly polyl) and poly2.

• If the degree of poly2 is greater than the degree of polyl, their sum is a
polynomial that has the same leading term as poly2 and the rest of their

sum is the sum of (rest-of-poly poly2) and polyl.


• If polyl and poly2 have the same degree n, the degree of their sum is n,

and the leading coefficient of their sum is the sum of the leading coefficients
of polyl and poly2, and the rest of their sum is the sum of (rest-ol-poly
polyl) and (rest-of-poly poly2).

This algorithm for the sum, p+, of polyl and poly2 leads to Program 5.9.

In this program, the use of the let expression enabled us to write each of
the cond clauses more concisely and more clearly. For example, had we not
used the let expression, the first cond clause would have looked like

5.3 Symbolic Manipulation of Polynomials 145


Progr£un 5.9 p+

(define p+
(lambda (polyl poly2)
(cond
((zero-poly? polyl) poly2)
((zero-poly? poly2) polyl)
(else (let ((nl (degree polyl))
(n2 (degree poly2))
(al (leading-coef polyl))
(a2 (leading-coef poly2))
(restl (rest-of-poly polyl))
(rest2 (rest-of-poly poly2)))
(cond
((> nl n2) (poly-cons nl al (p+ restl poly2)))
((< nl n2) (poly-cons n2 a2 (p+ polyl rest2)))
(else
(poly-cons nl (+ al a2) (p+ restl rest2) ) ))) ))))

((> (degree polyl)(degree poly2))


(poly-cons (degree polyl)
(leading-coef polyl)
(p+ (rest-of-poly polyl) poly2)))

Such use of let makes programs more readable.


expressions often
We next define the product p* of two polynomials polyl and poly2. The
product of the terms OfcX*^ and UmX^ is the term (ajt x am)x^'^"^ To multiply .

a term Ax"^ times a polynomial 3x^ + 2x^ + 4x + 5, we multiply each of the


terms of the polynomial by 4x^ and add the resulting terms to get

12x^ + 8x^ + 16x^ + 20x2

Now to multiply two polynomials,

4x2 j^2>x^2 and 3x^ + 2x^ + 4x + 5


we first multiply each term of the first by the entire second polynomial to get
the three polynomials

+ Sx^ +
12x^ 16x3 + 20x2
9x^+6x^ + 12x2 + 15x

6x^ + 4x3 _,.8x + 10

146 Locally Defined Procedures


and then we add these three polynomials to get the desired product:

12z^ + 9x^ + 14x^ + 6x^ + 20z^ + 32x2 + 23x + 10

We now example into an algorithm for multiplying any


translate the above
two polynomials polyl and poly2. It will be convenient to define locally the
product t* of a term trm and a polynomial poly. The algorithm for this is:

• If poly is the-zero-poly, then (t* trm poly) is just the-zero-poly.


• Otherwise the degree of their product is the sum of the degrees of trm
and poly. The leading coefficient of their product is the product of the
coefficient of trm and the leading coefficient of poly. The rest of their
product is just the product of trm and the rest of poly.

Once t* has been defined, the product p* of polyl and poly2 can be defined
using the following algorithm:

• If polyl is the-zero-poly, then (p* polyl poly2) is just the-zero-


poly.

• Otherwise, we multiply the leading term of polyl by poly2, and add that
to the product of the rest of polyl and poly2.

This leads us to Program 5.10 for p*. In this program, t* is defined locally
using a letrec expression since it is a recursive definition. The lambda expres-
sion for the procedure p* is placed within the body of the letrec expression for
t* so that the local procedure it defines is available for use in p*-helper. The
letrec expression that defines p*-helper is used because the variable poly2 is

not changed in the recursive invocations of the procedure being defined. Thus
it is programming style not to carry it along as a parameter. We define
better
the local procedure p*-helper that has as its only parameter the polynomial
pi that is bound to polyl when it is later invoked.
Program 5.11 defines a unary operation negative-poly such that when
poly is a polynomial, (negative-poly poly) is its negative: the polynomial
with the signs of all of its coefficients changed. We compute it by multiplying
poly by the polynomial that is a term of degree and leading coefficient —1.
Now we have the negative of a polynomial, we can
that define the difference
p- between polyl and poly2 as shown in Program 5.12.
We now consider how to find the value of a polynomial poly when a number
is substituted for the variable x. To evaluate the polynomial 4x^-|-8z^ — 7aj-|-6
for X = 5, we substitute 5 for x and get 4{5'^) + 8{5^) — 7{b) + 6. The polynomial

can be evaluated at a given value of x by computing each term a^x* separately

5.3 Symbolic Manipulation of Polynomials 147


Progrsun 5.10 p*

(define p*
(letrec
((t* (lambda (trm poly)
(if (zero-poly? poly)
the-zero-poly
(poly-cons
(+ (degree trm) (degree poly))
( (leading-coef trm) (leading-coef poly))
(t* trm (rest-of-poly poly)))))))
(lambda (polyl poly2)
(letrec
((p*-helper (lambda (pi)
(if (zero-poly? pi)
the-zero-poly
(p+ (t* (leading-term pi) poly2)
(p*-helper (rest-of-poly pi)))))))
(p*-helper polyl)))))

Program 5.11 negative-poly

(define negative-poly
(lambda (poly)
(let ((poly-negative-one (make-term -1)))
(p* poly-negative-one poly))))

Program 5.12 p-

(define p-
(leunbda (polyl poly2)
(p+ polyl (negative-poly poly2))))

and then adding the results. For example, we have

4(5^) + 8(5^) - 7(5) + 6 = 4x(5x5x5) + 8x(5x5)-7x(5) + 6

but this is very inefficient. If we evaluate this by computing each x* by


multiplying x by itself k — \ times and then multiplying the result by a^ we
,

lltS Locally Defined Procedures


would be using k multiplications. For a polynomial of degree n, we must add
the number of multiplications for each term, which is l + 2 + 3 + --- +n=
n{n 4- l)/2 multiplications, to which we add the n additions needed to add

up the terms, and we get a grand total of n{n + l)/2 + n operations. We can
reduce this number of operations significantly by using the method of nested
known as Horner's rule, or synthetic division.
multiplication, also
Before we derive the method of nested multiplication, we consider as an
example the polynomial P{x) = Ax^ + 8x^ - 7x + 6. If we write the constant
term first and factor an x out of the rest of the terms, we get P{x) = 6 +
x(— 7 + 8x + 4x^). We next factor an x out of the terms after the —7 in
the parentheses, to get P{x) = 6 + x(-7 + x(8 -|- x(4))). For x = 5, this
becomes 6 + 5(-7 + 5(8 + 5(4))). Whereas evaluating the polynomial P(x) in
its original form required nine operations, in this last form only six operations
are required — three multiplications and three additions.
In the general case of a polynomial of degree n, we note that all terms of
degree 1 or more contain a factor of x, so we can factor it out to get our
polynomial in the following form:

ao + x(ai + a2X + oax^ H (- Onx""-^)

We repeat this process, starting with the term ai, to represent our polynomial
as:

ao + x(ai + x(a2 + asx H h anx'^ ^))

By continuing to factor out an x from the terms after the constant term, we
finally arrive at the result:

Oo + x(oi + x(a2 + x(a3 H (- x{an-l + XOn) . .


.)))

In this method of evaluating the polynomial, we have n multiplications and


n additions, so there are altogether 2n operations. In this way, the number of
operations grows linearly with n (that is, like n to the first power, or using the
notation of Chapter 3, 0{n)) while in the previous way, it grew quadratically
(that is, like n to the second power, or 0{n^)).

We next define a procedure poly-value such that (poly-value poly num)


is the value of the polynomial poly when num is substituted for x. A clue for
defining this procedure recursively (in fact, iteratively) comes from observing
that if we think of the last expression an-i + xon as a coefficient 6, then
the expression is just a polynomial of degree n — 1 having leading coefficient
b and all terms of degree less than n — 1 the same as those in poly. In
implementing this, we obtain an-i by taking the leading coefficient of (rest-
of-poly poly). But this works only if (rest-of-poly poly) has degree

5.3 Symbolic Manipulation of Polynomials 149


Progr2iin 5.13 poly-value

(define poly- value


(lambda (poly num)
(letrec
((pvalue (lambda (p)
(let ((n (degree p)))
(if (zero? n)
(leading-coef p)
(let ((rest (rest-of-poly p)))
(if «(degree rest) (subl n))
(pvalue (poly-cons
(subl n)
(* num (leading-coef p))
rest))
(pvalue (poly-cons
(subl n)
(+ (* nuB (leading-coef p))
(leading-coef rest))
(rest-of-poly rest))))))))))
(pvalue poly))))

n— 1, that when a„_i ^ 0. Thus, if (rest-of-poly poly) has degree less


is,

than n — we use xan for b. Thus the code for poly-value in Program 5.13
1,

treats two Ccises depending upon the degree of rest.


This program is iterative since when pvalue is called in the if clauses, no
further operation is performed after the application of pvalue. Moreover,
because the procedure of one argument pvalue appears first in both the con-
sequent and the alternative of the last if expression, it can be pulled out of
the if expression so that the body of the last let expression reads

(pvalue (if (< (degree rest) (subl n))


(poly-cons
...)
(poly-cons
...)))

The last thing we illustrate before thinking about the representation of the
polynomial is how to build a given polynomial. For example, if we want to
define pi to be the polynomial

5x^ -3x2 -I- X- 17

150 Locally Defined Procedures


Program 5.14 The five basic definitions (Version I)

(define the-zero-poly '(0))

(define degree
(lambda (poly)
(subl (length poly))))

(define leading-coef
(Isimbda (poly)
(car poly)))

(define rest-of-poly
(lambda (poly)
(cond
((zero? (degree poly)) the-zero-poly)
((zero? (leading-coef (cdr poly)))
(rest-of-poly (cdr poly)))
(else (cdr poly)))))

(define poly-cons
(lambda (deg coef poly)
(let ((deg-p (degree poly)))
(cond
((and (zero? deg) (equal? poly the-zero-poly)) (list coef))
((>= deg-p deg)
(error "poly-cons: Degree too high in" poly))
((zero? coef) poly)
(else
(cons coef
(append (list-of -zeros (subl (- deg deg-p)))
poly)))))))

we simply write

(define pi (poly-cons 3 5
(poly-cons 2 -3
(poly-cons 1 1
(poly-cons -17 the-zero-poly)))))

Using the concept of data abstraction again, we have been able to develop
a symbolic algebra of polynomials without knowing how the polynomials are
represented. We did this by assuming that we had the selector and constructor

5.3 Symbolic Manipulation of Polynomials 151


procedures and the zero polynomial. We shall now see several ways in which
these can be defined.
A polynomial is completely determined if we give a list of its coefficients,

where we enter a zero when a term of a given degree is missing. The degree
of the polynomial is then one less than the length of the list of coefficients.

Thus the polynomial

OnX^ +an-lX^~^ -\ haiZ + Oo, On ^


can be represented by the list (on On-i • • •
ai oq). For example, the poly-
nomial 5z^ — 7z^ + 21 is represented by the list (5 —7 21), where the zero
corresponds to the term Ox^ that is suppressed in 5z^ — 7x^ + 21.

If this representation of a polynomial as a list of its coefficients is adopted,


we can make the five basic definitions for the symbolic algebra of polynomials
as shown in Program 5.14. Since we required that the leading coefficient of
a polynomial be different from zero, we put a zero test in the second cond
clause in the definition of rest-of-poly to skip over the missing zeros. In
the definition of poly-cons, the third cond clause guards against a leading
coefficient of zero, and the last line uses the procedure list-of -zeros defined
inProgram 3.5 to fill in missing zeros if deg differs from the degree of p by
more than 1.
The above representation of a polynomial by its list of coefficients has an
obvious disadvantage when we try to represent the polynomial z^°°°+ 1. We
would have to construct a list of 1001 numbers, all zero except the first and
last. The above representation is perfectly adequate when we are dealing
with polynomials of low degree, but it becomes cumbersome when we have
to write "sparse" polynomials of high degree. The following representation
of polynomials is more convenient for such higher-degree polynomials; we
represent the polynomial by a list of pairs of numbers. In each pair, the first

element is the degree of a term, and the second element is the coefficient of
that term. The pairs corresponding to terms with zero coefficients are not
included, except for the-zero-poly, which is represented by ((0 0)). The
pairs are ordered so that the degrees decrease. Thus the polynomial

has the representation ((n On) (n-1 On-i) • • •


(1 ai) (0 oq)), for those terms
with o^ 7^ 0. We then can write the five basic definitions for the algebra of
polynomials as shown in Program 5.15.

There may be other representations of polynomials that are more conve-


nient to use in special circumstances. The advantage of our approach us-
ing data abstraction is that we only need to define the-zero-poly, degree,

152 Locally Defined Procedures


Program 5.15 The five basic definitions (Version II)

(define the-zero-poly '((0 0)))

(define degree
(lambda (poly)
(caar poly)))

(define leading-coef
(lambda (poly)
(cadar poly)))

(define rest-of-poly
(leunbda (poly)
(if (null? (cdr poly))
the-zero-poly
(cdr poly))))

(define poly-cons
(lambda (deg coef poly)
(let ((deg-p (degree poly)))
(cond
((and (zero? deg) (equal? poly the-zero-poly))
(list (list coef)))
((>= deg-p deg)
(error "poly-cons: Degree too high in" poly))
((zero? coef) poly)
(else
(cons (list deg coef) poly))))))

leading-coef, rest-of-poly, and poly-cons, and the rest of our algebra of


polynomials is still valid with no modifications.

Exercises

Exercise 5.9
Implement the algebra of polynomials in the two ways indicated in the text.

For each implementation, test each of the procedures p+, p-, and p* with the
polynomials

5.3 Symbolic Manipulation of Polynomials 153


)

pi(x) = Sx"^ - 7z^ + 2z - 4

P2(z) = x^ + 6x^ - 3z

and using poly-value, find pi(-l), Pi(2), P2(0), and p2(-2).

Exercise 5.10
Look closely at the definition of p+ (see Program 5.9). When nl is greater than

n2, the variables a2 and rest2 are ignored. Similarly, when nl is less than
n2, the variables al and restl are ignored. Rewrite p+ so that this wasting
of effort disappears. Hint: You will need to use let within the consequents
of cond clauses.

Exercise 5.11: poly-quotient, poly-remainder


Define a procedure poly-quotient that finds the quotient polynomial when
polyl is divided by poly2 and a procedure poly-remainder that finds the
remainder polynomial when polyl is divided by poly2.

Exercise 5.12
Another representation of polynomials eis lists that can be used is a list of
coefficients in the order of increasing degree. The list of pairs representation
given above can also be written in order of increasing degree. Consider the ad-
vantages and disadvantages of these representations compared to those given
above.

Exercise 5.13
How would the constructors and selectors be defiined if we use

(cons deg coef ) instead of (list deg coef

in our second representation using lists of pairs?

Exercise 5.14
The definition oft* in Program 5.10 is flawed. Each time t* is invoked recur-
sively it evaluates both (degree trm) and (leading-coef trm), although
these values never change. In addition, the variable trm does not need to be
passed to t* because trm never changes. Explain how these two flaws are
eliminated in the following definition of p*.

154 Locally Defined Procedures


(define p*
(let
((t* (lambda (trm poly)
(let ((deg (degree trm))
(Ic (leading-coef trm)))
(letrec
((t*-helper
(lambda (poly)
(if (zero-poly? poly)
the-zero-poly
(poly-cons
(+ deg (degree poly))
(* Ic (leading-coef poly))
(t*-helper (rest-of-poly poly)))))))
(t*-helper poly))))))
(lambda (polyl poly2) ...)))

Exercise 5.15: append-to-list-of -zeros


In the first version of poly-cons presented in Program 5.14, poly is appended
to a list of zeros. The procedure list-of-zeros requires one recursion to
build the list of zeros and append requires another. Two recursions over the
list of zeros is inefficient. The program can be rewritten so as to require
only one recursion over the list of zeros. One suggestion for doing so is to
combine the construction of the list of zeros and the appending of poly into
one procedure append-to-list-of-zeros, which takes two parameters, n and
X and produces a list that contains x preceded by n zeros. This procedure can
be written either recursively or iteratively. Try your hand at both versions
and test them in poly-cons.

5.4 Binary Numbers

Information is stored in the computer in the form of binary numbers. One


may loosely think of the memory cells in which information is stored as a
row of switches, each having two positions: on and off. If a switch is on, it

represents the digit 1, and if it is off, it represents the digit 0. The information
contained in one such switch is called a bit, and eight bits of information

usually constitute a byte of information. Since there are 2® different settings


for eight switches, we can represent 256 different values by using one byte. In
this section, we shall discuss the representation of numbers in binary form,
and more generally, as numbers with an arbitrary base.

5.4 Binary Numbers 155


First recall that in the decimal system, each digit in a number is a place-
holder representing the number of times a certain power of 10 is counted.
Thus 4,723 is the same as

4 X 10^ + 7 X lOV 2 X 10^ + 3 X 10°

The 10 is called the base of the number system. We can think of any number
represented in this way as a polynomial in which the variable x has been
replaced by 10. In the same way, we can represent any number as a polynomial
in which the variable x has been replaced by the base b and the coefficients are
taken to be numbers between and 6—1. It is customary to write a number
in the base b system using the placeholder concept as a string of digits, each
digit being the corresponding coefficient in the polynomial. For example, for
base 2 {binary numbers), the digits are and 1 and the polynomial

1 X 2^ + 1 X 2^ + X 2^ + X 2^ + 1 X 2^ + X 2^ + 1 X 2°

can be represented as 1100101.


In general, for binary numbers, the number

an2" + an_i2''-^ + • • •
+ ai2 + oq

is written in the form a^an-i . . • aiao. We first consider the problem of finding
the decimal number when we are given its binary representation. This is

precisely the problem of evaluating a polynomial when the variable x has the
value 2. We can use the results of the last section if we represent our binary
number as a polynomial of degree n that has the coefficient ojfe for the term of
degree k, for A; = 0, 1, .., . n. We digits->poly that takes
define a procedure
a list of the digits of a binary number as its argument and returns a polynomial
of degree one less than the number of digits and that has the given digits as its
coefficients. The polynomial is constructed by the local procedure make-poly,
which has as its parameters the degree deg of the polynomial, which is one less

than the number of digits in the binary number, and Is, which is the list of
digits of the binary number. If we already have the polynomial for the binary
number obtained when the first digit is removed, that is, for parameters (subl
deg) and (cdr Is), we get the polynomial for the parameters deg and Is by
adding the term having degree deg and coefficient (car Is). This leads us
to the definition given in Program 5.16.

Now to convert from the binary representation of a number to the deci-


mal number, we use the procedure binary->decimal given in Program 5.17,
which takes a list of the binary digits as its argument and returns the decimal

156 Locally Defined Procedures


Program 5.16 digits->poly

(define digits->poly
(lambda (digit-list)
(if (null? digit-list)
(error "digits->poly: Not defined for the empty list")
(letrec
((make-poly
(lambda (deg Is)
(if (null? Is)
the-zero-poly
(poly-cons deg (car Is)
(make-poly (subl deg) (cdr Is)))))))
(make-poly (subl (length digit-list)) digit-list)))))

Program 5.17 binary->decimal

(define binary->decimal
(lambda (digit-list)
(poly-value (digits->poly digit-list) 2)))

number. As an example, we find the decimal number for the representation

of the binary number 11001101.

(binary->decimal ' (1 1 1 1 1)) => 205

If we have a polynomial, say 1x^ + 1, which corresponds to the binary num-


ber 101, how do we recover the list of binary digits (1 1)? To do this, we
define a procedure poly->digits that takes a polynomial poly corresponding
to a binary number and returns a list of the digits of that binary number. For
example,

(poly->digits (digits->poly '(11010 1))) => (1 1 1 1)

Consider (rest-of-poly poly); if its degree is one less than the degree of
poly, then (poly->digits poly) is just

(cons (leading-coef poly) (poly->digits (rest-of-poly poly)))

5.4 Binary Numbers 157


Program 5.18 poly->digits

(define poly->digits
(lambda (poly)
(letrec
((convert
(lambda (p deg)
(cond
((zero? deg) (list (leading-coef p)))
'

((= (degree p) deg)


(cons (leading-coef p)
(convert (rest-of- poly p) (subl deg))))
(else
(cons (convert p (subl deg))))))))
(convert poly (degree poly)))))

Otherwise, we have to cons zeros onto the list to take into account the gap
in the degrees between the leading term and the next term with nonzero co-
efficient. In order to do this, it is convenient to introduce a local procedure,
which we call convert. It keeps track of the degree of the term being con-
sidered, even if the coefficient is zero. Thus convert has two parameters: p,
which is a polynomial, and deg, which is an integer representing the degree
of the term. We define poly->digits £is shown in Program 5.18.

We also want to convert from the decimal number to its binary represen-
tation. We shall do this with the procedure decimal->binaxy, which takes a
decimal number and returns a list of the digits in its binary representation.
We can easily derive the algorithm if we recall that we want to find the co-
efficients an, On-i, ... ,ao in the polynomial corresponding to the number q,

which we now write using nested multiplication:

q = ao + 2(ai + + 2{an-i + 2an) •)

Observe that ao, which must be either or 1, is just the remainder tq when
q is divided by 2, since q = ao + 2{somenuTnber). Recall that tq is if 9 is

even, and it is 1 if 5 is odd. If we let qo be the quotient when q is divided by


2, then we have

qo = ai + 2(02 H 1- 2(an_i -I- 2an) • • •)

We now repeat this process to find that ai is the remainder ri when qo is

divided by 2, and so forth. In general, if qk is the quotient when ^jt-i is

158 Locally Defined Procedures


Quotient Remainder
197
98 1

49
24 1

12
6
3
1 1

Figure 5.19 Conversion of 197 to its binary representation

divided by 2 and if rk is the remainder when qk-i is divided by 2, then


cik = rk-
For example, to convert the decimal number 197 to binary form, we do
our work in two columns; the first gives the quotient, and the second gives
the remainder when the successive numbers are divided by 2. Figure 5.19
shows this computation. Each line in the table represents the quotient and

the remainder when the previous quotient is divided by 2. The binary repre-
sentation of the number is found by reading the remainders from the bottom
of the table to the top: 11000101. We will then have

(decimal->binary 197) =» (1 1 1 1)

Implementing this algorithm is accomplished in Program 5.20 by building


up the polynomial corresponding to the binary number term by term as the
remainders are obtained. The first term we build has degree and the de-
grees incresise by one each time a new remainder is found. Thus we are able
to define decinial->bin2a:y with the help of dec->bin, which has a second
parameter deg that keeps track of the degree of the term, starting from zero
and increasing by one in each recursive invocation.

We now have

(decinial->binary (binary->decimal '


(1 1 1 0))) =* (1 1 1 0)
(binary->decimal (decimal->binary 143)) =* 143

Two other number systems that are commonly used in computing are the oc-

tal (base 8) and the hexadecimal (base 16) systems. For octal, the base b in the
polynomial representation is replaced by 8, and in hexadecimal, the base 6 is

5.4 Binary Numbers 159


Program 5.20 decimal->binary

(define decimal->binary
(lambda (num)
(letrec
((dec->bin
(lambda (rI deg)
(if (zero? n)
the-zero- poly
(P+ (make -term deg (remainder n 2))
(dec- >bin (quol,ient n 2) (addl deg)))))))
(poly->digit£ (dec ->bin num 0)))))
.

replaced by 16. The digits 0,1,2,3,4,5,6,7 are used for octal numbers and the
digits 0, 1,2, 3, 4, 5, 6, 7, 8, 9, A, B, C, D, E, F for hexadecimal numbers. Here
A stands for 10, fi for 11, . .
.
, F for 15.

Exercises

Exercise 5.16
Convert each of the following decimal numbers to base 2.

53

404

Exercise 5.17
Convert each of the following base 2 numbers to decimals.

a. 10101010
b. 1101011

Exercise 5.18: octal->decimal, hexadecimal->decimal


Look over the programs for binary->decimal and deciraal->binary and see
what changes have to be made to get definitions for the four procedures:
octal->decimal
hexade c imal ->dec imal
decimal->octal
dec imal->hexadec imal

Since we are representing our hexadecimal numbers as lists of digits, we can


use the number 10 for A, 11 for B, and so on, so that (12 5 10) is the list

160 Locally Defined Procedures


representation of the hexadecimal number C5A. Define one pair of conversion
procedures base->decimal and decimal->base that take two arguments, the
number to be converted and the beise, where the bcise can be any positive
integer. Then define a procedure change-base that changes a number num
from base bl to base b2, where nxm is a list of digits. Thus (cheinge-base

n\un bl b2) is a list of digits that gives the base b2 representation of num.
Test your program on:

(change-base '(5 11) 16 8) =^ (1 3 3)


(change-base (6 6 2) 8 2)
(change-base (1
'

1 1 1 1
'
^
1
(1
1)
1

2 16)
1 1

=*
1

(1 7
0)
13)

Exercise 5.19: binary-sum, binary-product


Define two procedures, binaa"y-sum and binary-product, that take two bi-
nary numbers as arguments and return the sum and product of those numbers
in binary form. This can be done in two ways. First, you could convert both
numbers to decimal form, perform the arithmetic operation, and then con-
vert to binary form. You could, on the other hand, treat the binary numbers
as polynomials and perform the arithmetic operations on these polynomials,
using the appropriate carrying rules for binary numbers. Write programs for
binary-sum and binary-product using both approaches.

Exercise 5.20: binary->decimal, decimal->binary


We have presented the conversions from binary to decimal and from deci-
mal to binary as applications of the algebra of polynomials developed in this
chapter. Write the two procedures binary->decimal and decimal->binary
directly from the definitions of binary and decimal numbers, using the list
representation for binary numbers and not making use of the polynomial al-
gebra.

5.4 Binary Numbers 161


6 Interactive Programming

6.1 Overview

In this chapter, we begin by taking a brief look at the string data type. We
then illustrate some of the input and output features available in Scheme by
developing a program to find the square root of numbers. After implementing
the basic square root algorithm, we look at ways of viewing intermediate
results and of providing data at run time. We close this chapter with a look
at two famous problems: the Tower of Hanoi and the Eight Queens problem,
both of which demonstrate ways of outputting data.

6.2 Strings

Strings form an important data type, and there are a number of operations
that can be performed on strings. A brief introduction to strings was presented
in Chapter 2. Recall that a string is written in Scheme sis a sequence of
keyboard characters enclosed within double quotes. We now look at a few of
the procedures in Scheme for manipulating strings. For example, the predicate
string? tests whether its argument is a string; string-length takes a string

as argument and returns the number of characters in the string, including


its

blank spaces; and string-append takes any number of strings as arguments


and forms a new string by appending (or concatenating) them. The procedure
substring has the call structure

(substring string start end)

where string is a given string, and start and end are integers satisfying the
inequalities < start < end < L where L represents the length of string. It

returns a string, which is a substring of string consisting of those characters


beginning with the zero-based index start and including all of the characters

up to but not including the one with index end. Thus the length of the
substring is just the difference between end and start. It is also possible to

convert a symbol, such as 'hello into the string "hello" using the procedure
syinbol->string. Below are some examples illustrating string operations:

(string-length "This is a string") =* 16

(string-length "") =»
(string-append "This is" " a string") =^ "This is a string"
(string-append "12" "34" "56") => "123456"
(substring "This is a string" 4) ==» "This"

(substring "This is a string" 4 6) =^ " i"


(substring "This is a string" 5 13) =» "is a str"
(syiBbol->string 'hello) =» "hello"
(strings? "This is a string" "This is a string") ^^ #t
(strings? "This is a string" "This is a STRING") =» #f
(string-ci=? "This is a string" "This is a STRING") => «t

The predicate string=? tests whether two strings are the same and distin-
guishes between upper- and lowercase. The predicate string-ci=? treats
upper- and lowercase as though they were the same character. (The "ci"
stands for case insensitive .) Thus the next to the last example above is false,
and the last example is true.
"We illustrate the use of these string procedures by defining a procedure
string-insert that inserts a string insrt into a string stmg so that the
first character in insrt has index n in the resulting string. For example,

(string- insert "45" "123678" 3) => "12345678"

The definition appends the substring of stmg consisting of those characters


with indices less than n, the string insrt. and the substring of stmg consist-
ing of those characters with indices n or greater. This gives us the definition
in Program 6.1.

We shall introduce more string-handling procedures as they are needed in


our discussions and see examples of how they are used.

164 Interactive Programming


Program 6.1 string-insert

(define string-insert
(lambda (insrt strng n)
(string-append
(substring strng n)
insrt
(substring strng n (stiring--length strng)))))

Exercises

Exercise 6.1: substring?


Define a predicate substring? with two parameters, sstr and strng, that
tests whether the string sstr is a substring of strng. Hint: This can be done
using string-length, substring, and string=?. Test your predicate on the
following:

(substring? "s a s" "This is a string.") =» #t


(substring? "ringer" "This is a string.") ^=* #f
(substring? "" "This is a string.") =^ #t

Exercise 6.2: string-reverse


Define a procedure string-reverse that takes a string as its argument and
returns a string tnat is the given string with its characters in reverse order.
Hint: You may find the following procedure useful:

(define substring-ref
(lambda (strng n)
(substring strng n (addl n))))

Test your procedure on the following:

(string-reverse "Jack and Jill") => "lliJ dna kcaJ"


(string-reverse "mom n dad") =* "dad n mom"
(string-reverse "") => ""

Exercise 6.3: palindrome?


A string is a palindrome if the reverse of the string is the same as the string.

6.2 Strings 165


For example, "mom" and "dad" are examples of palindromes. Define a pred-
icate palindrome? that tests whether a given string is a palindrome. Test
your predicate on the following:

(palindroae? "able Has I ere I saw elba") =* it


(palindrone? "mom n dad") =^ #f

6.3 Implicit begin

The use of begin enables us to evaluate several expressions sequentially and


return the value of the last expression. This is useful in an if expression that
expects just one expression in each of its clauses. There are certain special
forms in Scheme that have an implicit begin built into their definitions. These
include the special forms with keywords lambda, let, letrec, and cond. For
example, writing

(lambda (x y)
(writeln x)
(writeln y)
(+ X y))

is the same as

(lambda (x y)
(begin
(writeln x)
(sriteln y)
(+ X y)))

Similarly, in the case of let and letrec, the body may consist of several
expressions so that

(let ((a 3) (b 4))


(writeln a)
(writeln b)
(+ a b))

is the same as

166 Interactive Programmtng


(let ((a 3) (b 4))
(begin
(writeln a)
(Hriteln b)
(+ a b)))

In a cond expression, each of the clauses contains a condition and a consequent


that is evaluated if the condition is true. The consequent may consist of several
expressions, which are then evaluated in sequential order a^s if they were in a
begin expression.

Exercise

Exercise 6.4
An example of the use of implicit begins in cond clauses is given below:

(define mystery
(leunbda (pos-int)
(letrec ((helper
(lambda (n count)
(cond
((= n 1)
(newline)
(writeln "It took " count " steps to get to 1."))
((even? n)
(writeln count
". We divide " n " by 2.")
(helper (/ n 2) (addl count)))
(else
(writeln coiint
". We multiply " n " by 3 and add 1.")
(helper (+ (* n 3) 1) (addl count)))))))
(helper pos-int 0))))

In this example, each cond clause uses an implicit begin. What is the output
of (mystery 9)? Invoke mystery with a few other positive integer argu-
ments. Safe recursive programs contain a terminating condition which even-
tually halts the computation. No one has, eis yet, been able to demonstrate
that mystery is safe. Nor has a positive integer argument been found for

which mystery is unsafe.

6.4 Input and Output 167


6.4 Input and Output

We have been using the keyboard to enter Scheme expressions, and we have
seen Scheme send output to the screen. The Scheme expressions we enter
its

are evaluated after we press the <RETURH> key, and then the result is printed
out. There are programs in which we would like to enter additional data while
the evaluation is taking place or in which we would like to print out not only
the final result but some intermediate results of the computation.
As an example illustrating the desire to see intermediate results, we look at
a program to compute the square root of a number by the method of successive
averaging known as Newton's method. If we have an estimate u for the square
root of a positive number a, a better estimate is always given by the average
of u and ^, that is, by v where^

-^("+^) (1)

Suppose we start with the estimate u = 1 and use formula (1) to compute v.

We then substitute this value v for u in (1) to get the next value v. We continue
this process until we are satisfied with the value we get: but what criterion
should we apply to decide that we are satisfied? We decide how many decimal
places we want in the answer, say five, and stop when we get two successive
estimates that are the same to five decimal places. In particular, we shall stop
the calculation when u and v differ by less than the agreed-upon tolerance,
0.000005. To test whether u and r are closer than the given tolerance, we
shall use the predicate close-enough? defined by writing

(define tolerance 0.000005)

(define close-enough?
(laabda (u v)
(< (abs (- u v)) tolerance)))

We can describe the algorithm as follows:

1. Make an initial estimate w = 1 for the square root of a.

^ To make this plausible, recall that we axe looking for the number s having the property
that 5 X s = a. If the estimate u is too large, then — is too small, and their average v is a
better approximation to s. Similarly when the estimate u is too small, — is too leirge, emd
their average i; is a better approximation to s.

168 Interactive Programmtng


Program 6.2 square-root

(define square-root
(lambda (a)
(letrec
( (next-estimate
(leunbda (u)
(let ((v (/ (+ u (/ a u)) 2)))
(if (close-enough? u v)
V
(next-estimate v))))))
(next -estimate 1))))

2. If u is an estimate for the square root of a, then the next estimate is given
by the v calculated in (1).

3. We continue applying Step 2 with the previously calculated value of u used


as the new value of u to get the next value of v until u and v differ by less
than tolerance.

Program 6.2 is a procedure that implements this algorithm.


We use this program to compute a few square roots:

[1] (square-root 100)


10.0
[2] (sqrt 100)
10.0
[3] (square-root 1000)
31.6227766016838
[4] (sqrt 1000)
31.6227766016838
[5] (square-root 2)
1.41421356237469
[6] (sqrt 2)
1.4142135623731

In [2], [4], and [6], we called the built-in square root procedure sqrt to

compare its (presumably correct) results with our approximation. We see that
we got more than just five-decimal-place accuracy. This averaging method is
known to halve the error until the error is less than 1 in absolute value and
then to double the number of decimal-place accuracy with each successive av-
eraging. In order to see that this is actually happening, it would be interesting

6.4 Input and Output 169


to be able to see each of the successive averages. We would like to send the
value V to the screen each time it is computed.
Scheme provides several procedures that enable us to send output to the
screen or, as we shall see in Chapter 15, to a file. We already have used the
procedure writein to write to the screen when we traced procedures. The one
we now use is display, and later in this section we shall discuss others. The
Scheme procedure display is called with only one argument, and it has the
side effect of printing that argument on the screen. The value that it returns
in not specified in Scheme, and we shall assume that our implementation does

not print a value for display. For example:

[7] (begin
(display "Is")
(display " ")

(display 1.4142)
(display " the square root of 2?"))
Is 1.4142 the square root of 2?

To put a space between the items being printed with display, we have to print
a space using (display " "). Strings are printed without the double quotes.
If we want to print the string "the square root of 2?" on the next line,
we use the Scheme procedure newline, which takes no arguments and has the
effect of moving the cursor to the beginning of the next line. Scheme does not

specify a value for newline, and we again assume that our implementation
does not print a value for newline. We now use newline to print the string
"the square root of 2?" on the next line instead of on the same line as

the other items.

[8] (begin
(display "Is")
(display " ")

(display 1.4142)
(newline)
(display "the square root of 2?")
(newline))
Is 1.4142
the square root of 2?

170 Interactive Programming


Program 6.3 square-root-display

(define square-root-display
(lambda (a)
(letrec ((next-estimate (lambda (u)
(let ((v (/ (+ u (/ a u)) 2)))
(if (close-enough? u v)
V
(begin
(display v)
(newline)
(next-estimate v)))))))
(next-estimate 1))))

We now define the square root procedure using display to print the inter-
mediate results on the screen (Program 6.3).^ When we call square-root-
display, we get the output shown in Figure 6.4. We can now observe that
the convergence of the square-root algorithm does display the convergence
behavior described.
we have only asked for five-decimal-place accuracy in the answer, it
Since
would be more appropriate to print the final answer to five places. This can be
done using the Scheme procedure round, which rounds a number to the closest
integer. If we have a number like the one obtained for (square-root 2), we
round it to five places by first multiplying it by l.Oe+5 (i.e., 100000.0), to get
141421.356237469. This result is then rounded to the nearest integer using
round to get 141421, and to get the final answer, we divide by l.Oe+5, yielding
the result 1.41421, which is correct to five decimal places. In Program 6.5, we
round-n-places that has as parameters an integer
define a general procedure
n and a decimal number dec-num and returns the number rounded off" to n
decimal places.
Then to get the answer to (square-root 2) rounded off" to five decimal
places, we write:

(round-n-places 5 (square-root 2)) => 1.41421

If we are rounding many numbers off to five decimal places, it is convenient


to define a procedure round-5-places as

^ Although the invocation of the local procedure next -estimate is within the begin ex-
is still an iterative program, since the value of the recursive invocation of
pression, this
next-estimate is returned directly as the value of next-estimate.

6.4 Input and Output 171


[9] (square-root-display 100)
50.5
26.240099009901
15.0255301199868
10.8404346730269
10.0325785109606
10.0000528956427
10.0000000001399
10.
[10] (square-root-display 1000)
500.5
251.249000999001
127.614558163459
67.725327360826
41.2454260749912
32.7452493444886
31.6420158686508
31.622782450701
31.6227766016843
31.6227766016838
[11] (square-root-display 2)
1.5
1.41666666666667
1.41421568627451
1.41421356237469

Figure 6.4 Display of intermediate results

Progreun 6.5 round-n-places

(define round-n-places
(lambda (n dec-num)
(let ((scale-factor (expt 10 n)))
(/ (round (* dec-num scale-factor)) scale-factor))))

(define round-5-places
(lambda (dec-num)
(round-n-places 5 dec-num)))

172 Interactive Programming


and then simply write

(round-S-places (square-root 2)) =^ 1.41421

In squ2are-root-display, each successive value of v was printed out on a


new line. Suppose we want to print out all of the successive values on one line.
Then we would not follow each application of display with an application of
nevline. We would only use newline before returning the final answer to set
it off from the intermediate values.

(if (close-enough? u v)
(begin
(newline)
y)
(begin
(display v)
(display " ")

(next-estimate v)))

Here, (display v) prints the value of v to the screen; then (display " ")

prints a blank space after the value of v. Thus the


numbers will
successive
be separated by blank spaces. The final answer v will be on a new line. For
example, with the procedure squetre-root-display redefined this way, we
get:

[12] (squcire-root-display 2)
1.5 1.41666666666667 1.41421568627451
1.41421356237469

The procedure display prints the string "the squcure root of 2?" with-
out double quotes. For the occasions when we do want the double quotes to
be printed in addition to the string, we can use the Scheme procedure write
instead of display. If we use write instead of display in [8], we get

[13] (begin
(write "Is")
(write " ")

(write 1.4142)
(newline)
(write "the squ<u:e root of 2?")
(newline))
"Is"" "1.4142
"the square root of 2?"

6.4 Input and Output 173


Program 6.6 read-demo

(define read- demo


( lambda ()
(display "Enter data (enter done when finished) :
")

(let ((response (read)))


(cond
((eq? response 'done) (display "Thank you. Good--bye. "))
(else (display "You entered: ")
(write response)
(newline)
(read-demo))))))

We also see that (write " ") prints a blank space with double quotes around
it, whereas (display " ") prints just the blank space.

It is also possible to enter data interactively while a procedure is running


by making use of the Scheme procedure read. When the procedure read is

invoked with no arguments, the computer stops and waits for an expression
to be entered from the keyboard. The value entered from the keyboard is
returned by (read). In Chapter 15, we shall see how read may be called
with one argument to read from a file instead of from the keyboard. In
Program 6.6, we illustrate the use of read by writing a simple program that
asks us to enter data, reads the data we enter, and then tells us what data
we entered. A statement written to the screen asking us to do something is

called a prompt. A statement that shows what we entered in response to the


prompt is said to echo what we entered.
The first thing to notice about read-demo is that it is written as a procedure
of no arguments; the parameter list in the lambda expression is the empty
list (). A procedure of no arguments is called a thunk, and it is invoked by
merely enclosing the name of the procedure in parentheses. For example, if

we write

(define greeting
(lambda ()

(writeln "Hello. How are you?")))

then the procedure greeting is called as follows:

[14] (greeting)
Hello. How are you?

174 Interactive Programming


" 5

In the definition of read-demo, the first display expression prompts us for


data. The let expression binds the variable response to the object that is

produced by the read expression. During the evaluation, the computer pauses
and waits for us to enter a datum from the keyboard, and it is that datum that
is bound to response. We chose to use write instead of display to print the

response in order to show it exactly as it was entered. Thus when a string is

entered with double quotes, it is printed on the screen with the double quotes.
Had we used display instead of write, a string would be printed without the
double quotes. In order to stop the recursion, we enter done. The condition
terminating the recursion tests response to see if it is the same as done using
the predicate eq?. Here is a sample run using read-demo when a number,
a symbol, and a string are entered in response to the prompt asking for a
datum. The responses are presented in italics to distinguish them from the
prompts.

[16] (read-demo)
Enter data (enter done vhen finished) : 6.5432
You entered: 6.5432
Enter data (enter done when finished) : Hello
You entered: Hello
Enter data (enter done when finished) : "How are you?"
You entered: "How are you?"
Enter data (enter done when finished) : done
Thank you. Good-bye.

We now define interactive-square-root in such a way that it prompts


us for the numbers for which square roots are desired.

Program 6.7 interactive-square-root

(define interactive-square-root
(lambda
,
(writ Bin "Enter the number whose square root you want
" or enter done to quit: ")
(let ((n (read)))
(if (eq? n 'done)
(writeln "That's all, folks .")
(begin
" " "
(writeln "The square root of n is (squeire--root n))

(newline)
(interactive-square--root))))))

6.4 Input and Output If


The first writeln expression provides a prompt for us to enter a number.
Then the let expression binds the value given by (read) to n. When the
expression (read) is evaluated, the computer pauses and waits for the user to
enter a datum at the keyboard. In this Ccise the datum is a number, which is
bound to n. When n is not the symbol done, the writeln expression evaluates
its operands, which includes the expression (square-root n) and echoes n
while printing its good practice to echo back the number
square root. It is

n along with its square root to be sure that you did not make the mistake
of entering a wrong number when you responded to the prompt. Next the
procedure interactive-square-root is called, and the process is repeated
until thesymbol done is entered as a terminating condition. Below is an
example of an interactive session after calling interactive-square-root:

[1] (interactive-square-root)
Enter the number whose square root you want, or enter done to quit:
100
The square root of 100 is 10

Enter the number whose square root you want, or enter done to quit:
1000
The square root of 1000 is 31.6227766016838

Enter the number whose square root you want, or enter done to quit:
done
That's all, folks.

The interactive-square-root example illustrates the use of prompts to


ask the user to enter something at the terminal, and it illustrates the use of a
read expression to bind a value to a variable interactively. It also shows the
echoing of input data in the result to verify that the correct data were entered.
Study this example to understand these concepts fully. In this section, we have
seen that output from our programs can be sent to the screen using the four
procedures display, write, newline, and writeln. Input from the keyboard
can be entered during the evaluation of an expression using the procedure
read.

Exercises

Exercise 6.5
Write an interactive program that prompts for a number and then prints
the square and the square root of that number. It continues prompting for

1 76 Interactive Programming
:

numbers until stop is entered. The display should include the appropriate text
to identify the input and output.

Exercise 6.6: Making change


Write a program that prompts for an amount of money; for example

For what amoiint do you want chemge? $

and the user enters a number like 23.45. The program then tells how this
amount is made up of 100 dollar, 20 dollar, 10 dollar, 5 dollar and 1 dollar
bills and of quarters, dimes, nickels, and pennies. The output should say
something like:

Your change is
1 twenty-dollar bill

3 one-dollar bills
1 queurter
2 dimes

The program should then ask if you want change for another amount and
terminate if the answer is "no."

Exercise 6. 7; Weekday of a given date


A Reverend Zeller developed a formula to compute the day of the week for any
given day of the Gregorian calendar. The input to the algorithm is specified
in the following manner:

• m is the month of the year, with March as m= 1. January and February


are months 11 and 12 of the previous year.

• d is the day of the month.

• y is the year of the century.

• c is the previous century.

For example, for July 4, 1989, m = 5, d = 4, y = 89, and c = 19, while for

January 25, 1989, m = 11, d = 25, y = 88, and c = 19. The algorithm to
compute the day of the week is:

1. Take the integer part of (13m — l)/5. Call this a.

2. Take the integer part of y/4. Call this b.

3. Take the integer part of c/4. Call this e.

4. Compute f = a + b-\-e + d-iry-2c.

6.4 Input and Output 177


5. Set r equal to f modulo 7.

6. T tells us the day of the week, with Sunday corresponding to r = 0, Monday


to r = 1, etc.

Write a program that prompts for the month, the day, and the year. The
month should be entered in the usual way with January as 1 and December
as 12. The year should also be entered in the usual way (e.g., 1989). The
program should then convert these data to what is needed by the algorithm
and compute the day. The output should be a statement such as "1/13/1989
is a Friday." The program should ask whether another day is desired and

terminate if the user responds with "no."

6.5 Two Famous Problems

These two problems, known by the names Tower of Hanoi and Eight Queens,
are included in this chapter to illustrate how information is displayed while the
program is running. The Tower of Hanoi^ problem was apparently originated
by the French mathematician Edouard Luccis in the nineteenth century. The
following story is told in connection with the problem:

In the greatTemple of Brahma in Benares, on a brass plate beneath


the dome that marks the Center of the World, there are 64 disks of
pure gold which the priests carry one at a time between three diamond
needles according to Brahma's immutable law: No disk may be placed
on a smaller disk. In the Beginning of the World, all 64 disks formed the
Tower of Brahma on one needle. Now, however, the process of transfer
of the tower from one needle to another is in midcourse. When the last
disk is finally in place, once again forming the Tower of Brahma but on a
different needle, then will come the End of the World, and All will turn
to dust.

We shall formulate the problem so as to have three posts, labeled L (for

left), C (for center), and R (for right), and n disks of decreasing diameter (in
going from bottom to top) all on the left post. Our goal is to move the n
disks to the right post, so that at the end they will again be stacked in order
of decreasing diameter in going from bottom to top. The two rules are that
we can move only one disk at a time and we must never put a larger disk on

' For a fuller account of the Tower of Hanoi puzzle, see Hofstadter, 1985.

178 Interactive Programming


Figure 6.8 The Tower of Hanoi

top of a smaller one. Figure 6.8 illustrates the initial configuration for three
disks.

This problem lends itself to a beautiful recursive solution. The idea is that
if we have solved it for moving n — 1 disks from a source post to a destination
post (making use of the third post £is a help post), we can immediately solve
it for moving n disks from a source post (say L) to a destination post (say R)
by making the following moves:

1. With n disks on the source post L, we use the fact that we know the solution
for n— 1 disks to move the top n— 1 disks to the post C. (In these moves,
the destination post R serves as the help post.)

2. Now the largest disk is the only one on the source post L and the destination
post R is empty. We move the largest disk from the source post L to the
destination post R.

3. Making use of the now-empty post L as a help post, we now move the n — 1

disks from post C to the destination post R.

We have now solved the problem for n disks under the assumption that
we knew the solution for n — 1 disks. If we have only one disk (n = 1), the
solution is easy: one merely carries that one disk from the source post to the
destination post. This case serves as the base case for our recursion. To be
able to write the solution to this problem, let us represent a single move that
carries a disk from a post called source to a post called destination by a
pair (source destination). For example, the move that carries a disk from
L to C is denoted by the pair (L C). A list of these pairs gives a sequence of
moves; for example, ((L C) (L R) (C R)) says that we move the top
first

diskfrom post L to post C, then the top disk on post L is moved to post R,
and finally the top disk on post C is moved to post R.
The procedure to solve the Tower of Hanoi problem for n disks is called

6.5 Two Famous Problems 179


tOHer-of-hanoi and its definition begins with

(define toser-of-hanoi
(lambda (n)
... ))

We now move that moves n disks from


define a local recursive procedure
the post denoted by source to the post denoted by destination making use
of the post called helper. Thus move takes the four arguments n, source,
destination, and helper and produces a list of pairs that is the solution of
the problem of moving n disks from the post source to the post destination.
Thus we continue the definition with

(define tower-of-hanoi
(lambda (n)
(letrec
((move
(lambda (n source destination helper)
... ))))))

The terminating condition for the recursion on n is the case in which n


is 1. Then we merely move the disk from the source to destination, and

the solution is a list whose only member is the pair consisting of source and
destination. Thus we have

(define tower-of-hanoi
(lambda (n)
(letrec
( (move
(lambda (n source destination helper)
(if (= n 1)
(list (list source destination))
... )))))))

Now for any n > we make use of the three steps given above. Step
1, 1 tells

us to move n — 1 disks from source to helper, so we first invoke

(move (subl n) source helper destination)

which produces a list of pairs giving the moves that carry the first n— 1 disks
from the source post to the helper post making use of the destination post.
We append to that list the list containing the single move from the source

180 Interactive Programming


Program 6.9 tower-of-hanoi

(define tower-of-hanoi
(lambda (n)
(letrec
( (move
(leunbda (n source destination helper)
(if (= n 1)
(list (list source destination))
(append
(move (subl n) source helper destination)
(cons
(list source destination)
(move (subl n) helper destination source)))))))
(move n 'L 'R 'C))))

post to the destination post. The resulting list is then consed onto the list of
pairs produced by

(move (subl n) helper destination source)

which moves the n — 1 disks from the helper post to the destination post
making use of the source post. These three steps enable us to complete the
definition of the local procedure move:

(define tower-of-hanoi
(leu&bda (n)
(letrec
( (move
(lambda (n source destination helper)
(if (= n 1)
(list (list source destination))
(append
(move (subl n) source helper destination)
(cons
(list source destination)
(move (subl n) helper destination source)))))))
... )))

Now that the local procedure move is defined, we


n disks located
call it for

on the source L and with destination R with the help of the post C. Thus the
complete solution is in Program 6.9.

6.5 Two Famous Problems 181


Program 6.10 display-tower-of-hanoi

(define display- tower-of-hanoi


(let ((show-move (lambda (s d)
(display s)
(display " —> '
')

(display d))))
(lambda (n)
(letrec
( (move
(lambda (n source destination helper)
(if (= n 1)
(begin
(shoH-move source destination)
(newline))
(begin
(move (subl n) source helper destination)
(show-move source destination)
(display ", ")

(move (subl n) helper destination source))))))


(move n 'L 'R 'C)))))

Now to solve the Tower of Hanoi problem for three disks moving them from
the post L to the post R with the help of the post C, we enter

[1] (tower-of-hcinoi 3)
((L R) (L C) (R C) (L R) (C L) (C R) (L R))

This shows the seven moves that solve the problem for n = 3.

A minor modification of this program will enable us to see the individual


moves as they are being generated by the local procedure move. As written
now, the local procedure move builds a list of the individual pairs (list
source destination) and returns that as the answer. Now we ask it to
send those pairs, without parentheses, to the screen instead of building a list

of them. The code to do this is shown in Program 6.10. With this new
definition, we get the following output:

[3] (display-tower-of-heinoi 3)
L ~> R
L —> C, R —> C
L ~> R, C ~> L
C ~> R, L ~> R

182 Interactive Programming


It is good practice to walk through the program and explain how the output
is obtained.
The second problem we discuss in this section is that of the Eight Queens.
The challenge in this problem is to place eight chess queens on a chess board in
such a way that no queen is attacking any other queen.* How many different
solutions are there to this problem? One such is shown in Figure 6.11.

12 3 4 5 6 7 8

Figure 6.11 An Eight Queens Solution

Let us number the columns from 1 to 8 going from left and the
to right
rows from 1 to 8 going from the bottom to the top. The data structure we
use to denote the positions of the 8 queens on the board is a list of integers
of length 8 in which the A;th integer in the list denotes the row of the queen
in the fcth column. This data structure is permissible since the nature of the
problem rules out the possibility of two queens being on the same column.
The queens illustrated in Figure 6.11 are represented by the position list

(57263148)
In general the last element in the position list denotes the row of the queen in

the eighth column, so we denote the positions of the three rightmost queens
on the board in Figure 6.11 by (1 4 8). We call a position list legal if none

* For those not familiar with the rules of chess, a queen attacks another piece if the queen
and the other piece are on the same horizontal, vertical, or 45-degree diagonal line.

6.5 Two Famous Problems 183


Program 6.12 legal?

(define legal?
(lambda (try legal-pl)
(letrec
((good?
(lambda (nes-pl up down)
(cond
((null? new-pl) #t)
(else (let ((next-pos (ceir new-pl)))
(and
(not (= next-pos try))
(not (= next-pos up))
(not (= next-pos down))
(good? (cdr new-pl)
(addl up)
(subl down)))))))))
(good? legal-pl (addl try) (subl try)))))

(define solution?
(lambda (legal-pl)
(= (length legal-pl) 8)))

(define fresh-try 8)

of the queens in the list attacks any other queen in the list. The position list

(1 4 8) is legal, but the position lists (8 4 8) and (6 4 8) are not legal,


the first because two queens are on the same row and the second because two
queens are on the same diagonal. Naturally, a list containing just one element
is legal. Program 6.12 defines a predicate that tests whether adding a new
queen to a legal position list is legal.

When do we know that we have a solution? When we have a legal position


list that is of length 8. For this problem, we will define a predicate solution?
that returns true if its argument, a legal position list, is of length 8. We need
one other piece of information before we can start. We need a constant Iresh-
try, which has the value 8.

The Eight Queens problem is interesting because it represents a simple


problem in backtracking. When you make guesses to find a solution, you
often make a wrong guess and follow that wrong guess with other guesses
until it becomes clear that the set of guesses you have made will lead to a
failure. The undoing of such guesses is referred to as backtracking.

184 Interactive Programming


Program 6.13 build-solution

(define build-solution
(lambda (legal-pl)
(cond
((solution? legal-pl) legal-pl)
(else (forward fresh-try legal-pl)))))

Imagine that we have the position list (64 17538) and are about to
place the last queen on the chess board. The queen we placed most recently
was the 6. Now we discover that we cannot place the last queen, so it must
be that 6 was a bad choice. Because we try the positions in a column in
decreasing order, the next possibility is 5. We try this list with 5, 4, 3, 2,

and 1, and they all fail. When we decrease it one more time we get 0, so we
backtrack. This time we use 3 and (17 5 3 8). Neither 3, 2, nor 1 can
be used, so once again we backtrack. Once again we get 0. The indicates
that there are no queens that can be added, so we backtrack again. This
time we use 6 and (5 3 8). We discover that although we cannot use 6, 5,

4, or 3, we can use 2. Once the 2 has been placed in the position list, we
try to place another queen, starting in position 8. This means we look for
a five-position list from the possibilities: (8 2 5 3 8), (7 2 5 3 8), (6 2
6 3 8), (5 2 5 3 8), (4 2 5 3 8), (3 2 5 3 8), (2 2 5 3 8), and (1 2
5 3 8). If one of these works, then we will be looking for a six-position list

among eight possibilities. If none of these works, then we backtrack and try
to find a four-position list from the single possibility 1 and (5 3 8). This
process, which consists of moving forward toward a solution as far as possible
and then backing up when we have hit a dead end, terminates with a solution
if there is one and terminates with the empty list if there are no solutions.

To solve the Eight Queens problem, we use the three procedures: build-
solution, forward, and backtrack. When build-solution (defined in Pro-

gram 6.13) is called, we know that its argument is a legal position list, so that
termination follows if it is the correct length. If it is not the correct length,
we callforward with an attempt that may or may not be legal. This attempt
will use fresh-try (i.e., 8) and a legal position list.

The procedure forward (see Program 6.14) is always called with a try and
a position list. If the try is 0, then we know that we have tried all positions
in this column, and none of them works, so we must backtrack. If the try is

not a 0, then adding try might make a legal position list. If so, we invoke
build-solution with the new legal position list. If not, we try again.

6.5 Two Famous Problems 185


)

Program 6.14 forward

(define forBard
(lambda (try legal-pl)
(cond
((zero? try) (backtrack legal-pl)
((legal? try legal-pl) (build-solution (cons try legal-pl)))
(else (forward (subl try) legal-pl)))))

Program 6.15 backtrack

(define backtrack
(lambda (legal-pl)
(cond
((null? legal-pl) '())
(else (forward (subl (ceir legal-pl)) (cdr legal-pl))))))

We next discuss backtrack (see Program 6.15). At the time backtrack is

invoked, we know that its argument is either the empty list, or it is a legal
position that has shown no promise. If it is the empty list, that means we
list

have backtracked as far as is possible and could not find a solution. This is
the result when you solve the Three Queens problem on a 3 x 3 board. When
this happens, we have no solution. It is more likely, however, that the legal
position list has shown no promise. Hence, we sacrifice that position list and
try the next one. This is accomplished by subtracting one from the car of the
current position list.

With these three procedures, we can now produce a solution to the Eight
Queens problem.

[1] (build-solution '())


(57263148)

Generalizing this program to get more solutions is not we notice


difficult if

that how we look at a solution is a matter of judgment. When we get a


solution, we can add it to a list of solutions, but also we may imagine that
that solution has shown no promise and backtrack over it. This way, we will
be forced to search for another solution, since the one we have has shown,
in a manner of speaking, no promise. This technique is called failure- driven

backtracking. For example, if we want three solutions, we can write

186 Interactive Programming


)

[2] (let ((soil (build-solution '())))


(let ((sol2 (backtrack soil)))
(let ((sol3 (backtrack 8ol2)))
(list soil 8ol2 sol3))))
((57263148) (47526138) (64713528))

From here, it is an easy step to get all solutions. Each time we get a solution,
we save it in a list and backtrack over it to get another solution, until there
are no solutions left. In the experiment below, we are interested just in the
number of solutions.

[3] (define build-all-solutions


(lambda ()

(letrec
((loop (Isuabda (sol)
(cond
((null? sol) '())
(else (cons sol (loop (backtrack sol))))))))
(loop (build-solution '())))))
[4] (length (build-all-solutions))
92

The procedures build-solution and forweurd rely on three global vari-

ables: legal?, solution?, and Iresh-try. Program 6.16 is a procedure that


frees us from concern about these three variables. By scoping these variables,
we see how to make the not-so-general procedure for solving the Eight Queens
problem work for a larger class of problems.

Just getting answers is not satisfying. Backtracking should be witnessed.


As we have done earlier in this chapter, we are going to display selected
information so that you can get a better idea of how these procedures work.
The procedure lorwaird is uninteresting because it is just monitoring the
counting-down procedure; thus we shall not trace f orvsLrd. However, we will

display the position list on entrance to backtrack and build-solution. In


the trace we reverse the lists to make the trace more readable. By placing

(writeln "Backtrack : " (reverse legal -pi)

and

(writeln "Build-Solution : " (reverse legal -pi)

6.5 Two Famous Problems 187


)

Program 6.16 searcher

(define seeircher
(lambda (legal? solution? fresh-try)
(letrec
((build-solution
(lambda (legal-pl)
(cond
((solution? legal-pl) legal-pl)
(else (forscird fresh-try legal-pl)))))
(forseird
(lambda (try legal-pl)
(cond
((zero? try) (backtrack legal-pl))
((legal? try legal-pl)
(build-solution (cons try legal-pl)))
(else (forwaird (subl try) legal-pl)))))
(backtrack
(lambda (legal-pl)
(cond
((null? legal-pl) '())
(else
(forward (subl (ceir legal-pl)) (cdr legal-pl))))))
(build- all-solut ions
(leUttbda ()
(letrec
((loop (lambda (sol)
(cond
((null? sol) '())
(else (cons sol (loop (backtrack sol))))))))
(loop (build-solution '()))))))
(build-all-solutions) ) )

as the first expression in backtrack and build-solution, respectively, we


get a trace. If we only want to trace until the first solution is found, as shown
below, then we replace the body of the letrec by (build-solution '()). As
you study the trace below, remember that the position list has been reversed,
and hence the last item in each list is the one most recently entered.

188 Interactive Programming


[5] (searcher legal? (lambda (x) (= (length x) 7)) 7)
Build-Solution
Build-Solution (7)
Build-Solution (7 5)
Build-Solution (7 3)
Build-Solution (7 3 6)
Build-Solution (7 3 6 4)
Backtrack (7 3 6 4)
Backtrack (7 3 6)
Build-Solution (7 3 1)
Build-Solution (7 3 1 6)
Build-Solution (7 3 1 6 4)
Build-Solution (7 3 16 4 2)
(2461357)

Exercises

Exercise 6.8
An interesting question we can ask is how many moves M„ are needed to
move a tower of n disks from the source post to the destination post. We can
get a simple equation satisfied by M„ if we recall that we first used M„_i
moves to move the top n — \ disks to the helper post, then we used one move
to carry the largest one from the source disk to the destination disk, and
finally we used Mn-\ moves to take the n — \ disks from the helper post to
the destination post. This leads to the difference equation Mn = 1 + 2M„_i.
We know that if
also we have only one disk on the source post, it takes
only one move to take it to the destination post. Thus M\ = 1. Then
M2 = 1 + 2Mi = 1 + 2, and M3 = 1 + 2M2 = 1 + 2 + 22. gj^^^ ^y induction
that Mn = 1 + 2 + 2^ + 2^ H h 2""^ Sum this geometric series for M„ by

multiplying it termwise by 2 and then computing 2M„ — Mn to get the final


result Mn = 2" — 1. Estimate the number of digits in the number Me4 to
determine how many disks the priests of the Temple of Brahma must move
before All turns to dust.

Exercise 6.9
Write a program that solves the Tower of Hanoi problem for n disks and k
posts. All of the disks are initially on the first post. They should be moved
to the kih post with a minimum number of moves, placing no disk on top of
a smaller disk in the process.

Exercise 6.10: queens


The Eight Queens problem can be restated to apply to n queens placed on an

6.5 Two Famous Problems 189


nxn board so that none attacks any of the others. Write a procedure queens
that takes n as an argument and solves the problem for an arbitrary n. Test
your solution for n = 3, 4, 5, and 6.

Exercise 6.11: The Good Sequences


The Good Sequences problem may be stated as follows: a finite list of I's,

2's, and 3's is called a good sequence if it does not contain two identical
subsequences that are adjacent. Thus (123231) is not good because (2 3)
appears twice cis adjacent subsequences. On the other hand, (1 2 3 2 1 3) is
a good sequence. Methods similar to those used in solving the Eight Queens
problem can be used to show that for any n, one can find a good sequence of
length n. Generate all good sequences of length n.

Exercise 6.12
Change the definition of build-all-solutions so that instead of building a
list of all the answers, it displays the answers, one per line. Redefine your
solution so that it displays five solutions per line. Redefine the previous
solution to display n per line.

Exercise 6.13
The backtrack trace represents the search for the first solution of the Seven
Queens problem. Show what would be printed if we traced the second solution.
You may solve this by hand or by modifying the program.

Exercise 6.14
A standard technique for improving the efficiency of programs is to remove
invocations of length. In the procedures used to solve the Eight Queens
problem, we did not integrate the solution? test into the program because
we wanted to give you a relatively general program for doing backtracking.
Given that all you are concerned about solving is the Eight Queens problem,
rewrite the set of procedures so that there are no length invocations.

Exercise 6.15
Sets of procedures can sometimes be combined. For example, we do not
need both the backtrack and f orwaord procedures. we combine these two If

procedures, we will be left with only two procedures: build-solution and


the combined procedure. Test build-solution and the combined procedure.
Furthermore, we can take the resultant procedure and combine it with build-
solution. This would leave us with just one procedure, build-solution.
Test this new build-solution.

190 Interactive Programming


Exercise 6.16: blanks
In the trace of backtrack and build-solution, we use strings with sequences
As the number of
of blank characters. characters in such strings increeises,
they become more and more difficult to read. A solution is to write a proce-
dure bleinks that generates a string of n blank characters. Run the trace by
redefining backtrack and build-solution to use blauiks as defined below.

(define blemks
(IcUDbda (n)
(cond
((zero? n) "")
(else (string- append " " (blanks (subl n) ))))))

6.5 Two Famous Problems 191


Part 2

Procedures as Values

Procedures that take numbers as arguments and have numbers as values are
called simple arithmetic operations. Examples are addition and multiplica-
tion. Procedures that take procedures as arguments and have procedures
as values are called higher-order operations. One use we will make of these
higher-order procedures will be to show how we define a procedure that com-
bines similar characteristics of several different procedures. The activity of
combining properties of several procedures is also known as abstracting over
procedures, a natural generalization of abstracting over data. Not only is the
dining out procedure that we discussed in the introduction to Part 1 an ab-
straction of the dining activity, but it can be made to work for other external

consuming activities. Instead of eating in a restaurant, we watch a movie


in a theater, and instead of reading from a menu, we read a marquee. We
still enter, pay, and exit, so we can abstract the procedures within the dining
out procedure, and now we can refer to it as the consuming procedure. If

we feed the consuming procedure the activities of reading the menu and eat-

ing, then we will once again have the dining out procedure, but if we feed it

the activities of reading the marquee and watching a movie, we will have the
movie-going procedure.
A predicate tests whether a value is true or false. Quantifiers tell us whether
some or all objects satisfy a predicate. Set theory is about collections of
elements and the properties of operations over such collections. In Chapter 8,
we develop a set algebra with quantifiers using higher-order procedures. The
essence of reasoning with higher-order procedures is the point of this part.

^^4 Procedures as Values


Abstracting Procedures

7.1 Overview

In this chapter, we first see how procedures can be passed as arguments to


other procedures and how procedures may be the values of other procedures.
We illustrate these ideas with a development of the Ackermann procedure.
We then show how a procedure of two arguments may be rewritten as a
procedure of one argument whose value is a procedure of one argument. This
process is called currying. We next look at several programs that are similar
in structure and we abstract these common features in a program that can be
used easily to generate any other program with these features. This process
is called procedural abstraction. Flat recursion on lists is often encountered
in program.ming, so we have selected it as the first candidate for abstraction.
That is followed by an abstraction of deep recursion.

7.2 Procedures as Arguments and Values

In this section, we shall study the use of procedures as arguments to other


procedures and as values of procedures. In Chapter 1, we included procedures
as a type of datum and have on occasion used procedures as arguments to
other procedures. For example, in the definition of max in terms of extreme-
value in Chapter 3, we passed the procedure > as an argument to the proce-
dure extreme-value. In Scheme, all procedures may be used as arguments
to other procedures and as values of procedures. This idea is illustrated by
many examples in this section.

Suppose we have a list of numbers, such as (13 5 79), and we want to


Program 7.1 map

(define map
(lambda (proc Is)
(if (null? Is)
'()

(cons (proc (car Is)) (map proc (cdr Is))))))

produce a new list that is obtained from the old by adding 1 to each item in

the list, so that in our example, we would get (2468 10). We can define
a procedure addl-to-each-item that takes a list Is and returns the new list

with each number augmented by 1.

(define addl-to-each-item
(lambda (Is)
(if (null? Is)
'()

(cons (+ 1 (car Is)) (addl-to-each-item (cdr Is))))))

Now if we want to add 2 to each element, we have to write the definition


again but with (+1 (car Is)) replaced by (+ 2 (car Is)). Since we may
want to perform many different operations on the elements of the list, it

would be more we had a procedure that takes as arguments both


efficient if

the procedure we wish to apply to each element and the list. There is a
Scheme procedure map that has the parameters proc and Is and returns a
list that contains those elements that are obtained when the procedure proc

of one argument is applied to each element of Is. Thus

(map addl ' (1 3 5 7 9)) => (2 4 6 8 10)

A definition of map is given in Program 7.1. To add 2 to each element in the


list,we pass the procedure of one argument, (Isunbda (num) (+ num 2)), as
the first argument to map. Thus we have

(map (lambda (num) (+ num 2)) ' (1 3 5 7 9) ) =* (3 5 7 9 11)

We can also apply map with a procedure that operates on lists as its first

argument. For example:

(let ((proc (lambda (Is) (cons 'a Is))))


(map proc '((be) (d e) (f g h)))) => ( (a b c) (a d e) (a f g h))

196 Abstracting Procedures


Program 7.2 lor-each

(define for-each
(lambda (proc Is)
(if (not (null? Is))
(begin
(proc (car Is))
(for-each proc (cdr Is))))))

(let ((x 'a))


(let ((proc (lambda (Is) (member? x Is))))
(map proc '((a b c) (bed) (c d a))))) =* (#t #f #t)

Observe that the elements of the list making up the second argument to map
must be of the correct type for the procedure that is applied to them. In
the first of these two examples, proc is a procedure that takes a list as its

argument and conses the symbol a onto the list. Thus each element of the
second argument to map is a list, and the list that is returned consists of
sublists, each of which begins with the a that was consed onto it.

There are procedures, such as display, that produce side effects of interest
to us rather than their returned values. If we apply such a procedure to each
item in a list, the list that is returned is not what interests us but only the side
effects produced by the procedure. In such cases, we use the Scheme procedure

for-each instead of map to apply the side-effecting procedure to the elements


of the list. When lor-each is applied with a side-effecting procedure as its
first argument and a list as its second argument, the procedure is applied

to each item in the list, the desired side effects are produced, and the value
that is returned is unspecified, that is, it depends upon the implementation
ofScheme being used. A definition of for-each is given in Program 7.2. An
example using for-each is:

[1] (for-each display '("Hello. How are you?"))


Hello. How eire you?

We shall see several more examples of the use of for-each below. But first

we introduce the form of lambda that is used to define a procedure that takes
an arbitrary number of arguments. We use this unrestricted lambda to define
the procedures writeln and error, which we have been using.
In a lambda expression, the keyword lambda is followed by a list of param-
eters. Its syntax is

7.2 Procedures as Arguments and Values 197


. .

(Isunbda (parameteri . . . ) expri expr2 • • • )

where zero or more parameters are in the list of parameters and where the
number of arguments passed to the procedure, which is the value of this
lambda expression, must match the number of parameters. The body of the
lambda expression consists of one or more expressions, which are evaluated in
order and the value of the last one is returned. Suppose we want to define a
procedure add that can be applied to arbitrarily many numbers and returns
their sum. For example, we would like to have

(add 1 3 5 7 9) =» 25
(add 1 3 5 7 9 11) ==* 36
(add 1 3 5 7 9 11 13) =» 49

It is possible to define a procedure that can be applied to any number of


arguments using the unrestricted lambda, whose syntax is

(lauabda var expri expr2 . )

and it may be applied to any number of operands by invoking

(danbda var expri expr2 ) operandi ...)

If the operands operandi . . . have the values argi . . ., then the variable var is

bound to the list of arguments (argi . .). The expressions expri expr2 . .

in the body are evaluated with this binding in eff'ect.

Program 7.3 add

(define add
(letrec ((list-add
(laabda (Is)
(if (null? Is)

(+ (car Is) (list-add (cdr Is)))))))


(lambda eurgs
(list-add args))))

As an example. Program 7.3 shows the definition of a procedure add that


produces the sum of its arguments. For example, (add 12 3 4 6) =^ 16.

198 Abstracting the Structure of Procedures


Program 7.4 list

(define list (lambda args args))

Program 7.5 writeln

(define writeln
(lambda arge
(for-each display <irgs)
(newline)))

Program 7.6 error

(define error
(lambda args
(display "Error:")
(for-each (leunbda (value) (display " ") (display value)) eirgs)
(nevline)
(reset)))

The general strategy for using this form of lambda is to remember that eurgs is

a list, so we define a local procedure list-add that takes a list as its argument
and let it do what we want add to do. Then we call list-add with the list

args as its argument.


Similarly, the procedure list is defined in Program 7.4 so that

(list 'a 'b 'c 'd) =» (a b c d)

Two procedures, writeln and error, can also be defined using the unre-
stricted lambda. These are shown in Programs 7.5 and 7.6. The procedure
of no arguments reset in Program 7.6 returns the user to the prompt. Many
implementations of Scheme provide the procedure reset. A discussion of the
concepts used to define reset is given in Chapter 16. (See Exercise 16.6.)
Suppose we now want to find the maximum of two numbers in a list Is.

We cannot invoke (max Is), since the list Is is not the correct data type for
an argument to max, which expects each of its arguments to be a number.
If Is were, for example, (2 4), we would be looking at the expression (max

'(2 4)), which has the wrong type of argument for max. We could write
a recursive program that would compute the maximum of the values in Is.

7.2 Procedures as Arguments and Values 199


Program 7.7 add

(define add
(lambda args
(if (null? args)

(+ (car args) (apply add (cdr args))))))

However, there Scheme procedure, apply, that allows us to apply a


is the
procedure of k arguments to a list of k items, and the results are the same as
if the items in the list were passed as the k arguments. The procedure apply

has the call structure

(apply proc list-of -items)

where the procedure proc takes the same number of arguments as the number
of items in the list Hst-of-items. It returns the value obtained when we invoke
proc with the items in list-of-items as its arguments. For example, we can
call

(apply max '(2 4)) =* 4


(apply + '(4 ID) =^ 15

The use of apply gives us another way to define procedures using the un-
restricted leunbda. Program 7.7 illustrates it by redefining the procedure add
given in Program 7.3, this time using apply in the recursive invocation of add
on the list (cdr args). There add is defined to apply to an arbitrary number
of numbers, so it cannot be applied directly to (cdr aorgs), which is a list

of numbers. Thus we use apply to invoke add on the items in the list (cdr
args).
The Scheme procedures + and are also defined to take an arbitrary number
of arguments. Thus we have:

(+ 1 3 5 7 9) => 25
(+ 5) => 5
(+) =>
(* 2 4 6) =« 48
( 5) ==» 5
(*) => 1

200 Abstracting the Structure of Procedures


Scheme procedures maa and min
Similarly, the are defined to take one or
more arguments. Thus we have

(ax 5 -10 15 -20) ^ 15


(in 5 -10 15 -20) =* -20

An object in Scheme is said to be a firsi-class object if it can be petssed as


an argument to procedures, can be returned by procedures, and variables may
be bound to it. We have been using data objects such as numbers, symbols,
or lists ofnumbers or symbols as arguments to procedures and as values of
procedures, and we have bound them to variables using define, lambda, let
and letrec. Procedures are also treated as first-class objects in Scheme. This
is not the case in many other programming languages. We now explore further
the implications of procedures as first-class objects.
To we first look at the composi-
discuss the composition of two procedures,
tion of two functions from a mathematical point of view. Assume that / and
g are functions that take one argument and that each value of the function g
is a valid argument of the function /. We can then speak of the composition

h of the two functions / and g to be the function of one argument defined


by h{x) = fig{x)): that is. to get the value of h at x. we first evaluate g at
X. and then invoke / on the value gix). This idea can be interpreted for the
procedures we use in our programs. We now define a procedure compose that
takes two procedures f and g as parameters and returns another procedure
that is the composition of f and g.

Program 7.8 compose

(define compose
(la«bda (f g)
(laabda (x)
(f (g x)))))

The body of the first lambda expression constructs the procedure

(laabda (x)
(f (g x)))

with one parameter x.Thus (compose f g) is a procedure of one argument,


and we invoke this new procedure on 8 by writing ((compose f g) 8). As

7.2 Procedures as Arguments and \'alues 201


an example, let us take addl for g and sqrt for f . Then we can define the
composition h by writing

(define h (compose sqrt addl))

The new procedure h is and then takes the


the procedure that adds 1 to x
square root of the result; expressed mathematically, h{x) = yjx + 1. If we
invoke h with argument 8, we get (h 8) ==* 3. Observe that we have passed
the procedures sqrt and addl as arguments to the procedure compose. Fur-
thermore, the value of the procedure compose is itself a procedure of one
argument. This illustrates both the fact that we can pass procedures, such as
sqrt and addl, as arguments to a procedure and we can have the value of a
procedure be a procedure.
If we reverse the order of the two procedures addl and sqrt as arguments
of compose in our previous example, we get the procedure

(define k (compose addl sqrt))

The procedure k so defined first takes the square root of its argument and
then adds one to the result; that is, A:(x) = ^Jx-\- 1. Thus k is quite a diff"erent
function from h.

Exercise

Exercise 1.1
What operand do we pass to k to get the same value eis (h 8)?

We next develop several basic arithmetic procedures that lead to an inter-


esting example that illustrates the use of procedures as values. The procedure
plus may be defined in terms of addl and subl by making use of the fact
that to add two nonnegative integers x and y, we can add 1 to x repeatedly

y times. This leads to Program 7.9. Similarly, using the fact that multipli-
cation of positive integers can be considered as repeated addition, times can
be defined in terms of plus and subl as shown in Program 7.10. This says
that multiplication of positive integers x and y is the same as adding x to
itself y times. In the same way, we can consider raising x to the exponent y
eis multiplying x by itself y times, so we can write the procedure exponent cis

shown in Program 7.11.

202 Abstracting Procedures


Program 7.9 plus

(define plus
(Icimbda (x y)
(if (zero? y)
X
(addl (plus X (subl y))))))

Program 7.10 times

(define times
(lambda (x y )
(if (zero? y)

(plus X (times x (subl y))))))

Program 7.11 exponent

(define exponent
(Icimbda (x y)
(if (zero? y)
1

(times X (exponent x (subl y))))))

Program 7.12 super

(define super
(lambda (x y)
(if (zero? y)
1

(exponent x (super x (subl y))))))

The three procedures we have defined follow a simple pattern. Using this

pattern, we can define another procedure, which we call super, that uses

exponent and subl, as shown in Program 7.12. What does super do? Let us
evaluate (super 2 3).

7.2 Procedures as Arguments and Values 203


Program 7.13 superduper

(define superduper
(lafflbda (x y)

(if (zero? y)
1

(super I (superduper i (subl y))))))

Program 7.14 super- order

(define super- order


(lambda (n)
(cond
((= n 1) plus)
((= n 2) times)
(else (lambda (3: y)
(cond
((zerc>? y) 1)

(else ((super- order (subl n))


z
((super -order n) I (subl y))))))))))

(super 2 3) ^^ (erponent 2 (super 2 2))


=* (exponent 2 (exponent 2 (super 2 1)))
==> (exponent 2 (exponent 2 (exponent 2 (super 2 0))))
=* (exponent 2 (exponent 2 (exponent 2 1)))
^^ (exponent 2 (exponent 2 2))
^^ (exponent 2 4)
=> 16

Thus (super 2 3) is 2^ . In the same way we get that (super 2 4) is 2^

(a tower of 4 twos), which is 65,536. We see that super yields large numbers
even with relatively small arguments like 2 and 4.

We now go to the next step and define superduper using super ajid subl, as
shown in Program 7.13. Then (superduper 2 3) is (super 2 4) or 65,536,
and (superduper 2 4) is (super 2 65536), which is a tower of 65,536 twos.
This is a very large number.
We can continue defining successive procedures by this process, but we must
make up a new name for each one. It would be better to define a procediire

204 Abstracting Procedures


super-order that depends upon a number n, so that (super-order 1) is the
same procedure as plus, (super-order 2) is the same procedure as times,
(super-order 3) is the same procedure as exponent, and so forth. The
definition of super-order is given in Program 7.14. If n is 1,super-order
is the same as plus, and if n is 2, then super-order is the same as times.
For each value of (super-order n) is a procedure of two arguments;
n, for
example, ((super-order 4) 2 3) is the same as (super 2 3) or simply 16.
We can now write any procedure in the sequence by selecting the appropriate
value for the parameter n in (super-order n). For example, the procedure
that comes after superduper is (super-order 6).

If all three of the arguments, n, x, and y, in super-order are the same, it

is called the Ackermann procedure. Specifically, we can define

Program 7.15 ackermann

(define ackermemn
(lanbda (n)
((super-order n) n n)))

Then

(ackermann 1) is the sane as (plus 1 1) which is 2.

(ackermetnn 2) is the same as (times 2 2) which is 4.

(ackermanx. 3) is the same as (exponent 3 3) which is 27.

(ackermann 4) is the same as (super 4 4) which is 4

To get an estimate of how large (ackermann 4) is, we first note that 4'* is
256. To estimate 4^ = 4^^®, we set z = 4^"® and take the logarithm to get
logioz = 256logio4 = 154.13. Thus we get 4^^^ % 10^^* as our estimate for
4"* . Finally we estimate

similarly. If we set y = (ackermann 4), then log^oy ~ 10^^'*logio4 %


10^^'*0.602. Then y » 10^°"^ which means that (ackermann 4) has ap-
proximately 10^^^ digits. Can you estimate the magnitude of (ackermann
S)? The Ackermann procedure played an important role as an example in
the general theory of recursive functions. (See, for example, Minsky, 1967.)
It certainly does grow fast as n increases.

7.2 Procedures as Arguments and Values 205


)

We see in the definition of super-order that we have a procedure with

parameter n whose value is itself a procedure with parameters x and y, il-

lustrating again how procedures are first-class objects in Scheme. We shall


explore these ideas further in the next section, which deals with procedural
abstraction.

Exercises

Exercise 7.2: composes


Use the procedure compose to define a procedure composes that takes as
arguments three procedures. /. g. and h, and returns the composition k such
that for each argument x, k{x) = f{g{h{x))).

ExeTcise 7.3: compose -many


Use the unrestricted lambda to define a composition procedure compose-many
that forms the composition of arbitrarily many procedures of one argument.
Test your procedure on

((compose-many addl addl addl addl) 3) ^^ 7


( (coapose-aany sqrt abs subl (lambda (n) (* n n))) 0.6) ==* 0.8
(let ((f (lambda (n) (if (even? n) (/ n 2) (addl n) ) ) )
((compose-many f f f f f f) 21)) =^ 4

Exercise 7.4: subtract


Based on the technique used in this chapter to define plus, times, etc., define
the procedure subtract that has as parameters two nonnegative integers x
and y, with x > y. and returns the difference between x and y.

Exercise 7.5
In the following experiment, fill the blanks with the values of the expressions.

[1] (let ((h (lambda (i) (cons i i))))


(aap h '((1 2) (3 4) (5 6))))
7

[2] (aap (lambda (i) (cons i i)) '((12) (3 4) (5 6)))


9

[3] (aap (laabda (x) (+ 5 i)) '(1234))


7

[4] (let ((n 5))


(let ((proc (lambda (i) (+ n i))))
(map proc '(1 2 3 4))))

206 Abstracting Procedures


[5] (define iota
(lambda (n)
(letrec ((iota-helper
(lambda (k ace)
(cond
((zero? k) (cons ace))
(else (iota-helper (subl k) (eons k ace)))))))
(iota-helper (subl n) '()))))
[6] (letrec ((fact
(lambda (n)
(if (zero? n) 1 (* n (fact (subl n)))))))
(map fact (iota 6)))

[7] (map (lambda (x) (+ x (addl x))) (iota 5))


7

[8] (define mystery


(lambda (len base)
(letrec
( (mystery-help
(lambda (n s)
(if (zero? n)
(list s)
(let ((h (lambda (i)
(mystery-help (subl n) (cons x s)))))
(apply append (map h (iota base))))))))
(mystery-help len *()))))
[9] (mystery 4 3)

Exercise 7.6: map-first-two


Define a procedure, map-first-two, that works exactly like map except that
the procedure argument is always a procedure of two arguments instead of
just one argument. Use the first and second elements of the list as the first
pair of arguments to the procedure, then the second and third elements, then
the third and fourth elements, and so on, until the end of the list is reached.
If there are fewer than two elements in the list, the empty list is the value.

Test your procedure on:

(map-first-tHO + (2 3 4 5
' 7) ) => (5 7 9 12)
(map-first-two max '(2 4 3 5 4 1)) => (44554)

Exercise 1.1: reduce


Define a procedure, reduce, that has two parameters, proc and Is. The
procedure proc takes two arguments. The procedure reduce reduces the list

7.2 Procedures as Arguments and Values 207


Is by successively applying this operation: it builds a new list with the first

two elements of the preceding list replaced by the value obtained when proc
is applied to them. When the list is reduced to containing only two elements,
the value returned is the value of proc applied to these two elements. If the
original list Is contains fewer than two elements, an error is reported. Here
is how the successive stages in the reduction look when proc is + and Is is

(3 5 7 9):

(3 5 7 9) -> (8 7 9) ^ (15 9) -* 24

Test your procedure on:

(reduce + ' (1 3 5 7 9)) =J> 25


(reduce max ' (2 -4 6 8 3 1)) => 8
(reduce (lambda (i y) (and x y)) ' (#t #t #t #t)) => #t

The last example is not written as (reduce and '


(#t #t #t #t)) because
and is a keyword of a special form and not a procedure. Keywords only appear
in the first position of a list.

Exercise 7.8: andmap


Define a predicate andmap that takes two arguments, a one-argument predicate
pred and a The value returned by andmap is true when pred applied
list Is.
to each of the elements of Is is true. If pred applied to any one of the elements
of Is is false, andmap returns false. The solution

(define andmap
(lambda (pred Is)
(reduce (lambda (x y) (and x y)) (map pred Is))))

is unacceptable because of the extra recursion. Test your predicate on:

(andmap positive? '(3 4 6 9)) =* #t


(andmap positive? '(3-148)) => #f
(let ((not-null? (compose not null?)))
(andmap not-null? '((a b) (c) (c d e)))) => #t

Exercise 7.9: map2


Define map2, which is exactly like map except that its procedure argument
is always a procedure that takes two arguments, and it takes an additional
argument that is a list the same length as its second argument. The additional
list is where it gets its second argument. Test your procedure on:

(map2 + '(1234) '(579 11)) => (6 9 12 15)

208 Abstracting ProceduTes


(inap2 (let ((n 5))
(leunbda (x y)
(and « X n) « n y))))
'(13 2 1 7)
'(9 11 4 7 8))
)
=> (#t #t #f #t #f)

Exercise 7.10: map, ormap


We now present a definition of map that accepts any number of arguments.

(map proc Isi I32 ...Isn)

where proc is a procedure that takes n arguments and each of the n lists has
the same length. This generalizes the procedures map and map2 given above.

(define map
(leunbda args
(let ((proc (car args)))
(letrec ((map-helper
(lambda (a*)
(if (any-null? a*)
•()

(cons
(apply proc (.map car a*))
(map-helper (.map cdr a*)))))))
(map-helper (cdr args))))))

This program, as written, is incorrect because the two invocations of map


within the definition refer to the simple map we defined earlier in the chapter.
Add a definition of the simple map to the letrec (in the same way that even?
and odd? are in the same letrec) so that no names will be changed in the
definition of map-helper, and write zmy-null? using the definition of ormap
given below.

(define ormap
(lambda (pred Is)
(if (null? Is)
#f
(or (pred (car Is)) (ormap pred (cdr Is))))))

What does this version of map return when the n lists are not of equal length?

7.2 Procedures aa Arguments and Values 209


Exercise 7.11
To test your understanding of scope, determine the value of the expression

(letrec ((a (let ((a (lambda (b c)


(if (zero? b) c (a (subl b))))))
(lambda (x) (a x x)))))
(a 3))

7.3 Currying

The procedure + takes two numbers as arguments and returns their sum. The
procedure addl adds 1 to its argument. We can also define a procedure addS
that adds 5 to its argument by writing

(define add5
(lambda (n)
(+ 5 n)))

This can clearly be done for any number in place of 5. Another way of
approaching this problem makes use of the fact that a procedure may return
another procedure as its value. We can define a procedure curried+ that has
only one parameter, m, and returns a procedure having one parameter n, that
adds m and n:

(define curried+
(lambda (m)
(lambda (n)
(+mn))))

Thus (curried+ 5) returns a procedure defined by

(lambda (n) (+ m n))

where m is bound to 5. To add 5 and 7, we would then invoke

((curried* 5) 7) ^ 12

We can now define add5 by writing

(define add5 (curried+ 5))

210 Abstracting the Structure of Procedures


Moreover, we can define add8 by writing

(define add8 (curried* 8))

and we clearly can do the same for any other number in place of 8. What
underlies this method is the fact that we can take any procedure that has two
parameters, say x and y, and rewrite it as a procedure with one parameter
X that returns a procedure with one parameter y. The process of writing a
procedure of two parameters as a procedure of one parameter that returns
a procedure of one parameter is called currying the procedure.^ It is often
advantageous to use a curried procedure when you want to keep one argument
fixed while the other varies, so in essence, you are using a procedure of one
argument.
We next use currying to rewrite the definitions of four procedures in a way
that demonstrates certain common structural features that they possess. In
the next section, we shall abstract these common features and write a single
procedure from which the original four and many others can be obtained. The
four procedures are member?, map, sum, and product.
The procedure member? can be defined as follows:

(define member?
(Icunbda (item Is)
(if (null? Is)
»f
(or (equal? (car Is) item)
(member? item (cdr Is))))))

It tests whether the object item is a top-level object in the list Is. We are

going to apply the procedure member? with the same object item but different
lists Is, so we define the curried procedure member?-c, which is a procedure
with parameter item and returns a procedure that has the parameter Is and
tests whether item is a top-level member of Is. We do that in Program 7.16.

Observe the following points in the definition of member?-c:

1. member?-c is a procedure with one parameter item.

2. The procedure member?-c returns a procedure helper that has one param-
eter Is.

^ Conceived by Moses Schonfinkel in 1924 (See Schonfinkel, 1924) and neimed after the
logician Haskell B. Curry.

7.3 Currying 211


Program 7.16 member?-c

(define member?-c
(lambda (item)
(letrec
((helper
(lambda (Is)
(if (null? Is)
#f
(or (equal? (car Is) item) (helper (cdr Is)))))))
helper)))

3. We introduced the letrec expression to avoid having to pass the argument


item each time we make a recursive procedure call, since item does not
change throughout the program.

We can now define the original procedure member? in terms of member ?-c
by writing

(define member?
(lambda (a Is)
((member?-c a) Is)))

As another example of currying, we look at the definition of the procedure


map, presented in Program 7.1, which hcis two parameters, a procedure proc,
and a list Is. It applies the procedure proc elementwise to Is and returns a
list of the results. For example,

(map addl ' (1 2 3 4) ) ^ (2 3 4 5)

Its definition is:

(define map
(lambda (proc Is)
(if (null? Is)
'()

(cons (proc (car Is)) (map proc (cdr Is))))))

This can be written in curried form by using the procedure apply-to-all,


which takes one argument proc and is itself a procedure of the argument
Is. We give its definition in Program 7.17. We can write map in terms of
apply-to-all by defining

212 Abstracting Procedures


) )

Program 7.17 apply-to-all

(define apply-to-all
(lambda (proc)
(letrec
( (helper
(lambda (Is)
(if (null? Is)
'0
(cons (proc (c«ir Is)) (helper (cdr Is)))))))
helper) )

Program 7.18 sum

(define sum
(letrec
( (helper
(lambda (Is)
(if (null? Is)

(+ (cai Is) (helper (cdr Is0))))))

helper)

Program 7.19 product

(define product
(letrec
( (helper
(lambda (Is)
(if (null? Is)
1

(* (cai Is)

(helper (cdr Is)))))))
helper)

(define map
(lambda (proc Is)
((apply-to-all proc) Is)))

We next look at two more procedures that take lists as arguments. The first,

7.3 Currying 213


Program 7.20 swapper-m

(define swapper-m
(laMbda (x y)
(letrec
((helper
(laabda (Is)
(cond
((null? Is) '())
((equal? (car Is) x) (cons y (helper (cdr Is))))
((equal? (car Is) y) (cons X (helper (cdr is))))
(else (cons (cao: Is) (helper (cdr Is))))))))
helper)))

sum, assumes that the objects in the list are numbers and returns the sum of
the numbers in the list, and the second, product, assumes that the objects
in the list are numbers and returns the product of the numbers in the list.

We write their definitions in Programs 7.18 and 7.19 in such a way that they
demonstrate the same structure as the preceding definitions of member?-c
and apply-to-all. We could have written the procedures sum and product
without theletrec expressions, but we have chosen to do it this way to be

able tocompare the structure of these two procedures with the structure of
member?-c and apply-to-all when we abstract this structure in the next
section.
We close this section with an example that is similar to currying, this time
modifying a procedure with three parameters to get a procedure with two
parameters that returns a procedure with one parameter. We look at the
procedure swapper introduced in Program 2.8. Its definition is:

(define swapper
(laabda (x y Is)
(cond
((null? Is) '())
((equal? (car Is) x) (cons y (swapper x y (cdr Is))))
((equjd? (car Is) y) (cons x (swapper x y (cdr Is))))
(else (cons (car Is) (swapper x y (cdr Is)))))))

We modify it swapper-m (we use -m for "modified") that


to get a procedure
has the two parameters x and y and that returns a procedure of one parameter
Is. Its definition is given in Program 7.20. To swap the numbers and 1 in
the list (0 1 2 1 2), we would invoke

214 Abstracting the Structure of Procedures


((swapper-m 1) ' (0 1 2 1 2)) => (1 2 1 2)

This example illustrates that a generalization of currying can be used to re-


define a procedure with n m
= + k parameters to become a procedure with
m parameters that returns a procedure with k parameters. The term curry-
ing refers to redefining a procedure with n parameters to be expressed as n
procedures, each having only one parameter.
In this section, we have introduced the concept of currying a procedure of
two arguments to get a procedure of one argument that returns a procedure of
one argument. This technique is useful when we want to consider the behavior
of the procedure as the second argument varies while the first argument is

fixed. More generally, a procedure o{ n = m-j- k arguments may be modified


to get a procedure of m arguments that returns a procedure of k arguments.

Exercises

Exercise 7.12: curried*


Curry the procedure to get a procedure curried* and use it to define the
procedure timeslO that multiplies its argument by 10. Test your procedures
on:

((curried* 25) 5) =^ 125


(timeslO 125) =* 1250

Exercise 7.13: swapper-c


Curry the procedure swapper-m so that the curried procedure swapper-c has
one parameter x. It returns a procedure with one parameter y, which in turn
returns a procedure with one parameter Is. That procedure swaps x and y
in Is.

Exercise round-n-places
7.14-'

In Program procedure round-n-places was defined to take two pa-


6.5, the

rameters, n and dec-num, and returned the number dec-num rounded off" to
n decimal places. Rewrite the definition of round-n-places so that it takes
one parameter, and returns a procedure with one parameter, dec-num, that
n,

rounds the number dec-num off" to n decimal places. We can then write

(define round- 5 -places (roimd-n-places 5))

to get the procedure that rounds a given number off" to five decimal places.

7.3 Currying 215


Exercise 7.15: subst-all-m
Modify the deeply recursive procedure subst-all, which has the parameters
new, old, and Is, to get a procedure subst-all-m with the two parameters
new and old, which returns a procedure with the parameter Is, which replaces
each occurrence of old in Is by new. Test your procedure on:

((subst-all-m 10) '


(0 1 2 1 2) ) => (1 1 2 1 1 2)
((subst-all-m 1 0) '(0 1 2 ((0 1 2)))) => (1 1 2 ((1 1 2)))

Exercise 7.16: extreme-value-c


In Program 3.19, the procedure extreme-value was defined and then it was
used to define the procedures rmajc and rmin by pcissing it the appropriate
predicate. Write the definition of the procedure extreme-value-c, which
takes the predicate pred and returns a procedure that finds the maximum
of its two arguments or the minimum of its two arguments, depending upon
pred. Then express rmax and rmin in terms of extreme-value-c.

Exercise 7.17: extreme-value-c


In the previous exercise, the procedure (extreme-value-c pred) expects
only two arguments. Rewrite the definition of extreme-value-c using the
unrestricted lambda so that (extreme-value-c pred) is a procedure that
takes arbitrarily many numbers as arguments and returns the extreme value
(maximum or minimum) depending upon the predicate pred.

Exercise 7.18: between?, between?-c


Define a predicate between? that has three numbers i, y, and z, as parameters
and returns true when y is strictly between x and z, that is, when x < y <
z. Then define between?-c, a curried version of between?, where each of the

procedures has only one parameter. That is, between?-c has the parameter
X and returns a procedure that has the parameter y, which in turn returns
a procedure with the parameter z, that tests whether y is strictly between x
and z. Test your procedure on:

(((betHeen?-c 5) 6) 7) =^ #t
(((betBeen?-c 5) 5) 7) => »f
(((betHeen?-c 5) 4) 7) ==» #f

Exercise 7.19: andmap-c, ormap-c


Consider this definition of andmap-c:

216 Abstracting Procedures


(define emdmap-c
(lambda (pred)
(letrec
( (and-help
(lambda (Is)
(cond
((null? Is) #t)
(else (and (pred (car Is)) (and-help (cdr Is))))))))
and-help)))

Fill in the blanks below.

[1] (define all-positive? (£m.dmap-c positive?))


[2] (all-positive? '(3489))
7
[3] (all-positive? '(3 -1 4 8))
7
[4] ((andmap-c (compose not null?)) '((a b) (c) (c d e)))
7

Now define the procedure ormap-c, which takes a predicate as an argument


and returns a predicate that accepts a list as a value. We can define ormap
(see Exercise 7.10) using ormap-c as follows:

(define ormap
(lambda (pred Is)
((ormap-c pred) Is)))

Test ormap-c by filling in the blanks below.

[5] (define some-positive? (ormap-c positive?))


[6] (some-positive? '(3 4 8 9))
7
[7] (some-positive? '(3 -1 4 8))
7
[8] ((ormap-c (compose not null?)) '(() (a b) (c) (c d e)))
9

Exercise 7.20: is-divisible-by?, prime?


Consider the definition

(define is-divisible-by?
(leunbda (n)
(lambda (k)
(zero? (remainder n k)))))

7.3 Currying S17


A prime number is a positive integer greater than 1 that is not divisible by any
positive number other than 1 and itself. Using is-divisible-by?, write a
definition of the procedure prime? that tests whether a positive integer n > 2
is prime by first testing whether it is odd and greater than 1 and then testing

whether it is not divisible by any of the odd integers from 3 to the largest odd
integer less than or equal to the square root of n. Why is it necessary only to
try integers less than the square root of n?

Exercise 7.21
Justify the statement "If we restrict ourselves to using only lambda expressions
having only one parameter in its list of parameters, we can still define any
procedure, regardless of how many parameters it has." Note that the currying
examples in this section show how to define procedures having two and three
parameters using only lambda expressions with one parameter.

7.4 Procedural Abstraction of Flat Recursion

In this section, we show how to abstract the structure of flatly recursive pro-
cedures to obtain a general procedure in terms of which the various special
cases can be defined. We illustrate this idea by looking for common structural
features in the four procedures member?-c, apply-to-all, sum, and product
defined in Section 7.3. A comparison of the code for these four procedures

yields the fact that the four lines

(letrec
((helper
(lambda (Is)
(if (null? Is)

and the leist line

helper

are identical in all four programs. Furthermore, in we do something


all four,

to (car Is) and make the recursive call to helper on (cdr Is). We want
to define a procedure flat-recur that abstracts the structure of these four
programs; that is, it common features of these programs, and
embodies the
they can all be derived from it by using suitable parameters. Let us see how
much of flat -re cur we can write based on the above observations.

218 Abstracting Procedures


(define flat-recur
(Icunbda (

(letrec
( (helper
(Icu&bda (Is)
(if (null? Is)

))))
helper)))

How do we fill in the blanks? Let us first look at the blank that is the
consequent of the if expression. It is the action taken when Is is empty.
We call this consequent of the test (null? Is) the seed and denote it by the
variable seed. This will be the first parameter in the outer lambda expression.
Table 7.21 shows the seed for each of the four cases.

Procedure seed
inember?-c #f
apply-to-all
sum
product 1

Table 7.21 Seeds for the four procedures

The other blank in the if expression is in the action taken on (car Is) and
(helper (cdr Is)) when (null? Is) is false. The action taken on (car
Is) and (helper (cdr Is)) is a procedure that takes (car Is) and (helper
(cdr Is)) as arguments, and we call this procedure list-proc. We write

list-proc as a procedure with the parameters x and y. When list-proc is

invoked, x will be bound and y will be bound to (helper (cdr


to (caur Is)

Is) ) to give us the alternative action when (null? Is) is false. For example,
the alternative action in the case of apply-to-all is

(cons (proc (car Is)) (helper (cdr Is)))

If list-proc is the value of

(lambda (x y) (cons (proc x) y))

then

7.4 Procedural Abstraction of Flat Recursion 219


(list-proc (car Is) (helper (cdr Is)))

is the desired alternative action. We pass list-proc as the second parameter


in the outer lambda expression. Table 7.22 gives list-proc for each of the
four programs.^

Procedure list-proc
member?-c (lambda (x y) (or (equal? x item) y))
apply-to-all (lambda (x y) (cons (proc x) y))
sum +

product

Table 7.22 The four list procedures

We are now ready to define the procedure flat-recur, which takes seed
and list-proc as arguments and produces precisely the procedure with pa-
rameter Is that abstracts the structure of the four procedures. (See Pro-
gram 7.23.) We can then write each of the four procedures in terms of this
new procedure. Furthermore, we can use it to write any procedure using
recursion on a list of top-level items.
We can now write the four procedures using flat-recur as follows:

(define member?-c
(lambda (item)
(flat-recuT #f (lambda (x y) (or (equal? i item) y)))))

(define apply-to-all
(launbda (proc)
(flat-recur '() (launbda (x y) (cons (proc i) y)))))

(define sum (flat-recur +))

(define product (flat-recur 1 *))

^ The procedure that we selected for list-proc in the case of meniber?-c does more pro-
cessing than is necessary, for it loses the benefit of the behavior of or. Generally when the
first argument to or is true, the value #t is returned without evaluating the second argu-
ment. However, when list-proc both arguments are first evaluated, and then
is called,
the or expression is evaluated, so the argument to which y is bound is always evaluated.
Even though the resulting version of member?-c is less efficient, it illustrates the principle
of procediiral abstraction and a feature that one should be aware of when applying it.

220 Abstracting Procedures


Program 7.23 flat-recur

(define flat-recur
(lambda (seed list-proc)
(letrec
( (helper
(laabda (Is)
(if (null? Is)
seed
(list-proc (car Is) (helper (cdr Is)))))))
helper)))

You may be concerned that the procedure list-proc in these last two ex-
amples has a different structure from those in the first two examples. This is

really not the case, since we could also have used (lambda (x y) (+ x y))
in place of the variable +. and we could have used (lambda (x y) (* x y))
in place of the variable *.

The process we have used here looks for common features in several pro-
grams and then produces a procedure that embodies the code that is similar
in all of these programs. It enables us to express each of the original proce-
dures more simply. This process is called procedural abstraction. This is a very
powerful programming tool that should be exploited when it is applicable.

The procedure llat-recur can be used whenever a program does recursion


on the top-level objects in a list. We now see an example of how we can use
itto define the procedure f ilter-in-c. Let Is be a list and suppose that
we have a predicate pred that we want to apply to each top-level object in
the list. If the result of applying pred to an object in the list is false, then
the object is to be dropped from the list. Thus the procedure f ilter-in-c
returns a list consisting of those objects from Is that "pass" the test. This
program involves recursion on the top-level objects in the list Is, and if Is is
empty, f ilter-in-c returns the empty list, so seed is (). To get list-proc
we shall again use x for the (car Is) and y for (helper (cdr Is)). Then
if pred applied to x is true, we cons x to y; otherwise we just return y. Thus

the list-proc of flat-recur can be written as

(lambda (x y)
(if (pred x)
(cons X y)
y))

7.4 Procedural Abstraction of Flat Recursion 221


Program 7.24 f ilter-in-c

(define filter-in-c
(lambda (pred)
(flat-recur
'()

(lambda (x y)
(if (pred x)
(cons X y)
y)))))

and we can define filter-in-c shown in Program 7.24. If we do not want


as
to use filter-in-c in curried form, we can define the procedure filter-in
as:

(define filter-in
(lambda (pred Is)
((filter-in-c pred) Is)))

Here are some examples using filter-in:

(filter-in odd? ' (1 2 3 4 5 6 7 8 9)) =» (1 3 5 7 9)


(filter-in positive? ' (1 2 3 4)) => (1 2 3 4)
(filter-in (lambda (x) « x 5)) ' (1 2 3 4 5 6 7 8 9)) => (1 2 3 4)

In this section, we have illustrated the process of procedural abstraction of


flat recursion. We defined a procedure flat-recur from which procedures
that use flat recursion can be derived by passing flat-recur the appropriate
arguments. This is a powerful tool that can often be used to make programs
easier to write and to understand.

Exercises

Exercise 7.22: mult-by-scalar


In Exercise 3.1, we called a list of numbers an n-tuple. Using flat-recur,
deflne a procedure mult-by-scalar that takes as its argument a number c and
returns a procedure that takes as its argument an n-tuple ntpl and multiplies
each component of ntpl by the number c. Test your procedure on:

((mult -by-scalar 3) '(1-2 3 -4)) =*> (3 -6 9 -12)


((mult-by-scalar 5) '()) =>

222 Abstracting Procedures


Exercise 7.23: filter-out
The procedure filter-out takes two arguments, a predicate pred and a list

Is. It removes from the list Is all of its top-level elements that "pass" the
test, that is, it removes those top-level objects item for which (pred item)
is true. Write the definition of filter-out using a local procedure f ilter-
out-c that is defined using flat-recur.

Exercise 7.24: insert-left


Starting with the procedure insert-left described in Exercise 4.1 and using
flat-recur, define the modified version insert-left-m that takes as pa-
rameters the new and old values and returns a procedure with the list as its

parameter. Then define insert-left using insert-left-m.

Exercise 7.25: partial


Let proc be a procedure of one numerical argument with numerical values.

a. Define a procedure partial-sum that computes the sum of the numbers


(proc i) for i ranging from k to n, where k <.n. For example,

(partial-sum (lambda (m) (* m m)) 3 7) =* 135

b. Define a procedure partial-product that computes the product of the


numbers (proc i) for i ranging from k to n, where k < n. For example

(partial-product (lambda (m) (* m m)) 3 7) =^ 6350400

c. Define an abstraction of partial-sum and partial-product named par-


tial so that partial-sum and partial-product can be defined as

(define partial-sum (partial +))


(define partial-product (partial 1 *))

7.5 Procedural Abstraction of Deep Recursion

The deeply recursive procedures defined in Chapter 4 use recursion on nested


sublists rather than being limited to top-level objects of lists. They also
display a common structure that can be abstracted in a procedure deep-
recur. We now look at some deeply recursive procedures to find their common
structure and then formulate the definition of deep-recur.
We start with f ilter-in-all-c, which takes a pred as its argument and
returns a procedure that has a list Is as its parameter and, when applied to

7.5 Procedural Abstraction of Deep Recursion 223


Is, drops from the list those items that do not "pass" the test. For example,
if pred is odd? and Is is ((4 6) 2 (3 5 (8 7))) we have

((filter-in-all-c odd?) Is) => ((5) (3 5 (7)))

The code for filter-in-all-c is given in Program 7.25. We define lilter-


in-all as the procedure of two arguments, pred and Is, in terms of filter-
in-all-c as Program 7.26.
shown in

In the same way, we define sum-all as a procedure of one argument Is,


which is a list of numbers, such that (sum-all Is) is the sum of all of the
numbers in Is. For example

(sum-all '(3 (1 4) (2 (-3 5)))) ^ 12

The code for sum-all is presented in Program 7.27. Both of these procedures,
sum-all and filter-in-all-c, share the following lines:

(letrec
( (helper
(lambda (Is)
(if (null? Is)

(let ((a (car Is)))


(if (or (pair? a) (null? a))

helper

We are going to define a procedure deep-recur to abstract the structure


of these two procedures. Let us see how much of the code we can fill in from
the above observations.

(define deep-recur
(lambda ( )

(letrec
((helper
(leunbda (Is)
(if (null? Is)

(let ((a (car Is)))


(if (or (pair? a) (null? a))

))))))
helper)))

224 Abstracting the Structure of Procedures


)

Program 7.25 f ilter-in-all-c

(define f ilter-in-all-c

(Icunbda (pred)
(letrec
((helper
(lambda (Is)
(if (null? Is )
'0
(let ((a (car Is)))
(if (or (pair? a) (null? a))
(cons (helper a) (helper (cdr Is)))
(if (pred a)
(cons a lelper (cdr Is)))
(1-

(helper (cdr Is)))))))))


helper) )

Program 7.26 filter-in-all

(define filter- in-edl


(Icunbda (pred Is)
((f ilter-in-all-c pred) Is)))

Program 7.27 sum-all

(define sum- all


(letrec ((helper
(lambda (Is )
(if (null ? Is)

(let ((a (car Is)))


(if (or (pair? a) (null? a))
(+ (helper a) (helper (cdr Is)))
(+ a (helper (cdr Is)))))))))
helper))

7,5 Procedural Abstraction of Deep Recursiori 225


Once again, we use the variable seed to denote the consequent of the first

if expression with test (null? Is). In the case of sum-all, seed is 0, and
in the case of f ilter-in-all-c, seed is (). We take seed to be the first

parameter for the outer lambda expression.


In the consequent of the second if expression with test (or (pair? a)
(null? a) ), the local procedure helper for lilter-in-all-c invokes

(cons (helper a) (helper (cdr Is)))

and the local procedure helper for sum-all invokes

(+ (helper a) (helper (cdr Is)))

We refer to the procedure that is applied to (helper a) and (helper (cdr


Is)) as list-proc. We fill the blank with the application

(list-proc (helper a) (helper (cdr Is)))

and to generate the expression needed for f ilter-in-all-c. we bind list-


proc to cons, and to generate the expression needed for sum-all, we bind
list-proc to +. We take list-proc as the third parameter to the outer
lambda expression. We next consider what to use as the second parameter.
In both of our examples, the alternative of the second if expression with
test (or (pair? a) (null? a)) is a procedure invocation that involves a
and (helper (cdr Is)). For f ilter-in-all-c, we want to generate the
expression

(if (pred a) (cons a (helper (cdr Is))) (helper (cdr Is)))

while for sum-aill, we need

(+ a (helper (cdr Is)))

We can generate both of these using a procedure item-proc that has two
parameters, x and y. If we fill the blank with

(ite»-proc a (helper (cdr Is)))

then to get what we need for sum-all, we bind item-proc to +. To get what
we need for lilter-in-all-c, we bind item-proc to

226 Abstracting the Structure of Procedures


Program 7.28 deep-recur

(define deep-reciir
(lambda (seed item -proc list- proc)
(letrec
((helper
(leunbda (Is )
(if (null ? Is)
seed
(let ((a (car Is )))
(if (or (pair? a) (null? a))
(list-proc (h Blper a) (he Iper (cdr Is)))
(item-proc a (helper (cdr Is.)))))))))

helper)))

(lambda (z y)
(if (pred x)
(cons z y)
y))

We are now in a position to write a procedure deep-recur that abstracts


the structure of these procedures (and those in the exercises at the end of
this section). The procedure deep-recur hcis three parameters: seed, item-
proc, and list-proc. It returns helper, which is a procedure with only one
parameter Is. Combining the observations made above, we get the definition
presented in Program 7.28.
In particular, we can now write

(define sum-all (deep-recxir + +))

anc

(define f ilter-in-all-c

(lambda (pred)
(deep-recur
'()

(lambda (z y)
(if (pred z)
(cons z y)
y))
cons)))

7.5 Procedural Abstraction of Deep Recursion 2S7


In this chapter,we looked at the definitions of the four procedures sum.
product. member?-c. and filter-in-c. all of which performed flat recur-
sion, and abstracted from them their common structural features. We then

defined the procedure flat-recur, which incorporated those common fea-


tures and took as arguments the things that produced the features of the four
procedures that were not common to them all. This enabled us to recover the
original four procedures and others that do flat recursion from flat-recur
by passing to flat-recur the appropriate arguments. We then did a similar
thing with procedures that performed deep recursions. We abstracted from

the two procedures f ilter-in-all-c and sum-all their common features


and defined the procedure deep-rec\ir. We were able to recover the original
two procedures by passing to deep-recur the appropriate arguments. This
process of defining a procedure incorporating the common structural features
of a class of procedures, and then obtaining the procedures in that class by
passing the abstraction the appropriate arguments, is what we called proce-

dural abstraction.

Exercises

Exercise 7.26: remove-all-c. product-all


Write the definitions of remove-all-c and product-eill for arbitrary lists.

The procedure remove-all-c takes an object item as its argument and re-

turns a procedure of the list Is, which removes all occurrences of item in Is.
The call (product-all Is) returns the product of all of the numbers in the
list of numbers Is. In both procedures, preserve the structure displayed in
the above definitions of siun-all and f ilter-in-all-c using letrec.

Exercise 7.27: remove-all-c. product-all (continued)


Define the two procedures product-all and remove-all-c described in the
previous exercise using deep-recur.

Exercise 7.28: filter-out-all


In a manner analogous to that used in Exercise 7.23, use deep-recur to define
the deeply recursive procedure f ilter-out-all-c. and then use it to define
filter-out-all.

Exercise 7.29: subst-all-m


The procedure subst-cQl-m was described in Exercise 7.15. Define it using
deep-recur.

228 Abstracting the Structure of Procedures


Exercise 7.30: reverse-all
The procedure reverse-all was defined in Program 4.10. Define it using
deep-recur.

Exercise 7.31: flat-recur


Define flat-recur using deep-recur.

Exercise 7.32: deep-recur


Define deep-recur using flat-recur. Hint: Use letrec.

7.5 Procedural Abstraction of Deep Recursion 229


8 Sets and Relations

8.1 Overview

Sets play a fundamental role in the development of mathematics and logic.


In this chapter, we show how sets may be introduced as a data structure in
Scheme. We first define various procedures that give information about how
many elements satisfy certain given conditions. These procedures are called
quantifiers. We then present an implementation of set theory; that is, we
define sets as a data type and develop the usual set operations. In the last
section, we apply sets to a discussion of functions and relations. Throughout
the discussion, we make use of the fact that procedures are first-class objects.
We use them as values and pass them as arguments.

8.2 Quantifiers

We study various procedures in this section that we shall use later in our

discussion of sets. If we are given two items, these procedures are used to
tell whether both, at least one, or neither of the items satisfy some condition.
In a sense, they give an idea of how many of the items satisfy the condition;
hence these procedures are called quantifiers. In this section, we introduce the
quantifiers both, at-least-one, and neither; after sets have been introduced
in the next section, we add to this list the quantifiers f or-all, there-exists,
and none.
The first of these is a procedure both that has a predicate pred as its

parameter and returns another predicate that has two parameters, ajrgl and
arg2. It is true if and only if pred is true for both argl and arg2. It is easy
to write the definition of both:

Program 8.1 both

(define both
(lambda (pred)
(lambda (zirgl arg2)
(emd (pred argl) (pred arg2)))))

For example, if we want to test whether two lists are both nonempty, we
can invoke the predicate

(both (lambda (Is) (not (null? Is))))

on the two lists. Then

((both (lambda (Is) (not (null? Is)))) ' (a b c) '(d e)) => «t

Incidentally, the predicate in this case can also be written as

(both (compose not null?))

Thus

((both (compose not null?)) '(a b c) '(d e)) =^ #t

We similarly define a procedure neither that has as parameter a predicate


pred. It returns another predicate that has two parameters, argl and eu:g2.
Its value is true if and only if neither (pred argl) nor (pred arg2) is true.

Here is its definition:

Program 8.2 neither

(define neither
(Icunbda (pred)
(leimbda (argl 2u:g2)
(not (or (pred argl) (pred arg2))))))

Thus

232 Sets and Relations


((neither null?) '(a b c) '(d e)) => «t

Another useful procedure is at-least-one, which has as its parameter a


predicate pred. It returns another predicate that has two parameters, aurgl
and eLrg2. Its value is true when either (pred argl) or (pred arg2) is true.
Below is its definition:

Program 8.3 at-least-one

(define at-least-one
(lambda (pred)
(lambda (argl arg2)
(or (pred argl) (pred arg2)))))

Here is how it works:

((at-least-one even?) 1 3) ^^ #f
((at-least-one even?) 1 2) ^=»» #t

We can play with logic a little to show that we can take one of these three
procedures as basic and express the other two in terms of it. For example, let

us take neither as the basic one and try to express both and at-least-one
in terms of neither. Given two lists, if we want to say that both lists are
empty, we can also say that neither of the lists is not empty. In general, saying
that both items satisfy a predicate is the same as saying that neither of them
does not satisfy the predicate. Thus we can write

(define both
(lambda (pred)
(leunbda (aurgl axg2)
((neither (lambda (arg) (not (pred arg)))) «irgl arg2))))

Suppose we have a procedure definition of the form

(define name (lambda (argl ...) (proc argl ...)))

where corresponding arguments are exactly the same in both places. We can
often simplify the definition to be

(define name proc)

8.2 Quantifiers 233


since in both versions, iname x . . .) is the same as iproc x . . .). For example,
this simplificatton rule tells us that the expression

(lambda (a Is) (cons a Is))

evaluates to a procedure that behaves the same as cons.


Observe next that we can rewrite the expression

(lambda (arg) (not (pred arg)))

which appears in the last line of the definition of both, to be

(lambda (arg) ((compose not pred) arg))

which, according to the above simplification rule, has the same value as

(compose not pred)

We can therefore rewrite the definition of both to be

(define both
(lambda (pred)
(lambda (eirgl eurg2)
((neither (compose not pred)) eurgl arg2))))

We can apply the simplification rule again to see that

(lambda (curgl eurg2) ((neither (compose not pred)) ar^l arg2))

is the same as

(neither (compose not pred))

so we obtain the final version of both to be

(define both
(lambda (pred)
(neither (compose not pred))))

This kind of simplification was possible because we curried out the parameter
pred when we decided on the form of the definitions. This kind of simplifi-
cation actually leads to a definition that reflects the way we verbalize it in

234 Sets and Relations


English. We said above that both satisfy the predicate when neither does not
satisfy it. We see here another advantage of currying.

In a similar manner, we can show that at-least-one can be defined in


terms of neither by

(define at-least-one
(lambda (pred)
(lambda (eirgl arg2)
(not ((neither pred) argl arg2)))))

This says that at least one of the two satisfies the predicate when it is not
true that neither satisfies the predicate.

Exercises

Exercise 8.1
Show that the procedure both can be taken as the basic quantifier and that
the other two, neither and at-least-one, can be defined in terms of both.

Exercise 8.2
Start with at-least-one as the basic quantifier, and define neither and both
in terms of at-least-one.

Exercise 8.3: equal?


Use the procedures of this section to write a definition of the Scheme predicate
equal? that tests whether two expressions are the same. If neither of the ex-
pressions is a pair, it uses eqv? to test their equality. Otherwise it recursively
tests the car and the cdr of the expressions until it can use eqv?. Test your
predicate on

(equal? '(a (be (d e) f )) ' (a (b c (d e) f ) )) => #t


(equal? '(a ((b d) c) e) ' (a (b d) c e)) ==> «f
(equal? '(a ((b d) c) e) '(a ((d b) c) e)) ==> #f

8.2 Quantifiers 235


8.3 Sets

In this section, we develop a data type called sets. We treat sets as an abstract

data type that has certain basic operators whose existence we first assume and
later implement when we choose a representation of sets. An implementation
of sets must begin with the specification of the kinds of objects that are
allowed as the basic building blocks. The collection of all such base objects is

called the universe of discourse or simply the universe.


The objects contained in a set are referred to as elements or members of the
set. In talking about sets, we use the notation of the mathematical logicians
and enclose the elements of sets in braces. Thus a set containing the elements
and c is written as
a, b, {a, b, c}. A set may contain other sets as elements, as
illustrated by the set {{a, b}, {b, c}, {d, e}}, which contains the three elements
{a, 6}, {6, c}, and {d, e}. There is a set called the empty set that contains no
elements. Logicians write it as 0.
Either an element is in a set or it is not; it makes no sense to speak of
multiple occurrences of an element in a set. This is an important distinction
between lists and sets, since (a a) and (a) are two different lists, while we
never write {a, a}, since {a} is the set containing the element a, and repetition
of the element a is superfluous. Furthermore, the order in which the elements
of a set are written is immaterial. Thus {a, b, c} and {6, c, a} represent the
same set, whereas (a b c) and (b c a) represent different lists.

We now consider how we implement sets. We denote the empty set by the-
empty-set. There is a predicate to test whether a set is empty: empty-set?.
We also use a predicate set? that tests whether an object is a set. The base
elements of our sets belong to some universe for which there is a sameness
predicate, which we assume is equal?.
We now introduce the selectors and a constructor for use with sets. There
are two selectors, pick and residue. The selector pick takes a set as its

argument and returns an element of that set. Since order of elements is not
meaningful in sets, we cannot say that pick selects the first member of a set.
If obj is an object and s is a set, then the invocation ((residue obj) s)
returns a set that contains all of the elements of s except obj . If obj is not
in the set s, then ((residue obj) s) returns the set s. The constructor
is called adjoin. If obj is an object and s is a set, then (adjoin obj s)
returns a set that contains obj and all of the members of s. With these basic
operators, we now proceed to develop additional operations on sets.

236 Sets and Relations


We begin with the definition of a procedure make-set that takes any num-
ber of arguments and returns a set containing those arguments as elements.
Thus the invocation (meOce-set 'a 'b 'c) returns the set {a,b,c}. We use
the unrestricted lambda to define this procedure that takes an arbitrary num-
ber of arguments. If there are no arguments, the-empty-set is returned.
Otherwise we apply meike-set to all but the first of the arguments and then
add the first to that set using the constructor adjoin. This is just our usual
flat recursion when using the unrestricted lambda.

Program 8.4 msdce-set

(define meike-set
(lanbda args
(letrec
( (list-meJce-set
(Icunbda (args-list)
(if (null? args-list)
the-empty-set
(adjoin
(car eirgs-list)
(list-make-set (cdr args-list)))))))
(list-meJte-set args))))

In Section 8.2, we introduced the three quantifiers both, at-least-one, and


neither, each of which took two arguments. We now define analogs of these
that take a set as their argument. The analog
neither is the quantifier
of
none, the analog of at-least-one is the quantifier there-exists, and the
analog of both is the quantifier f or-all. We start with the procedure none
that takes a predicate pred as its parameter. It returns a predicate that has
a set s as its parameter and is true when pred is false for all elements in s.
For example,

(let ((s (make-set 2 4 6 8 10 12)))


((none odd?) s)) => «t
(let ((s (make-set 12 3 4 5 6)))
((none odd?) s)) =^ «f

Here is the definition of none:

8.3 Sets 237


Program 8.5 none

(define none
(laitbda (pre i)
(letrec
((test
(leunbda (s)
(or (empty--set? s)
(let ((elem (pick s)))
(and (not (pred elem))
(test , ((residue elem) s))))))))
test)))

Again, if pred is a predicate and s is a set, the expression

((there-exists pred) s)

is when there is at least one element in s for which pred is true. We can
true
define there-exists in terms of none using the fact that there is an element
of s satisfying pred only when it is not the case that none of the elements of
s satisfies the predicate.

(define there-exists
(lambda (pred)
(Icuabda (s)
(not ((none pred) s)))))

The expression in the last line of this definition of there-exists can be


written as

((compose not (none pred)) s)

Then the simplifying rule of Section 8.2 can be used to give us the following
form of the definition:

Progrfon 8.6 there-exists

(define there-exists
(lambda (pred)
(compose not (none pred))))

238 Sets and Relations


The procedure lor-all is called with the call structure ((lor-all pred)
s) and is true only when pred is true for all of the elements in s. We can
again express f or-all in terms of none if we observe that pred is true for all

of the elements in s only when there are no elements in s for which pred is

not true. Thus we can write

(define f or-all
(lambda (pred)
(lambda (s)
((none (lambda (x) (not (pred x)))) s))))

Once we can use compose in the expression


again, in the last line and the
simplifying rule to give Program 8.7.

Program 8.7 f or-all

(define for-all
(leUDbda (pred)
(none (compose not pred))))

We can test for the sameness of objects or sets with a sameness procedure
called set-equal. We define set-equal so that it can be used to test for the

equality of both elements of sets and sets themselves. We use set-equal in a


curried form, so that if objl and obj2 are objects, then ((set-equal objl)
obj2) is true when objl is the same as obj2. You may have expected the
name set-equal? instead of set-equal. We use set-equal because when it
is passed an operand, its value is a procedure and not a truth value. We can
define the predicate set-equal? in terms of set-equal by writing:

(define set-equal?
(lambda (objl obj2)
((set-equal objl) obj2)))

If objl and obj2 are base elements of a set, they belong to a universe, and
their sameness can be tested with the predicate equal?. On the other hand, if

the objects are sets, we use the criterion of sameness used in set theory. First,

it says that a set 5 is a subset of a set T if each element of S is also an element


of T. Then if 5 is a subset of T and T is a subset of S, the two sets must
contain exactly the same elements and hence are equal sets. The definition

8.3 Sets 239


Program 8.8 set-equal

(define set-equal
(lambda (objl)
(lambda (obj2)
(or (and ((neither set?) objl obj2)
(equal? objl obj2))
(and ((both set?) objl obj2)
((subset objl) obj2)
((subset obj2) objl))))))

of set-equal makes mutually recursive use of the procedure subset, which


is defined later. Program 8.8 shows the code for set-equal.
We now define a procedure element that tests whether an object is an
element of a given set. If the object is denoted by obj and the set is denoted
by s, then ((element obj) s) is true when obj is an element of s. We have
again chosen to curry the procedure because it simplifies some of the programs
that make use of it. We want to see whether there is an element in the set

s that is equal to obj. We thus want to use the quantifier there-exists


with an argument that tests whether a given member of the set s is equal to
obj. But (set-equal obj) is precisely that predicate, for it tests whether its

argument is same as obj This is a good illustration of how currying can


the .

help us. If we had the ordinary predicate set-equal?, with two arguments,
we wouldn't be able to create the obj-specific version so easily. We'd have to
say (lambda (s) (set-equal? s obj)) instead, and that's harder to read.
We then have

(define element
(lambda (obj)
(lambda (s)
((there-exists (set-equal obj)) s))))

Using the simplifying rule of Section 8.2, we may rewrite the definition of
element to be

(define element
(leunbda (obj)
(there-exists (set-equal obj))))

We now see that the last line is merely the composition of the two procedures,

240 Sets and Relations


there-exists and set-equal. We can then rewrite the definition of element
to be

Program 8.9 element

(define element (compose there-exists set-equal))

These steps element again show us how the use


in rewriting the definition of

of currying can enable us to express our ideas in more compact and convenient
form.
The invocation ((element obj) s) tests for the set-theoretic relation

obj G s

which says that the object obj is a member of the set s. The set-theoretic
relation
s 3 obj
which says that the set s contains the object obj as a member, is tested for
by the predicate (contains s). For example, b £ {a,b,c} and {a,b,c} 3 b.

Program 8.10 shows the definition of contains.

Program 8.10 contains

(define contains
(lambda (set)
(lambda (obj)
((element obj) set))))

A set si is a subset of a set s2 if each member member of s2.


of si is also a
This subset relation is denoted by si C s2. For example, {a, c} C {a, b, c, d}.
We also say that a set si is a superset of s2 if s2 is a subset of si. The superset
relation is denoted by si D s2. Thus {a,b,c,d} D {a,c}. We first define the
procedure superset such that if si and s2 are sets, then ((superset si)
s2) tests whether si is a superset of s2. We want to determine whether
all elements of s2 are contained in si. The predicate (contains si) tests
whether its argument is a member of si, so we can use it as the argument to
for-all to test whether all of the elements of s2 are contained in si. Thus
we get Program 8.11. We define the procedure subset using superset, as
shown in Program 8.12.

8.3 Sets 241


Program 8.11 superset

(define superset
(lambda (si)
(laabda (s2)
((for-all (contains sD) s2))))

Program 8.12 subset

(define subset
(lambda (si)
(lambda (s2)
((superset s2) si))))

The number of elements in a set is called the cardinal number of the set.
For example, the cardinal number of the set {a, 6, c, d} is 4, and the cardinal

number of the set {{a, b}, {c. d}, {e, /}} is 3, while each of the elements of this
set is itself a set with cardinal number 2. It is an easy matter to define the
procedure Ceurdinal, which determines the cardinal number of its argument

set. To do so, we use recursion on the elements of the set. The cardinal
number of the-empty-set is 0, which gives us our terminal condition. If the
set s is not empty, we pick out one element and compute the cardinal number

of the rest of the set. To get the cardinal number of s, we have to add 1 to
the cardinal number of the rest of the set. Here is the definition:

Program 8.13 ceirdinal

(define cardinal
(lambda (s)
(if (empty-set? s)

(let ((elem (pick s)))


(addl (cardinal ((res idue elem) s)))))))

The structure of this definition is typical of programs that perform recursion


over the elements of a set s. We pick an element out of s, then apply the
procedure to the rest of the set, and perform the appropriate operation on it

to get the result of applying the procedure to s.

242 Sets and Relations


Program 8.14 intersection

(define intersection
(leunbda (si s2)
(letrec
((helper
(lambda (si)
(if (empty-set? si)
the-empty-set
(let ((elem (pick si)))
(if ((contains s 2) elem)
(adjoin elen (helper ((residue elem) si)))
I

(helper ((residue elem) si))))))))


(helper si))))

The intersection of two sets si and s2 is the set consisting of those ele-
ments of si that are also elements of s2. The intersection of si and s2 is
denoted by si n s2. For example, {a, 6, c, d}n {&, d, e} = {b,d}. We define a
procedure intersection that returns the intersection of its two arguments.
This definition uses recursion on the elements of si. Since s2 is not affected
in each recursive call, we helper that has only the
define a local procedure
one parameter si. When si is empty, the intersection is the-empty-set.
Otherwise, we select an element elem from si and take the intersection of
the rest of the set si with s2. If elem is contained in s2, we adjoin it to the
intersection of the rest with si. Otherwise, we simply return the intersection
of the rest with si. The definition is given in Program 8.14.

The union of the sets si and s2 is the set consisting of all of the elements
that are either in si or in s2. It is denoted by si U s2. For example,
{a, b, c, d} U {b, d, e} = {a, b, c, d, e}. We define a procedure union that takes
two sets as arguments and returns their union. We again use recursion on the
set si. This time, when si is empty, the union is s2. This is our terminal
condition. The recursion proceeds as in the case of intersection, but now
we want to adjoin elem to the union of the rest of the set si with s2 when
elem is not contained in s2. Thus we get the definition in Program 8.15.
The difference between the sets si and s2 is the set consisting of those
elements of si that are not in s2. denoted by si \ s2. For example,
It is

{o, b, c, d}\ {b, d, e} = {a, c}. We define a procedure difference in a manner


similar to that used to define intersection and union. This time, when si
is empty, the-empty-set is returned. And when elem is not contained in s2,
it is adjoined to the difference between the rest of si and s2. This leads us

8.3 Sets 243


Program 8.15 union

(define union
(lambda (si s2)
(letrec
((helper
(lambda (si)
(if (empty-set? si)
s2
(let ((elem (pick si)))
(if (not ((contains s2) elem))
(adjoin elem (helper ((residue elem) si)))
(help er ((residue el em) si))))))))
(helper 5l))))

Program 8.16 difference

(define difference
(lambda (si s2)
(letrec
((helper
(leunbda (si)
(if (empty-set? si)
the-empty-set
(let ((elem (pick si)))
(if (not ((contains s2) elem))
(adjoin elem (helper ((residue elem) si)))
(helper ((residue elem) si))))))))
(helper si))))

to the definition of difference in Program 8.16.

The structural similarity of the definitions of intersection, union, and


difference is striking. This common structure is an obvious candidate for
procedural abstraction. The three programs differ in what set is returned

when the terminal condition is true. We call that set the base-set. And they
differ in the predicate that is applied to decide whether to adjoin the element
elem picked from the set si. We call that predicate pred. We use base- set
and pred as parameters to the procedure set-builder, which abstracts the
structure of the three preceding programs. (See Program 8.17.)

244 Sets and Relations


Program 8.17 set-builder

(define set-builder
(lambda (pred base -set)
(letrec
((helper
(lambda (s)
(if (empty-set? s)
base- set
(let ((elem (pick s)))
(if (pred elem)
(adjoin elem (helper ((residue elem) s)))
(helper ((residue elem) s))))))))
helper)))

Procedure base-set pred


intersection the-empty-set (contains s2)
union s2 (compose not (contains s2))
difference the-empty-set (compose not (contains s2))

Table 8.18 Base sets and predicates for abstraction

We can now rewrite the definitions of intersection, union, and differ-


ence using set-builder. Table 8.18 shows the values taken on by base-set
and pred in the definitions of these three procedures. With these correspon-
dences, we get

(define intersection
(lambda (si s2)
((set-builder (contains 82) the-empty-set) si)))

(define union
(lambda (si s2)
((set-builder (compose not (contains s2)) s2) si)))

(define difference
(lambda (si s2)
((set-builder (compose not (contains s2)) the-empty-set) si)))

8.3 Seta 245


Program 8.19 family-union

(define f aaily-union

( lambda (s)
(if (empty-set? s)
the-empty-set
(let ((elem (pick s)))
(union elem (family-union ((residue elem) s) ))))))

Program 8.20 family-intersection

(define family-intersection
(lambda (s)
(if (empty-set? s)
the-empty-set
(letrec
((f am-int
(lambda (s)
(let ((elem (pick s)))
(let ((rest ((residue elem) s)))
(if (empty-set? rest)
elem
(intersection elem (feui-int rest))))))))
(f am-int s)))))

If the set S has as its members other sets, we can ask for the union of the
member sets. The union of the sets that are members of the set S is called
the family union of S. We represent it symbolically by (JS. For example,
U{{a. b}. {b. c, (f), {a, e}} = {a, 6, c, d. e}. We define (in Program 8.19) a pro-
cedure family-union that takes as its parameter a set s whose elements are

sets and returns the union of all of the elements of s.

In a similar manner, the family intersection of a set S whose elements are


sets is the intersection of all of the elements of S. It is denoted by P|S and
is by P|{{a.6. c}, {a.c.e}. {a,6,c,/}} = {a, c}. We define (in Pro-
illustrated
gram 8.20) the procedure family-intersection that takes the parameter s,
which is a set whose elements are sets, and returns the intersection of all of
the sets in s.

Why family-intersection more complicated than family-union?


is In
family-union, the use of the-empty-set acts as an identity for union in

246 Sets and Relations


Program 8.21 set-map

(define set-map
(lambda (proc s)
(if (empty-set? s)
the-empty-set
(let ((elem (pick s)))
(adjoin (proc elem)
(set-map proc ((residue elem) s)))))))

the same way as acts as an identity for plus (see Program 7.9) and 1

acts as an identity for times (see Program 7.10). For intersection, there
is no computable identity. Moreover, the-empty-set acts as an annihilator
for intersection in the same way that acts as an annihilator for x. We
must avoid passing the-empty-set to intersection. This is accomplished
by terminating the recursion when we reach a set that contains a single set.
The next procedure we define before looking into how we represent sets
is set -map, which takes two parameters, a procedure proc and a set s. It

returns the set consisting of those elements that are obtained when proc is

applied to each of the elements of s. For example, if proc is the procedure


cardinal and {{a},{b,c},{d,e},{a,c, f}}, then (set-map proc
s is the set

s) evaluates to the set {3,2, 1}. Similarly, if s is { — 1,0, 1}, then (set-map
addl s) evaluates to the set {0, 1,2}. We define set-map in Program 8.21.

Suppose we have a list of objects and we want to convert it into a set


containing the same objects. All we have to do is use the procedure apply to
apply make-set to the list. Thus we define a procedure list->set as

Program 8.22 list->set

(define list->set
(lambda (Is)
(apply make-set Is)))

In a similar way, we can ask for a procedure that takes the elements of a set

and builds a list containing those elements. This is done by picking elements
out of the set and consing them onto a list. Program 8.23 shows how the
procedure set->list can be defined.
We have now included enough of the procedures for manipulating sets for

8.3 Sets 247


Program 8.23 set->list

(define set->list
(lambda (s)
(if (empty-set? s)
'()

(let ((elem (pick s)))


(cons elen I (set-->li8t ((res idue elem) s)))))))

you to get an idea of how to define set operations. We are now ready to
consider ways of representing sets. This is done in the next section.

Exercises

Exercise 8.4
In this section, we showed that there-exists and lor-all can be defined
in terms of none. Show that we could have taken there-exists as the basic
one and defined the other two in terms of show that we could
it. Similarly,
have taken f or-all as the basic one and defined the other two in terms of it.

Exercise 8.5: for-one


Consider the definition of the three-parameter procedure for-one, given be-
low. Its first parameter is a predicate, pred. Its second parameter is a proce-
dure of one argument, found-proc, and its third parameter is a procedure of
zero arguments, not-f ound-proc. It returns a procedure that takes a set s
as its parameter. If s is empty or if the predicate pred is false for all items in
s, then the procedure not-found-proc is invoked. If s contains an element
for which pred is true, then the procedure fo\md-proc is invoked on that
element.

(define for-one
(lambda (pred found-proc not-found-proc)
(letrec ((test
(lambda (s)
(if (empty-set? s)
(not-found-proc)
(let ((v (pick s)))
(if (pred v)
(found-proc v)
(test ((residue v) s) )))))))
test)))

248 Sets and Relations


Here is an example of how it works:

((for-one
(lambda (x) (> x 7))
(lambda (v) (+ v 8))
(lambda () "Not found"))
(make-set 2 4 6 19 21 7))

returns the value 27 (or possibly 29, depending upon which element was se-

lected first). Define there-exists and f or-all using for-one.

Exercise 8.6
Show, using a discussion analogous to that used with element in this section,

that we can also write the definition of superset as

(define superset (compose f or-all contains))

8.4 Representing Sets

We have now defined the set procedures that enable us to manipulate sets as
a data type. These definitions all depend upon the six basic representation-

dependent terms: the-empty-set, empty-set?, set?, pick, residue, and


adjoin. We now show how these can be defined.
We first specify that the universe contains only objects for which equal?
is the sameness predicate. The first representation that we use for a set of

elements is a tagged list of those elements. A tag is a unique string that is

placed in the car position of a pair which enables us to distinguish tagged


objects from other ones. We define the tag for sets to be:

(define set-tag "set")

We make this distinction in order to define the predicate set? which will

determine whether its argument is a pair and its car is that unique tag. For

example, we represent the set {a,b, c} as the tagged list ("set" a b c). In
this first representation, we allow repeated elements in the lists. However, if

an element occurs once in a list, that element belongs to the set represented
by the and any other occurrences of that element in the list are ignored.
list,

Thus the lists ("set" a b c) and ("set" a b a c b b) represent the same


set {a,b, c}. We divide the six basic definitions into two groups, the first

of which is used in both of our representations. This shared group includes

8.4 Representing Sets 249


Program 8.24 The shared basic definitions for sets

(define the-empty-set (cons set-tag '()))

(define empty-set?
(lanbda (s)
(eq? s the-eapty-set)))

(define set?
(leiHbda (<urg)
(and (pair? arg) (eq? (car arg) set-tag))))

(define pick
(Isjibda (s)
(let ((Is (cdr s)))
(if (null? Is)
(error "pick: The set is empty.")
(list-ref Is (random (length Is)))))))

the-empty-set, empty-set?, set?, and pick Program 8.24). We use


(see

the procedure random in defining pick in order to select some element from
the list. The procedure random takes a positive integer n as an argument and
returns some randomly selected integer k in the range < A: < n. We chose
to use a randomly selected element from the list rather than the car of the list
or any other specific element of the list in order to convey the idea that the
set is unordered. Random number generators are discussed in Footnote 1 of
Section 10.2.5 and the procedure described there is defined in Exercise 13.3.
The definitions in Program 8.25 of the remaining two basic procedures,
adjoin and residue, we allow repetitions
reflect the fact that in the repre-

sentation. We make use of the procedure remove (see Program 4.6).

Here are some examples of how the procedures behave using our represen-
tation that allows repetitions of items in the lists.

((set-equal (list->8et '(a b a b)))


(li8t->8et '(baa))) => «t
((element 'a) (make-set (li8t->set '(a))
(list->set '(a a))
(list->8et '(a a a)))) —* »f
((element 'a) (make-set (li8t->set '(a))
'a
(list->8et '(a a)))) =^ it

250 Seta and Relations


Program 8.25 adjoin, residue (Version I)

(define residue
(lambda (elem)
(lambda (s)
(let ((Is (remove elem (cdr s))))
(cond
((null? Is) the-empty-set)
(else (cons set-tag Is)))))))

(define adjoin
(lambda (elem s)
(cons set-tag (cons elem (cdr s)))))

(union (make-set 112 3 4)

(make-set 3 4 4 5 6 6))^ ("set" 12344566)


(intersection (make-set 12 3 3 4 5)
(make-set 3 4 4 5 6 7))^ ("set" 345)
(difference (make-set 112 3 3 4 5)
(make-set 3 4 4 5 6 7)) => ("set" 1 2)
(set-map cardinal (make-set (list->set ' (a b c))
(list->set '(a b a))
(list->set '(a a a))
(list->set '()))) => ("set" 3 2 10)
(f amily-inters9Ction (make-set (list->set '(a b c d d))
(list->set '(a c d e))
(list->set '(c d e f))))
=> ("set" c d)

Another representation for a set s is a list that contains the elements of s but
does not allow repetition of elements. The selector residue now has to remove
only the first occurrence of its first argument from the set since there are no
repetitions.Thus it uses remove-lst (Program 2.4) instead of remove. The
constructor adjoin must now test to determine whether its first argument,
the object, is already an element of its second argument, the set. It adds the
object to the set only if it is not already a member of the set. Program 8.26
shows the definitions of adjoin and residue for the representation with no
repetitions.

Here are some examples of how some of the procedures defined in Section 8.3
look when using the second representation of sets:

8.4 Representing Sets 251


)

Program 8.26 The basic definitions for sets (Version 11)

(define residue
(la>bda (elea)
(laabda (s)
(let ((Is (re«ove-lst elem (cdr s))))
'

(cond
((niill? Is) the-empty- set)
(else (cons set-tag Is )))))))

(define adjoin
(la»bda (el en s)
(cond
((e«ber? eles (cdr s)) s)
(else (cons set--tag (cons elem (cdr s) ))))))

(union (aake-set 12 3) (make-set 2 3 4)) ==» ("set" 1234)


(intersection (aake-set 12 3) (aake-set 2 3 4)) ^ ("set" 2 3)
(difference (make-set 1 2 3) (make-set 2 3 4))^ ("set" 1)

You now might ask. "Which representation is better?" Each has its advan-
tages. For example, in the first representation, the selector residue does
more work than its counterpart in the second representation since it has to
remove all occurrences of the element that was picked, while in the second
representation, it only has to remove the first occurrence. In the second rep-
resentation, adjoin does more work since it has to check whether the element
to be added is already in the tagged list. The lists involving no repetitions
represent the sets more compactly. Thus there is a trade-off when choosing
between these two representations.
We have built into our universe, not only symbols, numbers, and booleans.
but any data for which equal? works, and that includes lists. Here aire some
examples using this tagged-list representation of sets where some elements are
lists.

(union (make-set 1 '(1 2) '(2 3))

(make-set 1 2 ' (1 2) ' (3 4) )

=* ("set" (2 3) 1 2 (1 2) (3 4))
(union (make-set 1 (make-set 1 2) (make-set 2 3))
(make-set 1 2 (make-set 1 2) (make-set 3 4)))
=*• ("set" ("set" 2 3) 1 2 ("set" 1 2) ("set" 3 4))

252 Sets and Relattons


(family-union
(make-set
(make-set 1 2) (make-set 2 3) (make-set 3 4)))
=^ ("set" 12 3 4)

We have now seen another application of data abstraction in this devel-


opment of the set data type. We defined all of the set operations using six
basic definitions, and only these six depend upon the specific representation
of the sets that we use. We then showed how to define these six using two
different representations of sets. The extensive use we made of currying in the

definitions of many of the set operations made it possible to use composition


of procedures to simplify several definitions. It also enabled us more easily to
define new procedures in terms of others with certain arguments fixed. For
example, if we want to remove the number from sets of numbers, we can
apply (residue 0) to any such set of numbers and get the desired result. We
also saw another example of the abstraction of the structure of several proce-
dures in set -builder. In the next section, we shall apply sets to a discussion
of functions and relations.

Exercises

Exercise 8.7
Use this definition of pick to implement sets with lists having repetitions:

(define pick
(Icu&bda (s)
(car (cdr s))))

In the following exercises, use only operations on sets. Do not use operations
on sets that depend upon the representation of the sets.

Exercise 8.8
The procedures union and intersection defined in this section each took two
sets as arguments. Rewrite these definitions using the unrestricted lambda so
that both take an arbitrary number of sets as arguments. Test your procedures

on the following examples:

(union
(make-set 12 3 4)
(make-set 13 4 5)
(make-set 2 1)) ==> ("set" 12 3 4 5)

8.4 Representing Sets 253


(intersection
(ake-set 12 3 4)

(ake-set 13 4 5)
(ake-set 15 6 3 7))=^ ("set" 1 3)

Abstract the structure of these two definitions to get a procedure from which
tinion and intersection can both be obtained by passing the procedural
abstraction appropriate arguments.

Exercise 8.9: symmetric-dillerence


Define the set procedure symmetric-difference that has two sets si and s2
as parameters and returns the set consisting of those elements that are either
in si but not in s2 or in s2 but not in si. For example,

(synaetric-dif ference
(ake-set 12 3 4 5)
(ake-set 3 4 5 6 7)) =» ("set" 1267)

Exercise 8.10: power-set


Define a set procedure power-set that has a set s as parameter and returns
the set consisting of all subsets of s. For example,

(power-set (ake-set 'a 'b 'c))


=* ("set" ("set" a b c) ("set" a b) ("set" a c)
("set" a) ("set" b c) ("set" b) ("set" c) ("set"))

Hint: Assume that power-set is defined for the rest of the set when an element
is picked out.

Exercise 8.11: select-by-cardinal


Let s be a set whose elements are sets. Define a set procedure select-by-
cardinal that has an integer int as its parameter and returns a procedure
with parameter s that builds the set of all of those elements of s that have
cardinal int. For example,

((select-by-cardinal 2)
(ake-set (ake-set 'a) (aake-set 'a 'b) (ake-set 'a 'b 'c)

(ake-set 'b 'c) (ake-set 'b)))


-^ ("set" ("set" a b) ("set" b c))

254 Sets and Relations


8.5 Ordered Pairs, Functions, and Relations

As an application of sets, we show how ordered pairs and the Cartesian prod-
uct of two sets are defined and use these ideas to develop the logical concepts
of functions and relations. We present a development of ordered pairs based
upon the development of sets presented in the preceding sections.

An ordered pair is a pair of elements in which the order is significant; that is,

(x, y) and (y, x) represent different ordered pairs as long as x and y are not the
same. We again treat ordered pairs as an abstract data type and introduce
the basic operations that apply to ordered pairs and then look at possible
representations of ordered pairs. Ordered pairs have two selectors and one
constructor. There is a selector called op-lst that takes an ordered pair as
its argument and returns the first element in the ordered pair. Similarly, there
is a selector called op-2nd that returns the second element in the ordered pair.
Finally, there is a constructor that is called make-op such that (maie-op x
y) is the ordered pair containing x as its first member and y as its second

member. There is also a predicate op? that tests whether its argument is an
ordered pair.
We now consider ways of representing ordered pairs. We present three dif-
ferent representations in this section: sets, two-element lists, and dotted pairs.

Logicians usually start with sets and build other concepts from them. In our
first representation, we show how this can be carried out in Scheme. Com-
pared to the last two representations, the first is quite complicated. It shows
the natural advantage the list or dotted-pair representations have for repre-
senting ordered pairs. If you are not interested in the set theory development
of ordered pairs, you can skip over the next two paragraphs and go on to the
list and dotted-pair representations.
We first represent ordered pairs as sets. A naive first attempt would repre-
sent the ordered pair (x,y) with the set {x,y}. However, there is a problem:
if ^ y,
X then (x, y) 9^ (y, x), but {x, y} = {y, x} since order is immaterial in
sets. We can get around this difficulty by representing the ordered pair (x, y)
by the set {{x},{x,y}}. Then the ordered pair (y, x) is represented by the
set {{y}, {y, a:}}, which is not the same set as that used to represent (x,y),
as long as X is not the same as y. We identify the first element of the ordered

pair represented by {{x}, {x, y}} by noting that it is the only element in the
intersection {x} D {x, y} of the two member sets. Similarly, if x and y are not
equal, the second element of the ordered pair is the only element in the set
difference between Uii^)' i^' I/}} ^^^ flii^)' {^' y))- ^^ ^ ^^^ V ^^^ equal,
pick the first element of the ordered pair. It should be observed that the same

8.5 Ordered Pairs, Functions, and Relations 255


)

Program 8.27 Basic definitions for ordered pairs (Version I)

(define nake-op
(lambda (i y)
(maOce-set (make-set i) (make-set x y))))

(define op?
(lambda (set)
(and (set? set)
((for-all set?) set)
(= (cardinal (family-intersection set)) 1)

(or (= (cardinal set) 1)

((both (lambda (i) (= (cardinal i) 2)))


set
(family-union set))))))

(define op-lst
(lambda (op)
(pick (family-intersection op))))

(define op-2nd
(lambda (op)
(let ((fam-int (family-intersection op) )

(let ((diff (difference (family—union op) fam-int)))


(pick (if (empty-set? diff) fam-int diff))))))

ordered pair is represented by {{z}, {x, y}}, {{z}, {y, z}}, and {{y, z}, {z}}.
Using this representation in terms of sets, the definitions of the four basic
procedures for ordered pairs are given in Program 8.27. Given the first element
X and the second element y of the ordered pair, the constructor maie-op
produces the ordered pair {{z}, {z,y}}. To understand the definition of op?,
observe that it is possible for the cardinal number of an ordered pair to be
equal to one. This is illustrated by {{a}, {a, a}}, which represents the ordered
pair [a, a). We have {{a}, {a, a}} = {{a}, {a}} = {{^}}i so its cardinal
number is one.
There are other ways of representing ordered pairs that we can also use.

For example, we can an ordered pair containing the elements x and y be


let

represented by (list x y). This is using a representation by proper lists.


Then we have the definitions given in Program 8.28.

Another representation that is also a reasonable one to use represents the


pair containing x and y as a dotted pair containing those two elements, that

256 Seta and Relations


Program 8.28 Basic definitions for ordered pairs (Version II)

(define make-op
(leuttbda (x y)
(list X y)))

(define op?
(lambda (Is)
(and (pair? Is) (pair? (cdr Is)) (null? (cddr Is)))))

(define op- 1st


(lambda (op)
(car op)))

(define op-2nd
(lambda (op)
(cadr op)))

Program 8.29 Basic definitions for ordered pairs (Version III)

(define make-op
(Izimbda (x y)
(cons X y)))

(define op?
(lambda (pr)
(pair? pr)))

(define op-lst
(lambda (op)
(car op)))

(define op-2nd
(lambda (op)
(cdr op)))

is, as (cons x y). We then have the definitions given in Program 8.29. The
definitions in Programs 8.28 and 8.29 can be simplified using the simplification
rule of Section 8.2. See Exercise 8.12.

The Cartesian product of the two sets Si and 5*2 is the set of all ordered pairs

8.5 Ordered Pairs, Functions, and Relations 257


Progrcmi 8.30 cartesian-product

(define caurtesiam-product
(lambda (si s2)
(if (empty-set? si)
the-empty-set
(let ((elem (pick si)))
(union (set-map (lambda (> (make-op elem x)) 82)
(cartesian-product (( residue elem) 81) 82))))))

(x,t/)with X ^ Si and y E 82- The mathematical notation for the Cartesian


product of Si and ^2 is Si x 52- For example, the Cartesian product of the
two sets {a, 6, c} and {d, e] is the set of pairs

{(a, d), (a, e), (6, d), (6, e), (c, d), (c, e)}

The set procedure that forms the Cartesian product of two sets is defined in
Program 8.30.
A relation R from a set X to a set Y is defined to be a subset of the
Cartesian product of the two sets X and Y Thus the relation R is
. a, set of
ordered pairs in which the first element is in X and the second element is

in Y. The empty set is also a relation having no elements. For example, if

X is the set (make-set a b c) and Y is the set (make-set 1), then the
following is a relation from X to Y:

(make-set (make-op 'a 0) (make-op 'a 1) (make-op 'c 1))

The domain of the relation R from X to V is defined to be the subset of X


consisting of all elements of X that appear as first elements of some ordered
pair in R. In our example above, the domain of the relation is the set {a,c}.
The range of the relation R is defined to be the subset of Y consisting of
all elements of Y some ordered pair in R.
that appear as second elements of
Given a relation rel, we can define procedures domain and range that return
the domain and the range of rel, respectively. (See Program 8.31.)
A bineory relation on a set 5 is a subset of the Cartesian product of S
with itself. For example, if bob, torn, and jim are members of the set boys,
then a binary relation is-older-thsoi-relation on the set boys is given by

258 Sets and Relations


Program 8.31 domain, range

(define domain
(laabda (rel)
(set-aap op-•1st rel)))

(define rzmge
(lambda (rel)
(set -map op-•2nd rel)))

(define is-older-than-relation
(make-set (make-op 'tom 'bob)
(make-op 'tom 'jim)
(make-op 'bob 'jim)))

We can define a predicate is-older-tham? that has as its two parameters two
members bl and b2 of the set boys and returns true if the ordered pair (maJce-
op bl b2) is an element of the binary relation is-older-than-relation.
For example, we can write

(define is-older-than?
(lambda (bl b2)
((contains is-older-than-relation) (make-op bl b2))))

Suppose we are given a relation rel from one set to another. We now write
the definition of a procedure subrelation/lst that builds a new relation
consisting of all pairs from the relation rel that have a given element as their

first elements. Using our example above, when we enter

((subrelation/lst is-older-than-relation) 'torn)

the subrelation consisting of the two ordered pairs starting with tom is re-

turned. See Program 8.32.

A function from a set X to a set Y is defined to be a relation from X to

Y in which no two ordered pairs with the same first elements have diff'erent

second elements. Since functions are relations, domain and range are already
defined for functions. This definition of a function as a set of ordered pairs
is we have been using throughout the book.
equivalent to the definition If

we have a function denoted by y = f{x) with x in the domain X and y in

the range Y, this is the relation consisting of the ordered pairs (x,y) sat-
isfying y = f{x). With this view of functions, the factorial function is the

8.5 Ordered Pairs, Functions, and Relations 259


Program 8.32 subrelation/lst

(define subrelation/lst
(lambda (rel)
(lambda (arg)
((set-builder
(lambda (x) ((set-equal (op-lst i)) arg))
the-empty-set)
rel))))

Program 8.33 function?


1

(define function?
(lambda (rel)
(or (empty-set? rel)
(let ((subrel ((subrelat Lon/lst rel) (op-lst (pick rel)))))
(and (= (cardinal subr<3l) 1)
(fiinction? (difference rta subrel)))))))

set {(0, 1), (1, 1), (2, 2), (3, 6), . .


.}. We now write the definition of a predicate
function? that tests whether a relation is a function. (See Program 8.33.) If
the given relation rel is nonempty, this procedure looks at the subset of all

ordered pairs in rel that have the same first element as some ordered pair
(pick rel) in rel. If the number of distinct second elements in this subset is

greater than 1, false is returned. Otherwise the procedure is repeated on the


relation obtained by removing that subset from rel. This process continues
until no more ordered pairs are left to test, in which case true is returned.
The value of a function / at an element x in the domain of/ is the second el-

ement in the ordered pair in / that has x as its first element. In Program 8.34,
we define a procedure value that takes a function fun as its parameter and
returns a procedure that takes as its parameter an element arg in the domain
of fun and returns the value of fun at airg.

Let us summarize what we have accomplished. In Chapter 1, we introduced


as a data type having the constructor cons and the two selectors car
lists

and cdr. Here we have developed sets as a data type having the constructor
adjoin and the two selectors pick and residue. Using these, we defined many
procedures that manipulate sets. We used lists in several representations of
sets. We then proceeded to define ordered pairs using sets. Ordered pairs
are another data type having the constructor make-op and the two selectors

260 Sets and Relations


Progrjun 8.34 value

(define value
(lambda (fun)
(Icu&bda (eirg)
(op-2nd (pick ((subrelation/lst fun) arg))))))

op- 1st and op-2nd. These were used to define relations and functions on
sets. It is interesting to observe that if we start with sets as our bcisic data

type, we can use sets as we did to define ordered pairs, and then we can use
ordered pairs as a representation of lists. For any two elements, x and y, we
define (cons x y) to be (meOte-op x y). We then define car to be op-lst
and cdr to be op-2nd. The empty list () is represented by the-empty-set.
Using these definitions of cons, car. cdr, and (), we can proceed to define
all of the procedures on lists that were defined in the earlier chapters. Thus
we have come full circle. We can take sets as our basic data type and develop
lists in terms of sets, or we can take lists as our basic data type and develop
sets in terms of lists.

We leave it to the reader to develop more of the theory of functions and


relations in the exercises. The many examples in this chapter should make
it clear how powerful a tool it is to be able to pass procedures as arguments
to other procedures and to be able to have procedures whose values are pro-
cedures. We have seen how convenient it is to be able to curry procedures.
All of this is possible because procedures are treated as first-class objects in
Scheme.

Exercises

Exercise 8.12
In Programs 8.28 and 8.29, we can rewrite the definition of op-lst as (de-
fine op-lst caoc) using the simplifying rule of Section 8.2. Redefine all of
the procedures in Programs 8.28 and 8.29 for which the simplifying rule is

applicable.

In the following exercises, use the set operations developed in this chapter.
Make your programs independent of the representation of sets being used.
Many of the problems in this list use the results of previous problems, so do
them in order.

8.5 Ordered Pairs, Functions, and Relations 261


.

Exercise 8.13: relation?


Define a predicate relation? that tests whether a set is a relation.

Exercise 8.14: inverse-relation


The inverse of an ordered pair (a, b) is the ordered pair {b,a). The inverse of
a relation R is when each ordered pair is
the relation obtained replaced by its

inverse. Define a procedure inverse-relation that takes as its argument a


relation and returns its inverse relation.

Exercise 8.15: one-to-one?


A function is called one-to-one if its inverse relation is also a function. Write
the definition of a predicate one-to-one? that tests whether a function is

one-to-one. See the preceding exercise.

Exercise 8.16: make-relation


A convenient way of defining a relation rel is to give an arbitrary number
of pairs (x y) that corresponds to the ordered pairs (make-op x y) in rel.
Thus (make-relation '(1 2) '(1 3) '(2 3)) corresponds to the relation

{(1, 2), (1, 3), (2, 3)}. Define the procedure make-relation.

Exercise 8.17: reflexive?


A binary relation R on a set S is called reflexive if for each x in S, the
ordered pair {x,x) is an element of R. Define a predicate reflexive? that
tests whether a given relation rel is reflexive.

Exercise 8.18: symmetric?


A binary relation i? on a set S is called symmetric if it is equal as a set to its

inverse relation. See Exercise 8.14. Define a predicate S3rmmetric? that tests
whether a given relation rel is symmetric.

Exercise 8.19: function- compose


Suppose that / and g are functions such that the range of g is a subset of
the domain of /. The composition of / with g is the function consisting of
all ordered pairs (z, y) with x in the domain of g and y in the range of / and
for which there exists an element z such that (z, z) E g and (z, y) £ f. Define
the procedure function-compose such that (function-compose f g) is the
composition of f with g. Your procedure should first test whether the range
of g is a subset of the domain of f

262 Sets and Relations


Exercise 8.20: relation-compose
If Q and R are binary relations on a set 5, then the composition of Q with R
is the relation composed of all ordered pairs (x, y) such that for some 2 G 5,
there exists an ordered pair (x,2) E R and an ordered pair {z,y) G Q- Define
the procedure relation-compose such that (relation-compose q r) is the
composition of the relation q with the relation r.

Exercise 8.21: transitive?


A binary relation i? on a set S is called transitive if the composition of R
with R'\s a. subset of R. Define a predicate transitive? that tests whether
a relation rel is transitive. See the preceding exercise.

(trEuisitive?
(make-relation '(1 2) '(1 3) '(1 4) '(2 3) '(2 4) '(3 4))) => #t
(transitive?
(make-relation '(0 0) '(1 1) '(2 2) '(3 3) '(4 4))) => #t
(transitive?
(make-relation '(1 1) '(12) '(3 2) '(2 1))) ==*> #f

Exercise 8.22: equivalence-relation?


A binary relation rel on a set S is called an equivalence relation if it is

reflexive, symmetric, and transitive. Write the definition of the predicate


equivalence-relation? that tests whether a given relation is an equivalence
relation. See Exercises 8.17, 8.18, and 8.21.

(equivalence-relation?
(make-relation '(0 0) '(1 1) '(2 2) '(3 3))) => #t
(equivalence-relation?
(make-relation '(0 0) '(0 1) '(1 0) '(1 1))) =» #t
(equivalence-relation?
(make-relation '(0 0) '(0 1) '(1 1) '(2 2))) => #f

8.5 Ordered Pairs, Functions, and Relations 263


Part 3

Managing State

What is change? If we think further about the dining experience of Part I's
introduction, changes took place. Eating left you full, lessened the world's
food supply, enriched the purse of the restaurant's proprietor, and depleted
your buying power. All of these are changes. The state of the world after you
left the restaurant changed. Managing state means that all the effects of a
change must be taken into account.
Part 3 is about combining the management of state with the style of pro-
gramming that we have so far developed. In Chapter 9,we introduce a new
data structure, the vector. A vector is like a list, except that we access it
with operations that use the elements' indices, which are nonnegative inte-
gers, instead of with operations that find the first element and the rest of

the elements. In addition to the formal operations on vectors, we introduce


an operation that permanently changes the contents of a portion of a vector.
We use this operation to show the role of such state-changing operations in
general in enhancing the efficiency of correct procedures.
In Chapter 10, we use changing of state to develop some efficient procedures
for sorting and searching data stored in vectors. In Chapter 11, we strengthen
your intuition about writing procedures that use state-changing operations
by introducing such operations over lists and local variables. This leads to
Chapter 12, where we build an object-oriented system by merging higher-order
procedures with state-changing operations. In Chapter 13, we use object-
oriented programming to build a gas station simulation.
9 Using Vectors

9.1 Overview

We have been using lists as our basic data type, most of the ap-
and for

plications we have had so far, lists have been adequate. They do have one
disadvantage that is apparent when we have a long list Is. Let's say it contains
1000 elements, and we want to know what the element with zero-based index
900 is. One way we can access that information is by applying cdr 900 times
and then applying car. That seems like a lot of work, so we use the Scheme
procedure list-ref defined in Program 3.7, which does the cdring for us,

and we invoke (list-ref Is 900). But the computer is doing just as much
work to access the 900th element of the list for us. It would be nice to have a
data type in which we could store elements and look directly into the 900th
(or any other) place and what is there. Being able to access any element
see
in a list using the same amount of computer resources is called random access

into the list. What we now have available to us in lists is sequential access, in
which we have to cdr from the beginning of the list to the desired element. In
this chapter, we study a data type called vectors. Like lists, vectors are used

to store data. We discuss several possible implementations of vectors, the last


of which will provide data storage with random access.

9.2 Vectors

In mathematics, a vector is a function defined on a set of integers, say from


to n— 1, which assigns to each such integer a value that is said to be the
element with that integer as its index. A mathematical representation of a
vector with three elements, say a with index 0, b with index 1, and c with

index 2, is (a, 6, c). The word vector is also used as a data type in Scheme that
associates an element with each integer from zero to some given number. The
elements stored in a vector can be data of any type — for example, numbers,
symbols, lists, or procedures. For the external representation of a vector with
elements a, b, and c, Scheme uses #(a b c). In general Scheme's external
representation of a vector is a sharp symbol, #, followed by the elements
enclosed in parentheses.
As with the other data types we have studied, we begin with certain basic
procedures in terms of which we define the rest of the procedures involving
vectors. The actual representation of the vectors and the basic procedures
will be defined in several ways after we develop the other procedures in a

representation-independent fashion. The first of our four basic procedures is


the predicate vector?, which tests whether its argument is a vector. The sec-
ond one is the procedure vector-length, which takes a vector as its argument
and returns the number of elements in the vector.
The selector for vectors is called vector-ref It has the call structure.

(vector-ref vec k), where vec is a vector and A; is a nonnegative integer


less than the length of vec. It returns the element in vec that has index k. To

illustrate the use of this selector, we define a procedure view (See Program 9.1

and Exercise 9.2.) that takes a vector and displays its external representation.
If vec is the vector with the elements 1, 2, 3, and 4, we have

(view vec) displays #(1 2 3 4)

The indices of the elements of the vector vec go through the range from zero
to one less than the length of the vector vec. Thus we locally define highest-
index to be one less than (vector-length vec). The local procedure loop
displays in order the elements of vec, each, except for the last one, followed
by a space. Thus the desired output is obtained by first displaying "#(",
then invoking (loop 0) to display the elements of vec, and finally displaying
")"• In our implementation, we assume that the value returned by a display
expression is suppressed, so the same is true of view.
The constructor vector-generator, which we use for vectors, is a curried
procedure with the call structure

((vector-generator gen-proc) size)

The operand size is a nonnegative integer that is the length of the vector
we are constructing. The generating procedure gen-proc is a procedure that
takes an index as its argument and returns the value to be associated with

268 Using Vectors


Program 9.1 view

(define view
(lambda (vec)
(let ((highest- index (subl (vector-length vec))))
(letrec ((loop (lambda (i)
(display (vector-ref vec i))
(if (< i highest-index)
(begin
(display " ")

(loop (addl i) ))))))


(display "«(")
(loop 0)
(display ")")))))

that index in the vector we are constructing. The index is any integer in the
range from zero to one less than size. When passed a generating procedure,
vector-generator returns a procedure. When that procedure is passed an
integer specifying the vector's length, it returns a vector having the specified
size and whose elements are determined by the generating procedure. As an
example of the use of vector-generator, we want to construct a vector of
if

length 6 having for each of its elements, we can write

[1] (view ((vector-generator (lambda (i) 0)) 6))


«(0 0)

Here are some additional examples:

[2] (view ((vector-generator addl) 6))


#(123456)
[3] (view ((vector-generator (leunbda (i) i)) 5))
«(0 1 2 3 4)
[4] (view ((vector-generator (lambda (i) '())) 4))
«(() ())
[5] (define squares (vector-generator (lambda (i) ( i i))))
[6] (view (squares 4))
«(0 1 4 9)
[7] (view (squares 6))
#(0 14 9 16 25)

Later we shall give ways of representing vectors and defining these basic
procedures. We now show how to define the other procedures that we need,

9.2 Vectors 269


making use of the four basic procedures. The first one is the Scheme procedure
neike-vector, which builds a vector of a prescribed size and fills all of its
elements with the same specified value. If a fill value is not given, all of the
elements of the vector are filled with something, say (), although this fill

value is not specified by Scheme. Thus we define the procedure make-vector


that takes either one or two arguments. Its first argument is always the size

of the vector we eire building, and the optional second argument is the value
we use to fill the elements of the vector. To accomplish the definition of a
procedure with an optional second argument, we use the unrestricted lambda
in its definition. Thus make-vector has as its parameter a symbol denoted by
surgs. We distinguish between the two cases by testing whether (cdr aurgs) is

empty. we construct a vector of length (car airgs) and fill it with ().
If it is,

Never rely upon this fill value in your programs because the Scheme procedure
make-vector does not specify the fill value if you do not include it as a second
argument. Thus you may be surprised to find the implementation providing
something other than ( ) and your program will not run correctly. If you
,

want to use the fill value in your program, specify it as the second argument
to make-vector, and that value is used to fill the vector that is constructed.
Here is the code for meJce-vector:

Program 9.2 make-vector

(define nake-vector
(lanbda args
(let ((fill-value
(if (singleton-list? args)
'()

(cadr args))))
((vector-generator (lambda (i) fill-value)) (car args)))))

A convenient way of building a vector with given elements is to start with


a list containing those elements and converting the list into a vector using
the Scheme procedure li8t->vector that takes a list Is as its parameter.
The size of the vector being created is then the length of Is, and the gen-
erating procedure is simply (lambda (i) (list-ref Is i)). We then get
Program 9.3. Because list-rel is doing a recursion from the beginning of the
list for each index i, this is a very inefficient way of defining list->vector.
We consider a more efficient definition later.

270 Using Vectors


Program 9.3 list->vector

(define li8t->vector
(lanbda (Is)
((vector-generator (laabda (i) (list-ref Is i))) (length Is))))

Another convenient way of building a vector with given elements is pro-


vided by the Scheme procedure vector, which takes an arbitrary number of
arguments and returns a vector having those arguments as its elements. The
length of the vector returned is the same as the number of arguments. Since
thenumber of arguments is arbitrary, we must use the unrestricted lambda.
The definition of vector is:

Progr€iin 9.4 vector

(define vector
(laabda surgs
(li8t->vector args)))

Here are some experiments illustrating the use of these procedures:

[1] (view (aake-vector 5))


#(() () ())
[2] (view (li8t->vector '
(1 2 3 (4 5 6))))
id 2 3 (4 5 6))
[3] (view (vector 'a 'b '(a b c)))
«(a b (a b c))
[4] (view (vector 6 'symbol 5))
«(6 syabol 5)

The length of a vector is fixed when it is defined. Suppose we have defined


a vector vec of length k, and we find that we need a longer vector, say one of
length n, that has its first k elements the same as those of vec. We say that the
new vector is an extension of vec of length n. We now use vector-generator
to define the procedure vector-stretch that takes as its parameters a vector
vec and a number nev-size and returns an extension of vec of length new-
size. Its code is in Program 9.5. Although we use the name vector-stretch,
the new vector may also be the same length as or shorter than the original
vector.

9.2 Vectors 271


Program 9.5 vector-stretch

(define vector-stretch
(lambda (vec new-size)
(let ((size (vector-length vec)))
(let ((gen-proc (lambda (i)
(if (< i size)
(vector-ref vec i)
'()))))
((vector-generator gen-proc) new-size)))))

If the extension of a vector vec is the same size as vec, the extension

is a copy of vec. Thus we define the procedure vector-copy as shown in

Program 9.6.

Program 9.6 vector-copy

(define vector-copy
(lambda (vec)
(vector-stretch vec (vector-length vec))))

The procedure vector-copy gives us a copy of its argument with none of


its elements changed. Suppose we want a copy of the vector vec with the
element with index k replaced by the value val. We can define a new copying
procedure that has the kth element changed, as shown in Program 9.7.

Program 9.7 vector-update

(define vector-update
(lambda (vec k val)
(let ((gen-proc (lambda (i)
(if (- i k)
val
(vector-ref vec i)))))
((vector-generator gen-proc) (vector-length vec)))))

We can now give another version of the Scheme procedure list->vector,


this time using vector-update. In this version, we first build a vector vec

272 Using Vectors


Program 9.8 list->vector

(define li8t->vector
(lambda (Is)
(let ((vec (ncJce-vector (length Is))))
(letrec
((convert (lambda (Is* v i)
(if (null? Is*)
V
(let ((new-v (vector--update v i (car Is*))))
!

(convert ( ;cdr Is*) new-v (addl i)))))))


(convert Is vec 0)))))

having the same length as the list Is that we are converting into a vector.
Then we form a loop using a letrec expression in which we start with the
vector vec and successively use vector-update to give us a new vector new-v
that has the appropriate element changed to the corresponding element in
the list. This new vector is passed to the local procedure convert, which
continues this process until all of the elements of vec have been updated. Its
definition is given in Program 9.8. We make an improvement in the procedure
list->vector later in this section. At that time, we shall eliminate the need
to pass the vector new-v as an argument to convert and shall produce an
0{n) version instead of (9(n^).
Suppose we have a vector vec and want to construct a new vector whose
elements are obtained by applying the procedure proc to the corresponding
elements of vec. To accomplish this, we use a vector analog of the list proce-
dure map. We call the vector version vector-map, and we define it by:

Program 9.9 vector-map

(define vector-map
(lambda (proc vec)
((vector-generator (lambda (i) (proc (vector-ref vec i))))
(vector-length vec))))

9.2 Vectors 273


For example,

[1] (vies (vector-map addl (vector 10 11 12 13)))


«(11 12 13 14)
[2] (vies (vector-map even? (vector 10 11 12)))
#(«t #f #t)
[3] (view (vector-map
(lambda (elem) (list 'a elem))
(vector 10 11 12 13)))
«((a 10) (a 11) (a 12) (a 13))

In the language used with vectors, one usually refers to numbers as scalars.
We call a vector a numerical vector if all of its elements are numbers. The
product of a scalar c and a numerical vector (oi, a2,...,an) is the vector
(cai, ca2, . .
.
The procedure multiply— by-scalau: takes as parameters
, ccLn)-

a scalar c and a numerical vector vec and returns their product. It is defined
by

Program 9.10 multiply-by-scalar

(define multiply-by-scalair
(lambda (c vec)
(vector-map (lambda (elem) (* c elem)) vec)))

We define an analog to vector-map, called vector-apply-elementwise-


to-both, for a binary procedure proc and two vectors of the same length,
which applies proc to the corresponding elements of the two vectors.

Program 9.11 vector-apply-elementwise-to-both

(define vector-apply-elementwise-to-both
(lambda (proc)
(lambda (vecl vec2)
(let ((gen-proc
(lambda (i)
(proc (vector-ref vecl i) (vector-ref vec2 i)))))
((vector-generator gen-proc) (vector-length vecl))))))

The sum of two numerical vectors vecl and vec2 of the same length is the

274 Using Vectors


vector whose elements are the sums of the corresponding elements of vecl and
vec2. We vec+ that adds two vectors by simply
define the vector operator
using vector-apply-elementwise-to-both with + as its operand. Similarly
we define the vector operator vec* that multiplies two vectors elementwise by
applying vector-apply-elementwise-to-both with * as its operand.

Program 9.12 vec+, vec*

(define vec+ (vector-apply-elementwise-to-both +))

(define vec* (vector-apply-elementwise-to-both *))

The use of vec+ and vec* is illustrated by:

[1] (view (vec+ (vector 13 5 7 9) (vector 97531)))


#(10 10 10 10 10)
[2] (view (vec* (vector 13 5 7 9) (vector 97531)))
«(9 21 25 21 9)

We now look at the problem of adding all of the elements of a numerical


vector. We first need the length of the vector, which we locally define to be
size. Then we set up a local recursion on the index, starting with index 0.

When the index reaches size, there are no more elements to add. We define

vector-sum to be:

Program 9.13 vector-sum

(define vector-sum
(lambda (vec)
(let ((size (vector-length vec)))
(letrec
( (helper
(lambda (i)
(if (= i size)

(+ (vector-ref vec i) (helper (addl i)))))))


(helper 0)))))

In a similar way, we define vector-product, which takes the product of

9.2 Vectors 275


the elements of a numerical vector:

Program 9.14 vector-product

(define vector-product
(lambda (vec)
(let ((size (vector-length vec)))
(letrec
((helper
(lambda (i)
(if (= i size)
1

(* (vector-ref vec i) (helper (addl i) ))))))


(helper 0)))))

Here are some examples:

(vector-sum (vector 13 5 7 9)) ^^ 25


(vector-product (vector 13 5 7 9)) ^ 945

It should occur to you when looking at the last two definitions that they are
very similar in structure, and that they are ideal candidates for abstraction.
Let's define a procedurevector-accumulate that abstracts the structure of
those two procedures. There are just two essential differences: the value re-
turned when the terminating condition is true, which we call the seed, and
we call proc. Then the defini-
the operator applied in the alternative, which
tion of vector-accumulate is given in Program 9.15. We can now rewrite the
definitions of vector-sum and vector-product using vector-accumulate:

(define vector-sum (vector-accumulate +0))

(define vector-product (vector-accumulate * 1))

We defined list->vector, but we have not yet defined the Scheme proce-
dure vector->list that produces a list with the same elements as a given
vector. But that is just a recursion on the index with the procedure cons and
the seed (). We can then use vector-accumulate to define vector->list
as shown in Program 9.16. Then

(vector->li8t (vector 1 2 3 4)) =* (1 2 3 4)


(vector->li8t (vector 'abc 3 4)) =* (abc 3 4)

276 Using Vectors


Program 9.15 vector-accumulate

(define vector-accumulate
(Isunbda (proc seed)
(lambda (vec)
(let ((size (vector-length vec)))
(letrec
((helper
(lambda (i)
(if (= i size)
seed
(proc (vector-ref vec i) (helper (addl i)))))))
(helper 0))))))

Program 9.16 vector->list

(define vector->list (vector-accumulate cons '()))

Suppose that the elements of the vector (15.50, 8.95, 12.00) represent the
price of items we want
buy and the elements of the vector (2, 5, 3) represent
to
the number of each of those items we want. The total amount of money we
spend on the purchases is 2 x $15.50 + 5 x $8.95 + 3 x $12.00 = $111.75.
We found it by taking the products of the corresponding elements in the two
vectors and then summing the products. This type of computation involving
two vectors is used so often that it is given a name. It is called the dot-product
of the two vectors. In general, if u is the numerical vector (ao, ai, On-i) . .
.
,

and V is the numerical vector (&0) &i) •••, ^n-i)) the dot product u • v of
u and V is the number ao^o + t^i&i + • • • + o,n-ibn-i- We already have a
procedure vec* that computes the vector whose elements are the products
same length. We also
of the corresponding elements of two vectors of the
have a procedure vector-sum that sums the components of a vector. The
composition of these two procedures gives us the dot product:

(define dot-product (compose vector-sum vec*))

where we use a more general version of compose that allows its second argu-
ment to be a procedure of arbitrarily many arguments. It is defined by

9.2 Vectors 277


)

(define compose
(laabda (f g)
(lambda args
(f (apply g args)))))

Although this is the dot product of two vectors, it is not an efficient way of
getting it. In the process of computing the dot product, the first procedure
applied, vec*, constructs a vector containing the products of corresponding
elements of the original two vectors. This is a rather costly and unnecessary
construction, since we can justmake one pass down the elements of the two
vectors and accumulate the sum of the products as they are formed. The
definition of dot-product using this process is

(define dot-product
(lambda (vecl vec2)
(let ((size (vector-length vecl))
(letrec ((loop (lambda (i)
(cond
((= i size) 0)
(else (+ (* (vector-ref vecl i)
(vector-ref vec2 i))
(loop (addl i))))))))
(loop 0)))))

An even more efficient way of computing the dot product of two vectors
uses an accumulator to store the intermediate sums. Its code is given in
Program 9.17.

9.3 Representing Vectors

We have gone a long way without discussing the actual representation of


vectors we use in the computations and the definitions of the basic procedures.
It is now time to address these questions. Since a vector is mathematically
characterized as a function from the index to the element with that index, we
can first look at a representation of a vector as a tagged pair. The car of the
pair is the tag vector-tag whose value is "vector".

(define vector-tag "vector")

The cdr of the pair is another pair whose car is the vector's length, and
whose cdr is a procedure. That procedure takes an index as a parameter and

278 Using Vectors


Program 9.17 dot-product

(define dot -product


(lambda (vecl vec2)
(let ((size (vector-length vecl)))
(letrec
((loop
(lambda (i ace)
(if (= i size)
ace
(loop (addl i)
(+ ace (* (vector--ref vecl i)
(vector--ref vec2 i))))))))
(loop 0)))))

returns the element of the vector with that index. We make the convention
that the indices are zero based. With these conventions, we define vector?
and vector-length, in Program 9.18.

Program 9.18 vector?, vector-length

(define vector?
(lambda (arg)
(and (pair? arg) (eq? (car arg) vector-tag))))

(define vector-length
(lambda (vec)
(car (cdr vec))))

The constructor vector-generator has the generating procedure gen-proc


as its parameter. It returns a procedure that has the vector's length size as
its parameter. That in turn returns the vector being constructed, which is a
tagged pair containing the size and the procedure gen-proc. The definitions

of vector-ref and vector-generator are given in Program 9.19.

A tagged pair containing a list in place of a procedure can also be considered


as a representation of a vector. This is the second representation of vectors
we develop. We only need to redefine vector-ref and vector-generator as
in Program As we mentioned in the Overview, the elements in such lists
9.20.

are accessed with list-ref vector-generator contains a loop that invokes


.

9.3 Representing Vectors 279


Program 9.19 vector-rel. vector-generator Version I

(define vector-ref
(lambda (vec i)
((cddr vec) i)))

(define vector-generator
(laaibda (gen-proc)
(laabda (size)
(cons vector-tag (cons size gen-proc)))))

Program 9.20 vector-ref vector--generat or Version II

(define vector-ref
(lambda (vec i)
(list-ref (cddr vec) i )))

(define vector-generator
(lambda (gen-proc)
(lambda (size)
(cons vector-tag
(cons size
(letrec
((loop (lambda (i)
(cond
((= i size) '())
(else (cons (gen- proc i)
(loop (addl i.))))))))
(loop 3)))))))
(

gen-proc on each pass and builds a list of the results. We now have

[1] (vector 100 200 300 400 500)


("vector" 5 100 200 300 400 500)
[2] (vies (vector 100 200 300 400 500))
#(100 200 300 400 500)

With these two representations of vectors, we have implemented the prop-


erties of vectors specified in the mathematical definition; that is. we have
associated an element with each index. However, we have not considered the

280 Ustng Vectors


!

other question raised in the Overview, random access. It is clear that the list

representation does not provide random access to the elements, for we have
to cdr down the list until we find the element with the desired index, and the
computer resources used in this cdring increase with the index. It may appear
as though the representation of the vector using a procedure that assigns an
element to each index gives true random access to the elements, but consider
the following situation where vectors are represented as procedures:

(let ((a (make-vector 4 5)))


(let ((b (vector-update a 1 10)))
(let ((c (vector-update b 2 20)))
(let ((d (vector-update c 3 30)))
(vector-ref d 0))))) =» 5

In order to compute (vector-ref d 0), we first invoke the procedure rep-


resenting the vector d with argument to find that it invokes the procedure
representing the vector c with argument 0, which in turn invokes the pro-
cedure representing b with argument 0, which in turn invokes the procedure
representing a with argument 0, which returns the value 5. If we had asked
for (vector-ref d 3), the value 30 would have been returned with just the

one procedure invocation. Thus, the resources needed to access the different
elements of d depend upon the indices of the elements.
We do not have to give up on random access because Scheme has an imple-
mentation of vectors that does provide it. The third representation we discuss
is that provided by Scheme. The external representation of a vector in Scheme
as a list preceded by a sharp sign, #, is how we have been displaying vectors
with the procedure view. Thus the vector with elements 20, 30, 40, and 50 is

written as #(20 30 40 50). This is a representation, not an expression that


evaluates to a vector. Like lists, vector constants must be quoted, so that

when we enter a quoted vector, we get

[1] '#(10 u (+ 2 3) "Mary")


#(10 u (+ 2 3) "Mary")
[2] (writeln '#(10 20 (+ 10 20) 40 50))
#(10 20 (+ 10 20) 40 50)
[3] (vector 10 20 (+ 10 20) 40 50)
#(10 20 30 40 50)

The vector-length, and vector-ref are pro-


basic procedures vector?,
vided by Scheme. Our fourth basic procedure, vector-generator, is not in
Scheme, but we can use the two Scheme procedures make-vector and vector-
set ! to define it. We have already discussed make-vector, but vector-set

9.3 Representing Vectors 281


is new. With the and procedure representation of a vector, when we want
list

to change an element with index k in a vector vec to the new value c, we


invoke (vector-update vec k c), which makes a copy of the vector with a
given element changed. The original vector vec still has its original elements,

and only the copy has the element with index k changed to c. With the proce-
dure vector-set we invoke (vector-set vec k c) and we do not create
!
, ! ,

a new vector, but instead we change the element with index k in vec to have
the value c. Thus vector-set is not a constructor, since it does not create a
!

new vector. Instead, we call such procedures mutators or mutation procedures


that cause a mutation or change in the original vector. Its call structure is

(vector-set! vec i obj), where vec is a vector, i is an index, and obj is

an object that becomes the element with index i in vec. The element that
previously had index i in vec is replaced with obj. The value returned by
an invocation of vector-set! is not specified, so do not use the returned
value since it depends upon the implementation of Scheme. Programs that
do use such values are not portable; that is, they cannot be used with other
implementations of Scheme. The purpose of an invocation of vector-set ! is

to change a vector, which is a side effect. As such, it can be used in begin


expressions where side effects are done. It is a convention in Scheme to place
an exclamation mark, ! , at the end of the names of mutation procedures. The
exclamation mark is read as "bang," so we read vector-set! as "vector set
bang." We follow our convention of not displaying whatever is returned by
As with define, writeln, and the
side-effecting procedures. others, we shall
not display what a vector-set expression returns. !

As an example of the use of vector-set consider !


,

[1] (define vl (vector 2 4 6 8))


[2] vl
#(0 2 4 6 8)

[3] (vector-set! vl 2 5)
[4] vl
#(0 2 5 6 8)

we now present the definition of our


Using vector-set !, basic constructor
vector-generator. The program for vector-generator first constructs a
vector vec of the desired length size (> 0), with unspecified elements. Then
it enters a loop with index i going from to size. For each i less than
size, it changes the ith element of vec to be the generating procedure gen-
proc applied to i. When the index i reaches its upper limit size, the vector
vec has had all of its entries changed, and since the if expression has only a
consequent, some value (unspecified in Scheme) is returned. But this value is

282 Using Vectors


! !

ignored, since the letrec expression is the first expression in an implicit begin
expression. The value returned by the whole begin expression is the vector
vec. The program makes use of the fact that the vector
structure of this
vec is effects. The mutation is done in the loop within the
changed by side
letrec expression, and when its work is finished, the vector vec is returned.

Program 9.21 contains the code for vector-generator.

Program 9.21 vector-generator

(define vector-generat or
(lambda (gen-proc)
(lambda (size)
(let ((vec (make--vector size)))
(letrec
((loop (leunbda (i)
(if (< i size)
(begin
(vector-set vec i (gen--proc i))
(loop (addl i)))))))
(loop 0))
vec))))

When we use vector-set !


, we are interested in its side effects, and we do
not use the value that it returns. If we do want to use the updated vector that
has been reset by an invocation of vector-set we can use a mutating version!
,

of vector-update, which we call vector-update It first uses vector-set ! .

to set the element with the given index to a new value and then returns the
vector. It is defined in Program 9.22.

Program 9.22 vector-update!

(define vector-update!
(lambda (vec i c)
(vector-set! vec i c)
vec))

When we presented the second definition of list->vector, we mentioned


that we would give another version in which it would not be necessary to pass

9.3 Representing Vectors 283


the newly created vector as an argument to the local procedure convert. We
can now do it using vector-set ! . We first create a vector having the same
length as the list using make-vector. Then we cdr down the list, changing
the entry in the vector to be the corresponding entry in the list. The code is

in Program 9.23.

Program 9.23 list->vector


i

(define list->vector
(lambda (Is)
(let ((vec (make-vector (length Is))))
(letrec
((convert
(lambda (Is i)
(if (not (null? Is))
(begin
(vector-!set! vec i (car Is))
(convert (cdr Is) (addl i)))))))
(convert Is 0))
vec)))
!

In this program, we again see that vector-set ! is used in a begin expression


for its side effect of changing an element in a vector. The mutation of the
vector is ax:complished in the body of the local procedure convert. When the
letrec expression is finished, the vector vec is returned.
When mutation is introduced, we must adopt a different point of view about
computing than when we use functional programming. Programs that make
use of mutation are referred to as imperative- style programs. In functional
programming, an object is passed as an argument to a procedure and a new
object is created, but the original one does not change. If we start with a
vector a, which is (vector 2 4 6), and let b be (vector-update a 1 5),
then the vector a has not been changed, and we have

[1] (let ((a (vector 2 4 6)))


(let ((b (vector-update a 1 5)))
(vies a) (newline)
(view b) (newline)))
«(2 4 6)
«(2 5 6)

On the other hand, if we use the mutator vector-update! instead of

284 Using Vectors


.

vector-update, we actually do change the vector a, and we have

[2] (let ((a (vector 2 4 6)))


(let ((b (vector-update! a 1 5)))
(writeln a)
(writeln b)))
#(2 5 6)

#(2 5 6)

In the first let expression in [2] , the vector a has a certain state that is

determined by its elements. When vector-update! is invoked in the second


let expression, the state of a is changed. When objects change over time, we

say that at any given time the object is in a certain state that is determined by
certain state variables (in our example, they are the elements of the vector).
If we know the state of an object, we know its behavior at that time. When
using mutators, one must be conscious of the fact that each object has a
certain state and that an invocation of a mutation procedure causes a change
in the state of the object. We shall introduce other mutators in Chapter 11
and discuss the changes in state they cause. At this point, we give another
illustration comparing programming in functional style using vector-update
with programming in imperative style using the mutator vector-update !

We next look at the problem of reversing the elements of a vector. To do so,

we define a procedure vector-reverse that has a vector vec as its parameter


and returns a vector having the same elements as vec but in reverse order. We
first define a vector-reversing procedure in functional style without mutation.
We use either of the first two representations of vectors (either as tagged

procedures or as lists).The idea that we use in writing this definition is to


use two indices, i and j. The lower index i starts at 0, and the upper index
j starts at the index of the last element. The elements with indices i and
j are swapped, and then i is increased by 1 and j is decreased by 1. The
swapping and changing indices continue until either the two indices coincide
(this happens when the length of the vector is odd) or until i is greater than

j (this happens when the length of the vector is even). The resulting vector is

returned. The swapping is done with a helping procedure called swap-maker


that has a vector vec as its parameter and returns a procedure that has two
indices indexl and index2 as parameters and returns a copy of vec that has
its elements with indices, indexl and index2, interchanged. The code for

vector-reverse is in Program 9.24.

We now give the definition of swap-madter in Program 9.25. We first store


the element with index indexl in a local variable temp. Then vector-update
is invoked on vec and returns a new vector having its element with index

9.3 Representing Vectors 285


)

Program 9.24 vector-reverse (functional version)

(define vector -reverse


(lEunbda (vec.>
(letrec
((switch
(lambda (v i j)
(if (>= i j)
V
(let ((swapv (swap--maker v) )

(switch (swapv i j) (addl i) (subl j)))))))


(switch vec (subl (vector--length vec))))))

Program 9.25 swap-maker (functional version)

(define swap-maker
(lambda (vec)
(lambda (indexl index2)
(let ((temp (vector-ref vec indexl)))
(vector-update
(vector-update vec indexl (vector-ref vec index2))
index2
temp)))))

indexl changed to the element with index inciex2. Now vector-update is

again invoked, this time on the vector that was returned. It returns a new
vector that has its element with index index2 changed to temp, completing
the swap.
We illustrate the use of vector-reverse with the following experiment:

[1] (let ((a (vector 12 3 4 5)))


(let ((b (vector-reverse a)))
(view a) (newline)
(view b) (newline)))
#(12 3 4 5)
#(5 4 3 2 1)

We now write the definitions of corresponding procedures with mutations.


Program 9.26 gives the code for vector-reverse!. We are able to make
a few optimizations in the program, taking advantage of the fact that the
vectors have state that changes with invocations of the mutators. Now we

286 Using Vectors


do not have to pass the vector vec as an argument to the local procedure
switch and switch does not have to return a value. We can also define the
local procedure swapv! before the letrec expression defining switch. This
time, the helping procedure (swap-maker vec) (see Program 9.27) returns a
mutator that uses vector-update! actually to interchange the elements with
indices indexl and index2 in the vector vec itself. When switch finishes its
work, the vector vec has its elements in reverse order. After the invocation
of switch, the altered vector vec is returned as the value of the procedure
vector-reverse!. Thus using mutation reduces the need to pass vectors as
arguments to procedures and for procedures to return vectors as values. In
general, mutation provides for efficient communication between procedures
and faster running, more efficient programs.
We repeat the experiment, this time using the mutating version:

[2] (let ((a (vector 12 3 4 5)))

(let ((b (vector-reverse! a)))


(writeln a)
(writeln b)))
#(5 4 3 2 1)

«(5 4 3 2 1)

Notice that this time, the invocation of vector-reverse! on the vector a


actually changed the vector a itself, whereas in the previous version not in-
volving mutation, the vector a remained unchanged.

Program 9.26 vector-reverse ! (imperative version)

(define vector-reverse!
(launbda (vec)
(let ((sHapv! (swap -maker vec)))
(letrec
((switch (lambda (i j)
(if « i j)
(begin
(swapv! i j)
(switch (addl i) (subl j)))))))
(switch (subl (vector- length vec))))
vec)))

9.S Representing Vectors 287


!

Program 9.27 swap-maJter (imperative version)

(define swap-maker
(lambda (vec)
(lambda (indexl index2)
(let ((temp (vector-ref vec indexl)))
(vector-update
(vector-update! vec indexl (vector-ref vec index2))
index2
temp)))))

Comparing these two versions of vector-reversing procedures, we see that


when we are willing to abandon the original elements occupying various posi-
tions in a vector, we may use the same vector and update individual positions.
The values associated with the indices are changing, but they are in the same
vector, not a copy of the vector. Imagine a vector of length N to be a set
of N transparent shoe boxes fastened together at the sides and as elements
use billiard balls with values written on them. Then when we use vector-
update ! , we open the lid, take out the ball that is in the box, and replace that
ball with a different ball. We always use the same shoe boxes. The vector
itself is not changing, but its The shoe boxes did not change;
contents are.
only their contents did. By using vector-update! we have shown that the
communication structure associated with vectors as arguments and values can
be lessened. What makes mutation really important, however, has to do with
random access and the way virtually all computers are designed. Random ac-
cess provides constant time\ that is, the time required to access any element
in the vector is the same. Sequential access in lists provides linear time in
which the time is proportional to the index of the element. Thus we see that
using mutation considerably improves the running time of programs.
In summary, we have now seen three approaches to implementing vectors.
In the first two representations, mutations are not used. When we want to
change an element with a given index in a vector, we use vector-update,
which constructs a new vector that has the new element, and the original
vector is left unchanged. Such programming without mutations is called func-
tional programming. In our third approach we use the mutator to vectors,
vector-set to change an element with a given index in a vector, and this
!

actually makes the changes in the original vector itself so as to change the
state. Such programming with mutations is called imperative programming.

We gain using mutation because the way it is implemented in Scheme gives

288 Using Vectors


.

us random access to the elements in the vector. The price we pay is that we
actually change elements in the original vector when we use vector-set!,
so we need it for some reason after the invocation of vector-set
if we !
,

must first make a copy of the original vector using vector-copy and apply

the mutation procedures to the copy. The vector data type has analogs in
other programming languages where they are often called one-dimensional ar-
we state otherwise, we use the Scheme
rays. In the rest of this book, unless
implementation of vectors with the mutator vector-set !

Exercises

Exercise 9.1: successive-powers


Define a procedure successive-powers that constructs a vector of length n
whose entries are the successive powers of the number base. The element with
index is the 0th power of base. The procedure should be curried so that its

first parameter is base, and the procedure that is returned has parameter n.
Test your procedure on

((successive-powers 2) 8) ^^ #(1 2 4 8 16 32 64 128)


((successive-powers 3) 5) ^^ #(1 3 9 27 81)

Exercise 9.2
A vector of zero length can be displayed by #(). Rewrite the definition of
view to support vectors of zero length.

Exercise 9.3: vector-view


We defined a procedure view when we used the procedural representation of
a vector to display the vector in the notation using a sharp sign followed by a
list of elements. Define a procedure vector-view that works like view, except
that it displays the vector using angle bracket notation with commas as used
in mathematics. Take into account the observation of Exercise 9.2. With all

three representations of vectors given in this chapter, (vector-view (vector


10 20 30)) should display <10, 20, 30>. A similar program, set-view can
be defined using braces instead of angle brackets.

Exercise 9.4
Let vl be the vector (vector 12 3 4) and let v2 be (vector-copy vl).
What does each of the following expressions return? Test them with each of
the three representations of vectors.

a. (eq? vl v2), (eq? vl vl)

9.3 Representing Vectors 289


b. (eqv? vl v2), (eqv? vl vl)
c. (equal? vl v2), (equal? vl vl)

What conclusions can be drawn about the use of these predicates?

Exercise 9.5: vector-linear-search.


The vectors under consideration in this exercise contain elements that can be
tested for sameness with the predicate equal?. Define a procedure vector-
linear-search that has as its parameters a vector vec and an object obj. It

returns the smallest index whose element is the same as obj. If the object is

not in the vector, an appropriate message is returned. Test your program on:

(vector-linear-search '#(g npradlbs) 'a) ^^ 4


(vector-linear-search '#(29 13 96 -5 24 11 9 -15 2) 11) =^ 5

Exercise 9.6: vector-append, vector-reverse


Use vector-generator to define vector analogs of append and reverse.

9.4 Matrices

We have used lists and vectors to store data. In both of these, the elements
are sequentially organized, so that we can index the elements starting with
zero and increasing the index by one for each successive element. We often use
this way of organizing information, but there are also occasions where such
a sequential organization is not the best way of organizing the information.
Many times a table is a more convenient way of presenting data. For example,
in a telephone book, each person has several entries: a name, an address, and
a phone number. The data are entered in rows, each row containing the
information for one person. If we take the first entries in all of the rows, we
get the first column; the second entries of all rows form the second column;
and in general, the nth entries in all rows form the nth column. The table
in Figure 9.28 contains four rows and three columns. Each entry in the table
can be located if we are given two indices, the zero-based index of the row
and the zero-based index of the column. Thus in our table, "3314 Valley Dr."
is the item with row index 2 and column index 1.

A matrix is a table in which each of the entries has two indices, the first

being the zero-based row index and the second being the zero-based column
index. In our discussion, we refer to the element in the matrix A having row
index i and column index j as the element Oij . If the matrix has m rows and n

290 Using Vectors


"Jones, John" "2117 Plum St." "412-8421"
"Jones, M. S." "1392 First Ave." "424-7773"
"Jose, Michael W." "3314 Valley Dr." "421-0035"
"Joslin, Joan P." "2550 Western Blvd." "412-5531"

Figure 9.28 A table with four rows and three columns

columns, we call it an "m by n matrix." This is sometimes written as "m x n


matrix." For example, if A denotes the table given in Figure 9.28, then A is

a 4 X 3 matrix, and "424-7773" is the value of element ai2-


Unlike vectors, matrices are not a Scheme data type, so we have to decide
upon a representation for matrices, and we have to define the building blocks
in terms of which the matrix procedures are defined. We begin with two
procedures, num-rows and num-cols, that take a matrix as argument and
return the number of rows and the number of columns, respectively. We also
use a selector matrix-ref that has the call structure

( (matrix-ref mat) row-index column-index)

where mat is a matrix and row-index and column-index are nonnegative


integers. It returns the element of the matrix m.at with indices row-index
and column-index. Thus, if A represents the matrix in Figure 9.28, we can
write

[1] (define A-ref (matrix-ref A))


[2] (A-ref 1 2)
"424-7773"
[3] (A-ref 2 1)
"3314 Valley Dr."

We also use a constructor that is an analog of vector-generator. We call

it matrix-generator, and it has the call structure

((matrix-generator gen-proc) nrows ncols)

where nrows and ncols are the number of rows and columns, respectively, of
the matrix we are constructing. The procedure gen-proc takes as its argu-
ments two indices and produces the element with those indices in the matrix
being constructed.

9.4 Matrices 291


We now decide how to represent a matrix. Let us assume that we are
defining an m x n matrix A. There are m rows and n columns in A, and
altogether there are m x n elements in A. Each of the m rows contains
n elements. One way of representing ^ is as a vector V that contains the
m X n elements of the matrix. But we must have a way of knowing the
correspondence between elements of V and elements of A. It will be clearer
if we look at a concrete example first and then generalize the method we
develop. Consider the matrix in Figure 9.29.

A=

Figure 9.29 A 3 x 4 matrix

We have followed the usual mathematical convention of writing such nu-


merical matrices (or tables) enclosed by large parentheses. This matrix, A,
has three rows and four columns. There are two ways of writing the elements
of a matrix sequentially, both of which are used in practice. One is to write
the rows one after the other, to get the sequence of elements

523714058312
This way is called row major order. The other way is to write the columns
one after the other, to get the sequence of elements

518243301752
This way of writing the elements is called column major order. We arbitrarily
choose to use the row major order in our representation of matrices.
Now that we are agreed on row major order, we must have a way of knowing
where one row ends and the next begins. In our example, there are in all 12

elements in the matrix, as we can see by counting the number of elements in


the sequence. Since we know that there are 4 elements in each row, we can
divide 12 by 4 to get that there are 3 rows. We need someplace to store the
number 4, which number of elements in each row. Let us agree
tells us the to
put it at the end of the sequence of numbers and store the whole sequence in

a vector. Thus we represent the matrix A as the value of

(vector 523714058312 4)

292 Using Vectors


Program 9.30 num-cols

(define num-cols
(lambda (mat)
(let ((size (subl (vector- length mat))))
(vector-ref mat size))))

Consider the element in this vector. It has index 6 in the vector. What
indices does it have in the matrix A? Here is how we can compute them using
information we can extract from the vector A. The vector A has length 13,
and the last element is a 4. We use this last element to conclude that the
matrix has rows of length 4. Thus it has four columns. If we remove the
last element, there are 12 elements left, so the matrix has 12/4 = 3 rows.
Now if we start at the beginning of the vector and collect the elements in
groups of four, we see that is in the second group and is the third element in
that group. Thus is in the second row and third column. Using zero-based
indices, we see that has row index 1 and column index 2. We can get these
indices easily by noting that the row index is the quotient when the index 6
is divided by the row length 4, and the column index is the remainder when
6 is divided by 4.

Now let's reverse the process and start with the element in A having row
index 1 and column index 2. We find its index in the vector representing A by
multiplying the row index 1 by 4, which is the number of elements in each row
of A, and then adding the column index 2. Thus the index of that element in
the vector is (1 •
4) -I- 2 = 6.

In general, v/e represent an mxn matrix, mat, by a vector containing mn+1


elements, the last of which is a number telling us the number of columns of
mat, which is the same as the number of elements in each row of mat. The
elements of the matrix are enumerated in row major order, making up the first

mn elements. We can define the procedure num-cols for this representation


as shown in Program 9.30, for all we have to do is access the last element in
the vector representing the matrix.

To get the number of rowswe merely divide one less than


of the matrix,
the length of the vector representing the matrix by the number of columns.
Thus we get Program 9.31.
We now define matrix-rel that has the matrix mat cis its parameter and
returns a procedure that has as its parameters the row index i and the column

index j , and it in turn returns the element of mat with those indices. To find
the element in the vector representation of the matrix, we must multiply the

9.4 Matrices 293


Progrfon 9.31 num-roHS

(define nmn-rows
(leunbda (mat)
(let ((size (subl (vector--length mat))))
(/ size (vector--ref mat size)))))

row index i by the number of columns in the matrix mat and add to that
product the column index j. This gives us:

Program 9.32 matrix-ref

(define matrix-ref
(lambda (mat)
(let ((ncols (num-cols mat)))
(lambda (i j)
(vector-ref mat (+ (* i ncols) j))))))

Exercise

Exercise 9.7
The selector matrix-ref should contain in its definition a range test for the
indices i and j and return an error if either is out of range. Rewrite the
definition so that it contains such a test.

We now turn our attention to the constructor matrix-generator, which


is defined using vector-generator. We first have to build the generating
procedure of one argument that generates the vector representing the matrix.
If we are given the index k of an element e in the vector representation, the
corresponding row index is the quotient obtained when k is divided by ncols,
and the corresponding column index is just the remainder obtained when k is

divided by ncols. Recall that ncols is the number of elements in each row

of the matrix. Then to get the row index, we can count the number of groups
of ncols elements we can remove before including the element e with index
k. The number of such groups is the zero-based row index of the element e

in the matrix. But that number of groups is what we mean by the quotient

obtained when k is divided by ncols. The next such group of ncols elements

294 Using Vectors


)

contains e, and its zero- based index in that


group is the column index of e
in the matrix. But that zero-based index is also the remainder when k is
divided by ncols. Thus we can take as the vector-generating procedure of
one argument

(lambda (k)
(gen-proc (quotient k ncols) (remainder k ncols)))

The length of the vector representing the matrix is one more than the product
of nrows and ncols. We can then write:

Progrsan 9.33 matrix-generator

(define matrix-generator
(lambda (gen-proc)
(lambda (nrows ncols)
(let ((size (* nrows ncols)))
(let ((vec-gen-proc
(lambda (k)
(if (< k size)
(gen-proc (quotient k ncols)
(remainder k ncols))
ncols)))
((vector-generator vec-gen-proc)
(addl size)))))))

As an example, we construct a 3 x 5 matrix having all of its elements zero


by writing (make-zero-matrix 3 5) where

(define make-zero-matrix (matrix-generator (lambda (i j) 0)))

In mathematics, the rows and the columns of a matrix are considered to


be vectors. We adopt this point of view and define the procedures row-of
and column-of that are used to select a row or a column of a matrix. It is

convenient to curry the procedures row-of and column-of. To get the row of
the matrix mat with zero-based index i, we invoke ((row-of mat) i), and
to get its column with zero-based index j, we invoke ((column-of mat) j).

These two procedures are defined by:

9.4 Matrices 295


) ) )

Program 9.34 row-of

(define row-of
(lanbda (aat)
(let ((mat-ref (natrix-ref mat))
(munber-of-coluains (nua-cols mat) )

(lambda (i)
(let ((gen-proc (lambda (j) (mat-ref i j))))
((vector-generator gen-proc) number-of -columns)) )))

and

Program 9.35 column-of

(define column-of
(lambda (mat)
(let ((mat-ref (matrix-ref mat))
(number-of-ross (num-rows mat) )

(lambda (j)
(let ((gen-proc (lambda (i) (mat-ref i j))))
((vector-generator gen-proc) number-of-rows))) )))

Figure 9.36 The transpose of the matrix in Figure 9.29

The transpose of an m x n matrix .4 is an n x m matrix whose rovrs are the

columns of A. The transpose of the matrix in Figure 9.29 is the 4x3 matrix
given in Figure 9.36. To we use the procedure
find the transpose of a matrix,
matrix-transpose (see Program 9.37) that takes as its argument a matrix
and returns its transpose. The key to writing the program is the observation
that the element with indices i.j in the transpose of A is the same as the
element with indices j. in .4. It is easily defined using matrix-generator.
i

296 Using Vectors


)

Program 9.37 matrix-treinspose

(define matrix-transpose
(lambda (mat)
(let ((mat-ref (matrix-ref mat))
(let ((gen-proc (lambda (i j) (mat-ref j i))))
((matrix-generator gen-proc)
(num-cols mat)
(num-rows mat ))))))

The elements of matrices can be any of the data types we have been using
in Scheme. For numerical matrices, that is, matrices whose elements are
numbers, we can define useful arithmetic operations. For example, if A and
B are two matrices of the same size (same number of rows and same number
of columns), we can define the sum A + B to he the matrix whose elements
are the sums of the corresponding elements of A and B. Similarly, we can
multiply the matrix A by a, scalar (number) c, and the resulting product cA is

the matrix whose elements are c times the corresponding elements of A. It is

also possible to define a useful multiplication rule for certain pairs of matrices.
It is not customary to define the product to be the matrix whose elements are
the products of corresponding elements of the two matrices. The following
example illustrates the multiplication rule normally used.
A factory produces four products, W, X, Y and Z, at each of two sites, R
and S. Each product uses steel, plastic, and rubber. Product W uses 4 units
of steel, 2 units of plastic, and 2 units of rubber. Product X uses 5 units of
steel, 2 units of plajstic, and 2 units of rubber. Product Y uses 4 units of steel,
3 units of plastic,and 1 unit of rubber. Product Z uses 3 units of steel, 5 units
of plastic, and 2 units of rubber. The cost of the steel, plastic, and rubber at
site R is $8, $4, and $5 per unit, respectively, and at site S is $7, $5, and $4

per unit, respectively. To find the material costs of making one unit of each

product at each of the two sites, we set up the following matrices:

Units Used Cost Per Unit


Steel Plastic Rubber
Site R Site S
W / 4 2 2 \
Steel / $8 $7
X 5 2 2
Plastic $4 $5
Y 4 3 1
Rubber I $5 $4
Z \ 3 5 2 /

Let's consider the cost of making a unit of Product Y at Site R. We multiply


the number of units of each material by the corresponding cost at that site

9.4 Matrices 297


and add these products. This is the dot product of the third row of the Units
Used matrix and the first column of the Cost Per Unit matrix. The result is
4 $8 + 3 $4 + 1 $5 = $49. We compute the cost of making a unit of each
• •

product at each of the sites by finding the dot product of the appropriate row
of the Units Used matrix and the appropriate column of the Cost Per Unit
matrix. This leads to the following tabulation of the results:

Site R Site S
w 1 $50 $46
X $58 $53
Y $49 $47
Z V $54 $54

From this example, we make the following observations about the way the
product of two matrices is defined. In order to be able to find the product AB
for two matrices, they must be compatible, which means that the number of
columns of A must be the same as the number of rows of 5. If two matrices
A and B are compatible, then the rule for computing their product AB is: if

A is a.n m X n matrix and B is an n x k matrix, then their product AB is ein

mX k matrix. The element with indices i, j in AB is the dot product of the


ith row vector of A and the jth. column vector of B.
We have developed the tools that enable us to translate this rule directly
into the definition of the procedure matrix-product that takes two compat-
ible matrices as arguments and returns their matrix product.

(define matrix-product
(Isunbda (mat-a mat-b)
(let ((a-row (row-of mat-a))
(b-col (coliunn-of mat-b)))
(let ((gen-proc
(lambda (i j) (dot-product (a-roH i) (b-col j)))))
((matrix-generator gen-proc)
(num-roBs mat-a) (num-cols mat-b))))))

This way of defining matrix-product follows directly from the rule describ-
ing matrix multiplication, but it is not an efficient way of doing it. Before actu-
ally taking the dot product, the row and column vectors had to be constructed.
However, the answer does not require having these vectors. We can take the
product of two elements at a time and accumulate their sum directly from the
matrices without building the row and column vectors. Each element in the
ith row of A has first index i, and each element in the jth column of B has
second index j. Thus the ith row of A has the elements Oi^Oi cii,i> • • • > Oi,n-i>

298 Using Vectors


Program 9.38 matrix-product

(define matrix-product
(lambda (mat-a mat-b)
(let ((ncols-a (niim-cols mat-a))
(a-ref (matrii-ref mat-a))
(b-ref (matrix-ref mat-b)))
(if (not (= ncols-a (num-rows mat-b)))
(error "matrix-product:"
"The matrices are not compatible.")
(let
( (gen-proc
(lambda (i j)
(letrec
((loop
(lambda (r ace)
(if (= r ncols-a)
ace
(loop (addl r)
(+ ace (* (a-ref i r)
(b-ref r j))))))))
(loop 0)))))
((matrix-generator gen-proc)
(num-roHS mat-a) (num-eols mat-b)))))))

and the jth column of B has the elements boj, bij, . .


.
, fen-ij- Thus to form
the dot product of the ith row and jth column, we must add all products
of the form ai^rKj where r takes all integer values from n — 1. In Pro-
to
gram 9.38, we redefine matrix-product to do this summation of products
directly and include a test for compatibility.
So far this treatment of matrices has not involved mutation. If we use a
purely functional representation of vectors and their procedures, all of the
procedures defined for matrices are purely functional. We can introduce a
mutator matrix-set! that is similar to vector-set!. When we do so, the
matrix must be considered as having state with the state variables the ele-

ments in the matrix, matrix-set! performs a mutation on the elements of


the matrix. It has the call structure

((matrix-set! mat) row-index column-index obj)

where mat is a matrix whose element with indices given by row-index and

9.4 Matrices 299


!

Program 9.39 matrix-set

(define matrix-set!
(lambda (mat)
(let ((ncols (nvim-cols mat )))
(lambda (i j obj)
(vector-set ! mat (+ (* i ncols) J) obj)))))

column-index is changed to the value of obj. Continuing the previous example


from Figure 9.28, we have

[4] (define A-set ! (matrix-set! A))


[5] (A-set! 2 1 "1922 River St.")
[6] (A-ref 2 1)

"1922 River St."

The mutator matrix-set! is defined in terms of vector-set! in Pro-


gram 9.39.
we saw how matrices can be implemented using vectors.
In this section,
In a vector, each element has one index, and we refer to a vector as be-
ing one-dimensional. In a matrix, each element has two indices, the row
index and the column index. We refer to a matrix as being two-dimensional.
In some languages, the analogs of vectors and matrices are called one- and
two-dimensional arrays. In Exercise 9.14, three-dimensional arrays are im-
plemented, and the extension to higher-dimensional arrays is carried out in a
similar manner. We use vectors to provide random access to stored data in
our next chapter, which is on sorting and searching.

Exercises

Exercise 9.8: matrix


Define a procedure matrix that takes two arguments, m and n, and returns a
procedure that takes as its m x n arguments the elements of the mxn matrix
it creates. (Hint: Use an unrestricted lambda.) For example, to create the
matrix in Figure 9.29, we write

((matrix 3 4) 5 2 3 7
14 5
8 3 12)

300 Using Vectors


Exercise 9.9: mat+
Define a procedure mat+ that takes matrices A and B as arguments and
returns their sum A + B. A and B must have the same number of rows and
the same number of columns. If aij is the element of A with indices i,j, and
bij is the element oi B with indices i,j, then the element of A+B with indices
i,j is aij + bij .

Exercise 9.10: matrix-multiply-by-scalar


Define a procedure matrix-multiply-by-scalar that takes as arguments a
number c and a matrix A and returns the matrix cA, which has as elements
c times the corresponding elements of A.

Exercise 9.11: matrix-view


Define a procedure matrix-view that takes an tti x n matrix as its argument
and prints the matrix in the format shown below. If m is the matrix given in
Figure 9.29 an invocation of (matrix-view m) should print

5 2 3 7
14 5
8 3 12
Next, include a tag matrix-tag and generalize the procedure view to dis-
play a vector, set, or matrix depending on the type of its argument. (See
Exercise 9.3)

Exercise 9.12: Column major order


Suppose we had used the column major order for listing the elements of a
matrix in the vector representation. Write the definitions of the following
procedures using column major order: num-rows, mim-cols, matrix-ref,
matrix-set !
, and matrix-generator.

Exercise 9.13: Vector of vectors


An mx n matrix can also be represented as a vector of length m in which the
element with index i is a vector of length n containing the elements in row i

of the matrix. Thus the matrix


2 1 -3
4 -2 -1
is represented as the vector

(vector (vector 2 1 -3) (vector 4 -2 -1))

For this representation of matrices, define the procedures niim-rows, num-


cols, matrix-ref, matrix-set!, and matrix-generator.

9.4 Matrices 301


Exercise 9.14
The method used in this chapter for representing an m x n matrix as a vector of
length mn + l can be extended to higher-dimensional arrays. For example, an
element a,jfc, in a three-dimensional array A is indexed with three indices, i, j,

and k, where f = 0, 1, . .
.
, mi — 1, j = 0, 1, .m2 — 1, and ^ =
.
.
, 0, 1, . .
.
, ma— 1.
We would then represent A as a vector of length mi m2 ma • •
2. The integer
-I-

rrin for n = 1, 2, 3 is the size of the matrix in dimension n and we say that A is
an mi X m2 X ma array. The last two entries in the vector representation can
be taken to be m2 and the product m2m3. Using this information, describe
the vector representationmore completely, and define the procedures size-
dim-1, size-dim-2, and size-dim-S that are the analogs of num-rows and
num-cols and return the values mi, m2, and ma, respectively. Also define the
procedures array-rel, eoray-set! and array-generator, which are analogs
of the corresponding matrix procedures. These considerations can be extended
to give arrays of any dimension.

302 Using Vectors


10 Sorting and Searching

10.1 Overview

The process of rearranging the data in a list to put them in some specified
order, such as alphabetical order or increasing numerical order, is called sort-
ing the list. The process of locating a given item in a list is called searching
the list for the given item. In this chapter, we develop routines for sorting
and searching both lists and vectors. We also develop a relational calculus for
retrieving from a table those items that satisfy some specified conditions.

10.2 Sorting

We use tables to store many kinds of data, such as names, grades, or salaries.
It is more convenient to access data in tables if the data are arranged in some
increasing or decreasing order. For example, names are most conveniently
arranged in alphabetical order, and test grades are often most conveniently
arranged in decreasing order. There are many methods for sorting a
different

table into a desired order. We shall look at a few of them in this section.

10.2.1 Insertion Sort

Our first sorting technique is called an insertion sort. Suppose we are given
a nonempty list Is of numbers that are not ordered and that we wish to rear-
range to be in increasing order. We first think about the problem recursively.
If the list of numbers contains only one number, the list is already sorted, and
we are done. If the list contains at least two numbers, and if we recursively
Program 10.1 insertsort

(define insertsort
(lambda (Is)
(if (singleton-list? Is)
Is
(insert (car Is) (ins ertsort (cdr Is))))))

Program 10.2 insert

(define insert
(lambda (a Is)
(cond
((null? Is) (cons a '
()))
((< a (car Is)) (cons a Is))
(else (cons (car Is) (insert a (cdr Is)))))))

invoke insertsort on (cdr Is), we get a correctly sorted list containing all

but the first number. To get the completely sorted we have to do is in-
list, all

sert (car Is) into its correct position in the already sorted part (insertsort
(cdr Is)). For this insertion, we afterward define a procedure insert that
inserts a number a into the correct place in a sorted list Is. Program 10.1
contains the definition of insert -sort, and Program 10.2 contains that of
insert.
While (cdr Is) is not empty, insertsort invokes insert to insert (car
Is) into (insertsort (cdr Is)). Until (cdr Is) is empty, the value of
(insertsort (cdr Is)) is not known, so a return table is built. When Is
is finally reduced to contain a single number, then (insertsort (cdr Is))
is just the "sorted" list containing that single number. Then each of the
insertions that has been waiting in the return table can be evaluated, and the
final sorted list is built up. Figure 10.3 shows how the recursive insertion sort
routine sorts a list of numbers. We start with the list (50 40 30 20 10).
When we reach the invocation of insertsort on (10), the sorted sublist
(10) is returned, and the invocations of insert that have been waiting in the

return table are evaluated with sorted sublists as their second arguments.
We also write an iterative instead of recursive version of insertion sort. This
version will be written usingScheme vectors to store the data. We define a
mutation procedure called vector- insertsort that takes as its argument a!

vector containing the numbers to be sorted into ascending order and changes

304 Sorting and Searching


(insertsort '(50 40 30 20 10))
(insert 50 (insertsort '(40 30 20 10)))
(insert 50 (insert 40 (insertsort '(30 20 10))))
(insert 50 (insert 40 (insert 30 (insertsort '(20 10)))))
(insert 50 (insert 40 (insert 30 (insert 20 (insertsort '(10))))))
(insert 50 (insert 40 (insert 30 (insert 20 '(10)))))
(insert 50 (insert 40 (insert 30 '(10 20))))
(insert 50 (insert 40 '(10 20 30)))
(insert 50 '(10 20 30 40))
(10 20 30 40 50)

Figure 10.3 Return table for insertsort

that vector into one with the same elements sorted


The value it as desired.
returns is unspecified, as is the case with many mutators. It is more convenient
to insert the numbers to the left instead of to the right when using vectors. Our
sort of the vector #(60 50 30 40 10 20) proceeds as shown in Figure 10.4.
At each stage, we can picture the process as removing the next item to be
inserted and shifting those before it to the right until we come to the place
where the item belongs.
The sort is accomplished by applying the following algorithm. We assume
that all elements to the left of the element with index k have been sorted.
We then save the element with index k in a variable val, freeing up the kth
position so that we can shift into it. The element with index k is then inserted
into the correct position by successively testing each element to its left and
shifting it one place to the right until a smaller element is encountered. To do
this, procedure vector-insertsort ! uses a local procedure, sortloop, which
is first invoked with the index 1, since that is the index of the first element
to be inserted. The local procedure sortloop calls the procedure vector-
insert ! to do the insertion for each successive index. Each time it is called,

say with index k, all of the elements with indices less than k have already been
sorted, and vector-insert! inserts the element with index k in the correct

place relative to the first k elements. Now the elements with indices less than
k+ 1 are in the correct order. This insertion process continues until the index
k reaches the length of the original vector, by which time all of the elements
have been inserted. When k is equal to size, the condition in the if expression

is false, and the if some unspecified value. We assume in


expression returns
this book that the implementation of Scheme we are employing does not print

the value returned on the screen. The code for vector-insertsort is given !

in Program 10.5.

10.2.1 Sorting 305


!

#(60 50 30 40 10 20)
Insert 50 «(60 30 40 10 20)
#(50 60 30 40 10 20)
Insert 30 #(50 60 40 10 20)
#(50 60 40 10 20)
#( 50 60 40 10 20)
#(30 50 60 40 10 20)
Insert 40 #(30 50 60 10 20)
#(30 50 60 10 20)
#(30 50 60 10 20)
#(30 40 50 60 10 20)
Insert 10 #(30 40 50 60 20)
#(30 40 50 60 20)
#(30 40 50 60 20)
#(30 40 50 60 20)
#( 30 40 50 60 20)
#(10 30 40 50 60 20)
Insert 20 #(10 30 40 50 60 )

#(10 30 40 50 60)
#(10 30 40 50 60)
#(10 30 40 50 60)
#(10 30 40 50 60)
#(10 20 30 40 50 60)

Figure 10.4 Steps in sorting with vector-insertsort

Program 10.5 vector-insertsort!

(define vector-insertsort!
(laubda (v)
(let ((size (vector-length v)))
(letrec ((sortloop (lanbda (k)
(if (< k size)
(begin
(vector-insert! k v)
(sortloop (addl k)))))))
(sortloop 1)))))

306 Sorting and Searching


! !

We now consider the definition of vector- insert ! . The procedure vector-


insert ! is applied with first argument k when those elements of the vector
with indices less than k have already been sorted. It inserts the element with
index k into the correct place in the sorted part, so that those elements with
indices less than k + 1 are now sorted. Here is how vector-insert! works.
If it is called with index k, it lets val be the element with index k. The
local procedure insert -h is then called with index k. When insert-h is

called with some index m as argument, it compares val with the element comp
having index m— 1. If val is less than comp, we still have not found the correct
place for val, so comp is moved up to have index m, and insert-h is called
again with argument m — 1compare val with the element with index m — 2.
to
On the other hand, if val is not less than comp, m is the correct index for
val, and we assign it with (vector-set! vec m val). Each time insert-h
isinvoked, its argument is one less than on the previous invocation. If its

argument is zero, then there are no more elements to the left to which to
compare it, and val must be the first element in the vector. Thus (zero? m)
is the terminating condition for the recursion. The code for vector-insert

is in Program 10.6.

Program 10.6 vector-insert!

(define vector-insert 1

(lambda (k vec)
(let ((val (vector--ref vec k)))
(letrec ((insert--h
(leunbda (m)
(if (zero ? m)
(vector-set! vec v&l)
(let ((comp (vector -ref vec (subl m))))
(if (< val comp)
(begin
(vector-set ! vec m comp)
(insert-h (subl m)))
(vector-set 7ec ffl val)))))))
(insert -hk)))))

Throughout this definition, we used the fact that vector-set ! is a mutation


procedure that changes the vector v as a side effect. When a vector v is passed
as an argument to vector-insertsort its elements are actually reordered
!
,

within that vector, so when we look at v after the sorting, its elements are

10.2.1 Sorting S07


sorted. This is a different behavior from the procedure insertsort, which
returns a sorted copy of the original list and the original list is not affected.
If we want a procedure to apply insertion sort to a vector and return a sorted
copy of that vector but leave the original vector unaffected, we can use the
following procedure, vector-insertsort:

(define vector-insertsort
(lambda (vec)
(let ((v (vector-copy vec)))
(vector-insertsort ! v)
v)))

Here is an example of how these sorting procedures work:

[1] (define nimlist (list 60 50 40 30 20 10))


[2] (insertsort niimlist)
(10 20 30 40 50 60)
[3] numlist
(60 50 40 30 20 10)
[4] (define numvec (vector 60 50 40 30 20 10))
[5] (vector-insertsort numvec)
#(10 20 30 40 50 60)
[6] numvec
#(60 50 40 30 20 10)
[7] (vector-insertsort! numvec)
[8] numvec
#(10 20 30 40 50 60)

We next perform an operation count on insertsort to study its efficiency

in sorting a list of length n. The procedure insert successively inserts the


kth number into the already sorted list of numbers with indices from k + 1

to n — 1. The process starts with k equal to n — 2 and decreases A: by 1 after


each insertion. When the sorted list to the right of the kth. number has m
members, inserting the A;th number in the correct place can require up to m
comparisons between the kth number and the numbers in the sorted list. In
applying insertion sort to a list of n numbers, the first insertion is done on
a list of one number, the second insertion is done on a list of two numbers,
and so on, with the ^th insertion done on a list of k numbers. There will be
n— 1 such insertion loops, so the total number of comparisons needed will be
at most l-|-2 + 3+--- + (n— 1) = n[n — l)/2. This formula represents the
worst possible situation, in which one has to go to the end of the sorted list

in each case to put the inserted number in the correct place. This was the
case in the example above. But in general, we can say that on the average.

308 Sorting and Searching


we would have to search halfway through the list to find the correct place to
insert the number, so the expected number of comparisons in insertion sort
is n{n — l)/4. Because the expected number of comparisons is 0{n^), we call
this a method of order n? or simply a quadratic method. If the list is alreeidy
sorted, the first comparison in each insertion determines the correct place, so
for a list of n correctly sorted numbers, only n comparisons are required.

10.2.2 Mergesort

The insertion sort discussed in the previous section is a quadratic method


requiring on the order of n^ comparisons to sort a list or vector of n elements.
We have seen that on the average it takes about 2,500 comparisons to sort a
list of 100 items. There are several methods of order nlogj n that reduce this
number considerably, so that a list of 100 items can be sorted with fewer than
700 instead of the approximately 2,500 comparisons of the previous method.
For a list of 1,000 numbers, insertion sort takes approximately 250,000
com-
parisons, while the nlogjn method takes about 10,000 comparisons. This
is a substantial improvement, and we now look at one such method, called

natural mergesort. In Program 4.3, we defined the procedure merge, which


takes two sorted lists and merges them into a single sorted list. The nat-
mergesort procedure takes advantage of whatever order already exists in the
list by grouping the original list into a list of sublists, each sublist consisting of
those elements that are already correctly ordered. For example, if the original
list is (2 34 123 2 1), the first step is to insert parentheses to group the
members into the four sublists ((2 3 4) (1 2 3) (2) (1)). Then each of
the successive pairs of sublists is merged to give half as many sublists, each of
which is still correctly ordered. In our example, the next step produces ((1
22 3 3 4) (12)). This process of merging successive pairs of sublists is

continued until there is only one correctly ordered list, at which point the sort
is completed. In our example, the next merge operation yields ((11222
3 3 4)), and the car of this list is the desired sorted list.

In the grouping pheise of our sorting procedure, we made the sublists as


large as possible so that each sublist is sorted. This grouping method is what
adds the adjective natural to the name mergesort. Another grouping method,
which leads to the procedure called mergesort without the adjective natural
is the following. We group the elements of our original list so that there is one
element in each sublist. Thus in the example above, our initial grouping yields
((2) (3) (4) (1) (2) (3) (2) (1)). Since each sublist of one element is

sorted, we can merge successive pairs of sublists to get ((2 3) (1 4) (2 3)

10. 2. & Sorting 309


Program 10.7 make-groups

(define make -groups


(lambda (Is)
(cond
((null'' Is) '())
((null? (cdr Is)) (list Is))
(else :iet ((a (car Is))
(gps (make-groups (cdr Is))))
(if « (c adr Is) a)
(cons (list a) gps )
(cons (cons a (car gps )) (cdr gps))))))))

(1 2)). Then successive sorted lists are merged to give ((12 3 4) (12 2
3)) and then ((11222334)). The development of this version of the
mergesort procedure is left as an exercise.

The first step in performing the natural mergesort is to group the data into
sublists so that in each sublist, the data are correctly ordered. In order to
carry out this grouping, we define a procedure maie-groups that takes a list
of numbers, Is, as its argument and returns a list with the numbers in the

same order but arranged in largest possible sublists in which the numbers are
nondecreasing. If Is is empty, then there are no sublists, and () is returned.

This serves as the terminating condition for our recursion. Similarly, if (cdr
Is) is empty, then Is consists of only one element, so there is only one group
consisting of the Is itself. Thus the list of groups is simply (list Is). Now,
if we let gps denote (make-groups (cdx Is)), there are two cases to consider
in order to get (maie-groups Is). If the first number in Is is greater than
the second number in Is, then the first sublist in (make-groups Is) is a
singleton list containing just that first number. Thus we have (cons (list
(car Is)) gps) as the result. On the other hand, if the first number in Is
in not greater than the second number in Is, then it must be added to the
first group alrecidy in gps. Thus the result is (cons (cons (car Is) (cau:

gps)) (cdr gps)). We can now complete the definition of make-groups in

Program 10.7.

We next use merge in the procedure pair-merge, which merges successive


pairs of sublists that resulted from applying make-groups to Is. By merging
successive pairs, we mean that the first two sublists are merged, then the third
and fourth sublists are merged, and so on until there are no pairs left to merge.
If there were originally an odd number of sublists, one would be left over as

310 Sorting and Searching


Program 10.8 pair-merge

(define pair-nerge
(lambda (sublists)
(cond
((null? sublists) '())
((null? (cdr sublists)) sublists)
(else (cons (merge (car sublists) (cadr sublists))
(pair-merge (cddr sublists)))))))

the last sublist of the list returned by pair-merge. Thus the procedure pair-
merge has as its parameter suolists, which is a list of sublists, and in each
sublist the numbers are in ascending order. It returns a list in which each
successive pair of sublists has been merged. Its straightforward definition
is given in Program 10.8.

To do the mergesort, we now need a procedure that applies pair-merge


repeatedly until there is only one sublist. We test for whether the list contains
only one sublist by checking whether the cdr of the list is empty. Thus we can
define the procedure nat -mergesort, which has as its parameter Is a list of
numbers and returns a list with those numbers sorted in ascending order. It

performs a natural mergesort by first grouping the numbers in largest possible

ascending sublists and then repeatedly merging successive pairs of sublists


until the list contains only one sublist. Program 10.9 shows the definition of
nat -mergesort.

Program 10.9 nat-mergesort

(define nat-mergesort
(lambda (Is)
(if (null? ]-S)

'()

(letrec ((sort (lambda (gps)


(if (null? (cdr gps))
(CeLT gps)
(sort (pair--merge gps ))))))
(sort (make- groups Is))))))

To see that the nat-mergesort procedure is 0{n log n), we consider a worst

10.2.2 Sorting 311


case in which we have n numbers arranged in descending order, and we sort
them into ascending order. This is a worst case because the grouping forms
a list of n lists, each containing only one number. On each pass through the
list, the procedure sort halves the number of sublists by merging successive
pairs.The question we must first answer is: "How many times can we divide
a number n by 2, rounding up to the next higher integer each time, until
we reach 1?" For example, if we start with seven numbers, the first pass of
sort produces four sublists. This is obtained by dividing | = 3.5 and then
rounding up to 4, corresponding to the list ((6 7) (4 5) (2 3) (1)). Next
we divide 4 by 2 and get two sublists: ((4 5 6 7) (1 2 3)). Finally when
we divide 2 by 2, we get one sublist, which is the sorted list.

What we have asked is: "What is the smallest power k for which 2*^ > n?"
Again if 7,n = and 2^ = 8, so the value of k is 3. But the
then 2^ = 4
statement 2^ = 8 means exactly the same thing as the statement log2 8 = 3,
and in general, the statement 2* = n means exactly the same thing as the
statement logj n = Thus the number of passes through the list to get
k. to the
sorted list is at most log2 n rounded up to the next higher integer if it is not
a whole number. We can use the ceiling procedure to do this rounding and
use the usual notation [logj n\ . In merging two lists that together contain
m numbers, at most m— I comparisons are needed, so if all of the sublists
together contain n numbers, the number of comparisons needed in any one
pass of sort through the list we make at most
will never exceed n. Since
|log2 n] passes through the list and each pass needs at most n comparisons,
we have a total of at most npogjn] comparisons. Thus nat-mergesort is
0(n log n).
There are also situations in which we want to sort a list of numbers into
decreasing order. The sorting procedures given can easily be modified to do
this; for example, in nat-mergesort, one only has to change the < to > in the
two procedures merge and make-groups. It would be even more convenient
to use procedural abstraction here and write one nat-mergesort procedure
that takes an additional argument rel that will be either < or >, and replace
the inequalities in merge and maie-groups by rel.
An iterative vector version of mergesort can also be defined. Since we shall
be sorting data stored in vectors, we look at such a version. A recursive
vector version of mergesort is outlined in the exercises. The iterative sorting
program has three parts. We first determine the group size, with initial value
1. From the group size, we determine the end points of the groups. Then
we merge successive pairs of groups, with the usual proviso that if there is
an odd number of groups, one is left over after the merging. After the merge
phase, we double the group size and repeat the process. This continues until

312 Sorting and Searching


!

Program 10.10 vector-merge'

(define vector-merge!
(launbda (newvec vec)
(lambda (left top-left right top-r ight)
(letrec
((mergeloop
(lambda (left right i)
(cond
((and (< left top-left) (< right top -right))
(if (< (vector-ref vec left) (vector-ref vec right))
(begin
(vector-set! newvec i (vectoi-ref vec left))
(mergeloop (addl left) right (addl i)))
(begin
(vector-set! newvec i 'vectoi—ref vec right))
(mergeloop left (addl right) (addl i)))))
]

((< left top-left)


(vector-set ! newvec i (vector-ref vec left))
(mergeloop (addl left) right (addl i)))
((< right top-right)
(vector-set ! newvec i (vector-ref vec right))
(mergeloop left (addl right) (addl i)))))))
(mergeloop left right left)))))

the group size is the same as the length of the vector, at which time the sort

is completed. Since the initial group size is 1 for all groups, this is an ordinary
rather than a natural mergesort.

The merging phase is done by the procedure vector-merge !


, which merges
two adjacent groups in a vector vec. Suppose that the group in vec with in-
dices from left up to, but not including, top-left and the group with indices
from right (usually the same as top-left) up to, but not including, top-
right have already been The mutation procedure vector-merge
sorted.
merges these two groups into the vector newvec. The steps are similar to
those used in the procedure merge. The definition of vector-merge is given !

in Program 10.10.

We now carry out the sort using the procedure vector-mergesort !, given
in Program 10.11, which has a vector vecl to be sorted as its parameter. Since
the mutator vector-merge I merges two adjacent groups of one vector into
another vector, we must always have another vector into which to carry out

10.2.2 Sorting 313


! ! )

Program 10.11 vector-mergesort

(define vector-nergesort
(lanbda (vecl)
(let ((vec-size (vector-length vecl))
(let ((adjust (lambda (k) (min k vec-size)))
(vec2 (meike-vector vec-size))
(max-index (subl vec-size)))
(letrec
( (merge-pass
(lambda (group-size count)
(if (> group-size maix-index)
(if (even? count) (vector-change! vecl mzix-index vec2))
(let ((newvec (if (odd? count) vec2 vecl))
(vec (if (odd? covuit) vecl vec2)))
(let ((merge! (vector-merge! newvec vec)))
(letrec
((group-ends
(lambda (left top-left right top-right)
(if (<= left meuc-index)
(begin
(merge! left top-left right top-right)
(let ((new-right (+ top-right group-size)))
(group-ends
top-right
(adjust new-right)
new-right
(adjust (+ new-right group-size)))))))))
(group-ends (adjust group-size)
group-size (adjust (* 2 group-size)))))
(merge-petss (* group-size 2) (addl coimt) ))))))
(merge-pass 1 1))))))

this merge. Rather than create a new vector on each merging pass through
the vector, we make a second vector vec2 and alternate merges from vecl
into vec2 and from vec2 into vecl on successive merging passes.
On the first pass through the vector, we assume that each group contains
only one item, so the parameter group-size to the local procedure merge-
pass is given the initial value 1, and its second parameter count is set to 1.

On each successive merging pass through the vector, two adjacent groups are

314 Sorting and Searching


Program 10.12 vector-change!

(define vector-change!
(lambda (vecl j k vec2)
(letrec ((loop (leunbda (i)
(if (<= i k)
(begin
(vector-set I vecl i (vector-ref vec2 i))
(loop (addl i)))))))
(loop j))))

merged so group-size is doubled. On each merging pass, count is increased


by 1. When count is odd, the merging is done from vecl to vec2, and when
count is even, the merging is done from vec2 to vecl.
The end points of each pair of adjacent groups are the four parameters
of the local procedure group-ends; these are left, top-left, right, and
top-right. The parameter left is the left end point of the left group, and
right is the left end point of the right group of an adjacent pair of groups.
We get top-left by adding group-size to left, but with a large enough
group-size, top-left can exceed the length of the vector. In order to avoid
this,we use the local procedure adjust, which limits the size of its argument
to be at most the length of the vector vecl. The value of right is obtained
by adding group-size to left. And finally, we get top-right by adjusting
the sum of right and group-size. The mutator vector-merge! is then
invoked with these parameters to merge the two adjacent groups, and the
pass through the vector is made by repeated invocations of group- ends to
determine the successive pairs of groups. When a pass is completed, merge-
pass is invoked with the next group-size and count. Finally, when group-
size reaches the length of the vector, there is only one group and the vector
is Sorted. If it is vec2 that happens to be storing the sorted items, the
mutator vector-change! is invoked to copy vec2 into vecl. The definitions
of vector-mergesort and vector-change
! ! are given in Programs 10.11 and
10.12, respectively.

The mutator vector-change! copies the segment of vec2 with indices be-
tween j and k into vecl, actually changing the items in vecl. This vector
version of mergesort sorts a vector in just about the same time as the list

version of mergesort sorts a list of the same size. It is somewhat slower than
natural mergesort, but it has the advantage of not requiring a return table

10.2.2 Sorting 315


since it is iterative.

10.2.3 Quicksort

There is another popular method of sorting data stored in lists or in vectors,


called quicksort. When the data are rather randomly distributed, quicksort
is 0{nlogn) like mergesort, but in the worst cases, when the data are nearly
sorted, it is of order 0{n'^) like insertion sort. An advantage of the vector
version of quicksort over the vector version of mergesort is that the sorting is

done by mutation within the original vector, and no temporary storage vector
is necessary.

We first look at the algorithm for carrying out a quicksort of a list of n


numbers into increasing order.

1. We first select an item in the list, which we call the pivot. For convenience,
we shall select the first item in the list, although selecting other items as
the pivot sometimes improves the performance of the method.

2. The rest of the items in the list are copied into one of two lists. Those less

than the pivot are copied into the left list, and the rest are copied into the

right list. The order of the items in these two lists is immaterial.

3. A new list is created in which the items in the left group are followed by
the pivot, which is, in turn, followed by the items in the right group.

4. The quicksort algorithm is then repeated recursively starting again from


Step 1 on the items in the left group and on the items in the right group. On
each application of the algorithm, the groups decrease in size, and when all

groups have been decreased so as to consist of a single element, the original


list is sorted.

The program implementing this algorithm to sort a list of numbers into


increasing order is called quicksort. It first checks to see whether the list is

empty or if it consists of a single number. In these cases, it merely returns the


original list. Otherwise, it calls the local helping procedure called collect,
which carries out Steps 1 through 3 of the quicksort algorithm The procedure
collect has the pivot as its first parameter, the rest of the list as its second
parameter Is, and two accumulators, Igroup and rgroup, as its third and
fourth parameters. The first of these accumulators, Igroup, stores those
elements of the list that are less than the pivot to give us the left group,
and the second of the accumulators, rgroup, stores those elements that are
greater than or equal to the pivot to give us the right group. The procedure
collect tests each element of the list Is to determine whether it is less than

316 Sorting and Searching


Program 10.13 quicksort

(define quicksort
(letrec
((collect
(lambda (pivot Is Igroup rgroup)
(if (null? Is)
(append (qui cksort Igroup) [cons pivot (quicksort rgroup)))
(if (< (car Is) pivot)
(collect pivot (cdr Is) (cons (car Is) Igroup] rgroup) )

(collect pivot (cdr Is) Igroup (cons (.caoc Is) rgroup)))))))


(lambda (Is)
(if (or (null? Is) (null? (cdr Is)))
Is
(collect (car 1 s) (cdr Is) '0 '())))))

the pivot. If it is, it conses it onto Igroup; otherwise it conses it onto rgroup.
When Is is finally empty, the pivot is consed onto the quicksort of rgroup,
which is then appended to quicksort of Igroup. The definition of quicksort
is given in Program 10.13.
We also present the vector version of quicksort, which we call vector-
quicksort ! . It has the property that it sorts the data within the given
vector without having to make a copy of it, as mergesort must do. This is an
advantage if theamount of memory available is small and making a copy uses
too much of the memory. Like vector-mergesort !, vector-quicksort! is
a mutator that changes the order of the elements in the original vector.

To illustrate how vector-quicksort implements the quicksort algorithm


!

in place (that is, within the given vector), we shall walk through the sorting

of the vector

#(649782569)

To see how this version of quicksort works, we place our left index finger below
the item to the right of the pivot (the item with index 1) and our right index
finger below the last item in the vector (the item with index 8 in our example).
This is illustrated by the arrows under the items:

#(649782569)
/ \

10.2.3 Sorting 317


We now go though a sequence of steps designed to move the pivot into its final

position, so that all items to its left will be less than or equal to the pivot eind
all items to its right will be greater than or equal to the pivot.

1. Searching Up. We compare the item above our left index finger with the
pivot and if it is not greater than the pivot, we move our left index finger
one entry to the right and compare that item with the pivot. We continue
moving to the right until we encounter an item that is greater than the
pivot. We then hold our left index finger at that position. If we pass
beyond the last item, we go back to it. In our case, we have moved to the
item with index 2, and the positions are given by:

#(649782569)
/ \
2. Searching Down. We next compare the item above our right index finger
with the pivot and if it is not less than the pivot, we move our right index
finger one entry to the left and compare that item with the pivot. We
continue moving to the left until we encounter an item that is less than the
pivot. We hold our right index finger at that position. we come to the If

first item, we stop there. In our example, we move down until we encounter
the item 5 with index 6.

#(649782569)
/ \
3. Swapping. Swapping allows us to move both values from the wrong location
to the right location. As long as our left index finger is to the left of our right
index finger, we swap the items to which our index fingers are pointing,
and we move our left index finger one item to the right and our right index
finger one item to the left. This gives us:

#(645782969)
/ \
We repeat steps 1, 2, and 3 starting with our index fingers pointing to the
7 and 2 and using the same pivot, 6. Since our left index finger is pointing
to the item 7, which is greater than the pivot, and our right index finger is

pointing to the item 2, which is less than the pivot, we swap those two items
and move our index fingers to the next item, our left index finger moving right
and our right index finger moving left. This gives:

#(645287969)

318 Sorting and Searching


We again repeat steps 1,2, and 3 starting with our index fingers both pointing
to 8 with index 4 and using the same pivot, 6. Since our left index finger is

pointing to 8, which is greater than the pivot, it stays where it is. Our right
index finger is pointing to 8, which is greater than the pivot, so it moves one
to the left and comes to 2, which is less than the pivot, so it stays there. We
now have:

#(645287969)
X
4. Partition. After searching up and searching down, our left index finger is

pointing to an item that is to the right of the item to which our right index
finger is pointing. When this happens, we swap the pivot item with the
item to which our right index finger is pointing. This gives us:

#(245687969)
We next partition the vector into three parts: the left part consists of all items
to the left of the pivot 6 (these items are all less than 6); the pivot itself makes
up the middle part; and the right part consists of all items to the right of the
pivot (these items are all greater than or equal to 6).

#(245687969)

We now apply Steps 1 through 4 to the left and right parts, choosing the
leftmost item in each part as the pivot. We continue doing this until each
part contains only one point. When we reach that point, the vector is sorted.

In the definition of vector-quicksort ! in Program 10.14, the sorting is

done by the helping procedure qsort, which has as parameters the lowest
and highest indices of the part of the vector to be sorted. As long as there is


more than one item in that part that is, the lowest index, low, is less than

the highest index, high the helping procedure partition (defined later) is
called to carry out Steps 1 though 4, and the final position of the pivot is
called middle. Then the qsort procedure is invoked on both the left part
and the right part.
The procedure partition selects the pivot as the first item in the group
to be partitioned and then calls the local procedure search, which in turn
invokes the local procedures search-up and search-down to carry out Steps
1 and 2 given above. The indices of the items that search-up and search-

down locate are named new-left and new-right, respectively. If new-left is

10.2.3 Sorting 319


! ! )

Program 10.14 vector-quicksort

(define vector -quicksort


(lambda (v)
(letrec
((qsort (lambda (low high)
(if « low high)
(let ((middle (partition V low high) )

(qsort low (subl middl<0)


(qsort (addl middle) h]Lgh))))))
(qsort (subl (vector-length v))))))

less than new-right, the items with these indices are swapped, using the pro-
cedure vector-swap! defined in Program 10.16, and the searching continues
as described in Step 3. Otherwise, the pivot is swapped with the item with
the index new-right, and new-right is returned as the value of paortition.
The definition of partition is given in Program 10.15.

To get an idea of why quicksort is 0{n log2 n) for some lists, let's assume
that the items to be sorted are well mixed so that the correct location of the
pivot is near the middle of the list each time a left group or a right group is

sorted. Let's also assume that the list contains n = 2"* items; then m = logj n.
On the first pass, there will be approximately n comparisons with the pivot
(actually n — 1). If the correct location of the pivot item is near the middle
of the list, the group and the right group each contain approximately ^
left

items. Thus to sort each of them will take approximately ^ comparisons,


and since there are two groups, the total is again approximately 2(^) = ri

comparisons. Again assuming that the correct location of each pivot is near
the middle of the group, we have four groups each, containing approximately

^ items, so again approximately n comparisons will be needed to sort them


all. Since each partitioning is assumed to divide the group into two groups
of essentially equal size, the length of the list is divided by 2 on each pass,
so after m passes, each group will contain only one item. Thus there are
m passes, and each pass makes n comparisons, so there are nm = n logj n
comparisons in all. A similar but more involved analysis shows that if the list

is well mixed, quicksort is 0(n logjn).


On the other hand, when the original list is already sorted (or nearly sorted),
then the first pass requires n comparisons, only to find that the left group is

empty and the right group contains n— 1 items. Thus it takes n— 1 comparisons

320 Sorting and Searching


)

Program 10.15 paartition

(define partition
(laabda (v low high)
(let ((pivot (vector-ref v low)))
(letrec
((search
(laabda (left right)
(letrec
((search-up
(lambda (i)
(cond
((= i (addl right)) (subl i))
((> (vector-ref v i) pivot) i)
(else (search-up (addl i))))))
(search-down
(lambda (i)
(cond
((or (= i (subl left)) (< (vector-ref v i) pivot)) i)
(else (search-down (subl i)))))))
(let ((new-left (search-up left)
(new-right (search-downright)))
(if (< new-left new-right)
(begin
(vector-swap! v new-left new-right)
(search (addl new-left) ^subl new-right)))
(begin
(vector-swap! v low new-right)
new-right)))))))
(search (addl low) high)));)

Program 10.16 vector-swap!

(define vector-swap!
(lambda (vec i j)
(let ((temp (vector-ref vec i)))
(vector-set! vec i (vector-ref vec j))
(vector-set! vec j temp))))

10.2.3 Sorting 321


to partition the right group and that partitioning again leads to an empty left

group and a right group containing n — 2 items. Continuing this way, we see

that it will take

n + (n - 1) + (n - 2) + •
+ 2 = -(n^ + n - 2)

comparisons, which is Oin"^). Thus quicksort has the strange property that
it is the most efficient method for sorting data that are completely unsorted
and least efficient for data that are nearly sorted.

10.2.4 Sorting AlphabeticeJly

Often the data to be sorted consist of names that one would like to have
rearranged in alphabetical order. Scheme provides a convenient way to do
alphabetical sorts. Strings can be compared to decide which precedes the other
in alphabetical order. To decide whether one string precedes another string,
Scheme provides a predicate string<? that takes two strings as arguments
and is true if its first argument precedes its second argument lexicographically
(i.e., in the order in which they would appear in a dictionary). The usual
lexicographic order is modified to place the digits through 9 first, then all of
the capital letters, followed by all of the lowercase letters. A complete listing

of the order of the characters, including the punctuation characters, is given


in the ASCII Code Table, which is discussed in Appendix A. For example,

(string<? "Johnson, James" "Johnston, John") =^ #t


(string<? "Cleveland, Ohio" "Cincinnati, Ohio") ^ «f

There are also predicates string>?, string<=?, and string>=? that do the
expected things. The predicates string<? and string>? can be used in place
of < and > in the sorting programs to sort names into alphabetical order. If
the sort routine had been abstracted to take an additional argument rel, then
the alphabetical sort could have been accomplished by passing rel the value
string<? or string>?.
The data being sorted are sometimes in a table that can be sorted in many
different ways depending upon which column of the table we want in sorted
order. For example, a table may contain personnel data for a company. Each
row of the table contains the data for an individual employee. In the study of
databases, each row of the table is referred to as a record. The first column
contains the name, with last name first. The second column contains the
employee's identification number; the third column, the employee's age; the

322 Sorting and Searching


Name Id Age Yr.Emp Supervisor Salary

(define tablelO-17
'(("Smith, Harold W." 2324 43 1974 "Fox, Charles Q." 49325)
("Jones, Mary Ann" 1888 54 1965 "none" 65230)
("White, Thomas P." 3403 34 1982 "Smith, Harold W." 27300)
("Williams, John" 2451 46 1970 "Jones, John Paul" 41050)
("Brown, Susan E." 3620 28 1984 "Williams, John" 18500)
("August, Elizabeth" 2221 45 1971 "Jones, John Paul" 44100)
("Jones, John Paul" 1990 55 1965 "Jones, Maury Ann" 63700)
("Wilson, William W." 2455 46 1970 "August, Elizabeth" 41050)
("Black, Burton P." 3195 38 1978 "Smith, Harold W." 31420)
("Fox, Charles q." 2400 41 1981 "Jones, John Paul" 52200)
("Blue, Benjamin J." 3630 26 1984 "Williams, John" 18500)))

Table 10.17 A table represented as a list

fourth column, the year when the employee joined the company; the fifth col-
umn, the employee's supervisor; and the sixth column, the employee's annual
salary in dollars. Each of these columns is called a field, and each field has a
field name. The third field has the field name "Age." To process the data in
Scheme, one can represent it as a list (or in some cases, as a vector or a list of

vectors depending on which data structure is easier to process). Table 10.17


shows such a table represented as a list.

To sort the data in this table, we must first decide which field we are going
to sort on; that field is referred to as the sort key or simply the key. We can
sort these data so that the names are in alphabetical order, in which case we
are sorting on the first field, or we are sorting with the key "Name." One
could also sort the data by any of the other fields, depending upon the use
we are making of the data. The sorted data are then returned with the same
records but reordered so that the sorted field is in the desired order. We must
(or rel
modify the less-than predicate in the abstracted version) of the sort

routine so that makes the comparison on the appropriate member of each


it

row and then moves the whole row as a unit as the sorting is done.

10.2.5 Timing the Sorting Routines

Which of the sorting routines is We compare them by recording the time


best?
each takes to sort the same lists of numbers into increasing order. We use lists
of randomly generated numbers as the test data. Many implementations of
Scheme provide a procedure reuidom (see Exercise 13.3) that takes a positive

10.2.5 Sorting 323


integer argument n and returns a pseudo-random number in the range to
n-1, including the end values.^ Program 10.18 defines random-list, which
hcis as its output a list of n such randomly generated integers, each of which

is between and n — 1.

Program 10.18 random-list

(define random-list
(lambda (n)
(letrec ((build-list
(lambda (k)
(if (zero? k)
'0
(cons (random n) (build-list (subl k)))))))
(build-list n))))

We then define the test data as

(define randlOO (random-list 100))


(define rand200 (random-list 200))
(define rand400 (random-list 400))
(define v-randlOO (list->vector randlOO))
(define v-rand200 (list->vector rand200))
(define v-rand400 (list->vector rand400))

We can measure the time that each of the three sorting procedures takes to
sort these lists (or vectors) using the procedure timer, which we now define.

When we pass timer a procedure proc and its argument arg, it tells us both
the time in seconds that it took to run (proc arg) and the answer returned
by (proc arg). In order to do the timing, we assume that the Scheme we
are using has a way of getting the time of day accurate to hundredths of a
second. We assume that we can access the time of day by calling a procedure

^
{xfc}, k = 0, 1,
Such sequences of pseudo-random nvunbers can be generated in the
. . .

following way. Consider a congruence relation of the form /(x) = ax mod m, where is m
a large prime integer and a is an integer in the range 2, 3, . . . , m
— 1. Good choices for
m and a are m= 2^^ - 1 = 2,147,483,647 and a - 7^ - 16,807. We choose an initial
seed si and generate the sequence s^^i = /(*fc)> ^ = ^>^ Then u^ = si^/m is a
pseudo-random number in the range < u^ < 1, and a pseudo-rcmdom integer x^ in the
range < x^ < n — 1 is the integer part of nuh. (See Park and Miller 1988.)

324 Sorting and Searching


of no arguments called time-of-day.^ In Program 10.19, we define a timing
procedure that uses time-ol-day, which is assumed to be a thunk.

Program 10.19 timer

(define timer
(lambda (proc arg)
(let ((start (time-of-day)))
(let ((val (proc arg)))
(let ((finish (time-of-day)))
(let ((elapsed-time (/ (- finish start) 100)))
(writeln "Time = " elapsed-time ", Answer = " val)))))))

When a let expression, which contains several pair bindings, such as

(let ((vari vali) (var2 wa/2) ••• ivarn vain))


body)

is evaluated, there is in what order the pair bindings (vark


no guarantee
valk) are evaluated. In timer, we have to be sure that the order of the pair
binding is as shown in the code, so we had to nest the let expressions. In
addition, the last nesting was required because the va/-part of the last let
expression contained the t;ar-part of the previous let expression, which would
not be allowed if these two pair bindings were in the same let expression.
Scheme also has the special form let*, which has syntax similar to let:

(let* ((vari vali) {var2 va/2) ... ivarn vain))


body)

which is equivalent to a sequence of nested let expressions each containing one


of the pair bindings:

(let ((vori vali))


(let ((var2 vo/2))

(let (.(varn vain))


body) ... ))

^ Although the way of getting the time of day has not been standardized, eill Schemes have
some way of either getting the time of day or of timing a procedure.

10.2.5 Sorting 325


)

The pair bindings in a let* expression are evaluated from left to right, and
any ua/-part may contain var's from previous pair bindings. This enables us
to define timer in the following way:

(define timer
(lambda (proc arg)
(let* ((start (time-of-day))
(val (proc eirg))
(finish (time-of-day))
(elapsed-time (/ (- finish start) 100)))
(writeln "Time = " elapsed-time ", Answer = " val))))

Sort Time in Seconds

Number of Items 100 200 400

insertsort 0.55 2.69 10.76

nat-mergesort 0.17 0.38 0.88

quicksort 0.11 0.22 0.60

vector-insertsort ! 0.39 1.71 6.21

vector-mergesort ! 0.33 0.65 1.48

vector-quicksort ! 0.22 0.44 1.05

Table 10.20 Comparison on random data

The results of using timer on the various we have devel- sort routines
oped are given in Table 10.20."^ The three mutators vector-insertsort!,
vector-mergesort!, and vector-quicksort! were called by first making a
local copy of the vector to be sorted and then calling the timer on the invo-
cation of the mutator. For example, for vector-insertsort ! we have

(let ((v (vector-copy v-randlOO))


(timer vector-insertsort! v))

The time for the two insert sorts increases approximately by a factor of four
each time the length of the data is doubled, whereas mergesort's and quick-
sort's time increases by a factor between two and three.

The test was conducted in PC-Scheme (Version 3.02) on a Zenith 386 microcomputer.

326 Sorting and Searching


! !

We have seen the behavior of the various sorting routines on a list of ran-
domly distributed integers. It is also interesting to compare their behavior
on lists that are nearly sorted. Suppose we have a list starting with 50 and
followed by the integers from 1 to 100 in their natural order. We want to sort
this list into increasing order. This is equivalent to saying we want to insert

50 into the sorted list of integers from 1 to 100. We shall test our six sorting
routines on this problem of inserting 50 into the integers from 1 to 100, as
well as the problems of inserting 100 into the integers from 1 to 200 and of
inserting 200 into the integers from 1 to 400. The vector versions are tested
on the vectors obtained by converting the lists to vectors. The results are
shown in Table 10.21.

Sort Time in Seconds

Number of Items 101 201 401

insertsort 0.05 0.11 0.17

nat-mergesort 0.06 0.11 0.16

quicksort 0.38 1.37 5.98

vector-insertsort 0.05 0.11 0.17

vector-mergesort 0.28 0.66 1.42

vector-quicksort 0.44 1.59 5.93

Table 10.21 Comparison on nearly sorted data

In this kind of insertion into a sorted list, insertion sort and natural merge-
sort are about equally fast, and quicksort is much slower. We cannot recom-
mend any one of these routines as being the best. We must choose the sorting
routine that is best suited to do the job we have in mind for it. There are
many other sorting routines that have their advantages for specific kinds of
sorting jobs. However, we shall not explore this subject any further in this

book. There is an extensive literature on this topic in which you can read
about other ways of sorting data.

Exercises

Exercise 10.1: decr-ints


Define a procedure decr-ints that, given an integer n as its argument, pro-

10.2.5 Sorting 327


duces a list of the integers in decreasing order from n to 1. Use this procedure
to create test lists and vectors of length 25, 50, and 100, and use these to time
the sorting routines developed in this section. Explain why these lists provide
a "worst case" for all three.

Exercise 10.2: nat-mergesort


Rewrite the definition of nat-mergesort so that it is curried and takes as its
first argument a binary relation rel and returns a procedure that takes a list

as its argument. It returns the list sorted so that successive pairs of elements
satisfy the binary relation. For example, if the list contains numbers, to sort
the list in ascending order we use the procedure (nat-mergesort <), and
to sort a list of names in lexicographic order we use the procedure (nat-
mergesort string<?). Test your procedure on suitable lists of numerical
data and lexicographic data.

Exercise 10.3
Modify the mergesort program so that it can sort Table 10.17 by any one of
the six fields, and in the case of numerical fields, the sort can be in either
increasing or decreasing order. Test your program by sorting the data into
four separate tables, the first sorted alphabetically by Name, the second into
increasing order by Id, the third by decreasing order of Salary, and the fourth
by increasing Date of Employment.

Exercise 10.4: mergesort


We implemented the natural mergesort in which the original list is grouped
into the largest possible ordered sublists. The ordinary mergesort routine
groups the elements into sublists of one element each. Then the sublists are
merged pairwise until there is only one sublist. Define a procedure mergesort
that implements the ordinary mergesort. Compare the times for mergesort
and nat-mergesort to sort random lists of length 100, 200, and 400 numbers
and to do the insertions done in the second time tests. Write the definition of
a recursive vector version of mergesort that uses the two procedures vector-
merge and vector-change given in this section. The algorithm is outlined
! !

below.

a. Make a vector, store, that has the same length as the vector vec to be
sorted.

b. Define a local procedure sort that takes as its parameters first and last,
which are the indices of the first and last items, respectively, in the group
within vec to be sorted in a given recursive pass. The initial values of
first and last are and one less than the length of vec, respectively.

328 Sorting and Searching


c. As long as first is less than last, let mid be the index of the item that is

halfway between first and last. Then in a begin expression (which may
be implicit), do Steps d through g.

d. Invoke sort on the group from first to mid.

e. Invoke sort on the group from (addl mid) to last.

f. Invoke vector-merge ! on the appropriate arguments to merge the sorted


groups in vec from first to last into the vector store.

g. Copy the part of store between first and last into vec.

Compare the times for this version of mergesort to sort the various test vectors
with the times for the iterative vector version and with the times for natural
mergesort and ordinary mergesort to sort the test lists.

10.3 Searching

Suppose we have a list containing many items and we want to determine


whether a certain item is in the list and, in the case of a table, retrieve the

information in the row containing the item. We can easily write a program
that searches through the list from the beginning until the given item is found,
and if it reaches the end of the list without finding it, the program indicates
that the element is not in the list. A vector version of this procedure is in

Exercise 9.5. If each of the entries in a list of n items is accessed equally


frequently, we would on the average expect to have to go through half of the
list to find an item, and this would take on the average n/2 comparisons. Such
a method is called a linear search through the list for the given item.

If the list is already sorted into increasing (or decreasing) order, there is a
more efficient method for searching for a given item, which we call the target.

This more efficient method, called binary search, is described next. There is

a well-known guessing game in which one player. A, challenges another, B, to


guess the number A is thinking, knowing only that the number (or target) is
between 1 and 100, including both 1 and 100. B may ask any question, but

A can answer only "yes" or "no." The object is to determine the target by

asking the least number of questions. The most efficient strategy for asking
the questions is to divide the interval in which the target must lie into two
equal parts and question whether the target lies in one of these parts. For
example, the first question could be: "Is the number greater than 50?" If the
answer is yes, the next question should be: "Isthe number greater than 75?"
By halving the length of the interval in which the target can lie with each

10.3 Searching 329


question, it takes only seven questions to reduce the length of the interval to
1, at which point the number is determined.
The game described is an exeimple of a binary search. This method requires
random access of the items, so it will be more efficient to use vectors as the
data structure to store the data. If the vector has length n, we start by looking
at the item with index n/2. If it is equal to the target, we are finished. If it is

less than the target, we know that the target lies in the right half of the vector,

so we repeat the search procedure in the right heJf of the vector. If the item
with index n/2 is greater than the target, we know that the target lies in the
left half of the vector, and we repeat the search procedure in the left half of

the vector. This procedure divides the length of the vector by two every time
it is repeated, so it takes at most k steps to reach the point where the length is

reduced to one and the item is found, where 2* = n; that is, fc = logj n. Thus
a vector of 1,000 items that would take an average of 500 comparisons to find
an item using linear search would take at most 10 comparisons to find the
item using binary search.* This points out the advantage of using a method
that is O(logn) instead of 0(n).
Since we shall want to use the bineo-y search program to search for items
in vectors that may either be sorted in increasing or in decreasing order, or
in fact, in vectors of names that are sorted lexicographically, we shall write

a general version of binajy-seaxch. that takes as a parameter a relation rel


and returns a binary search procedure that takes a vector and a target as
its parameters. We can then pass <, >, or striiig<? to accomplish the three
kinds of searches mentioned above. Program 10.22 shows the code for such a
version of binary-search.
We apply this procedure to search a vector of names arranged alphabetically
as follows:

[1] (let ((names (vector "Ann S" "Ben J" "Ed A" "Guy S" "Kay ¥")))
((binary-search string<?) names "Guy S"))
3

If we have many alphabetical searches to perform, it may be convenient to


define a procedure alpha-search in terms of binary- search by writing

(define alpha-search (binary-search string<?))

* may involve two comparisons at each step in order


Actually, the procedure binaxy-search
todetermine whether the target is less than, greater than, or equal to the test value, so the
number of comparisons for a vector of 1,000 items could be as high as 20.

3S0 Sorting and Searching


Program 10.22 binary- search

(define binary-seeurch
(lambda (rel)
(lambda (vec target)
(letrec
((search
(leunbda (left right)
(if (< right left)
(writeln "The search failed.")
(let ((middle (floor (/ (+ left right) 2))))
(let ((mid-val (vector-ref vec middle)))
(cond
((rel tEurget mid-val)
(search left (subl middle)))
((rel mid-val target)
(search (addl middle) right))
(else middle))))))))
(search (subl (vector-length vec)))))))

and then we can apply this procedure to a vector of strings as follows:

[2] (let ((names (vector "Ann S" "Ben J" "Ed A" "Guy S" "Kay W")))
(alpha-search names "Ed A"))
2

Exercises

Exercise 10.5: list-linear-search


Write a program list-linear-search that takes an arbitrary list of numbers
and a number and searches through the given list linearly from the beginning
until it finds the number. If it finds the number, it returns the zero-based
index of that number in the list; if it does not find the number, it returns an
appropriate message.

Exercise 10.6
Write the procedures that are used to sort the personnel data in Table 10.17
on the field of names, and then use binary search to locate a given name and
return the personnel data for that individual. Use vectors to store the data
and use the vector sort routine you wish.

10.4 Relational Calculus 331


10.4 Relational Calculus

We have seen how information is stored in tables. Table 10.17, which contains
personnel data for a company, is an example of such a table. In accessing
information stored in a table, we often want to get all items that satisfy
certain conditions. For example, in Table 10.17 we can ask for the personnel

data for all people who joined the company we can ask
after 1980. Similarly,

for all people over 40 years of age whose salaries are over $50,000. We now
develop a relational calculus that will enable us to access data in this way
using the quantifiers f or-all and there-exists and the procedures set-
builder and set-map defined in Chapter 8. We assume in this section that
the data are stored in lists.

In order to use these predicates conveniently, we use the procedure apply,


which enables us to apply a procedure of several arguments to a list that con-
tains the same number of arguments. We define the procedure unlist, which
is a curried version of apply, in Program 10.23. Here proc is a procedure that
takes n arguments and Is is a list of n objects suitable to be the arguments
of proc. Then

((unlist operator) (list operandi operandn))

is equivalent to

{operator operandi ••• operandn)

For example, ((unlist +) '(2 3)) returns 5.

In the tables in which we store our data, we do not want to have two rows
exactly the same. For that reason, we take, as the data structure to represent
the table, a set whose elements are lists, each of which is one row of the table.
We use the set procedures to manipulate the information stored in the table.
For example, if we want to use the information in Table 10.17, we convert it

to a set using

(define setlO-17 (list->set tablelO-17))

To proceed with the development of our relational calculus, let us first


assume that we have a predicate pred, which is a question that will be true
or false on a given record in the set depending upon what values the items in
the record have. We first look at an example of how f or-all can be applied
to setlO-17. Suppose we want to see if a// of the employees are over 25 years

332 Sorting and Searching


Program 10.23 unlist

(define iinlist
(lambda (proc)
(lambda (Is)
(apply proc Is))))

of age. We write the predicate age-test?, which has as arguments the field
names in our table and tests to see if the age entry is over 25. We shall, in

general, find it convenient to invoke unlist on the predicates we define in


this section so that the quantifiers can process them directly.

(define age-test?
(unlist
(lambda (name id age yr-emp supervisor salciry)
(> age 25))))

Then we apply the procedure for-all to set 10-17 with the predicate age-
test? in the following way:

((for-all age-test?) setlO-17) =* #t

It returns true since all of the employees are over 25 years of age. Had we
replaced the 25 by 35 in age-test?, for-all would have returned false, since
some of the employees are not over 35.
In a similar wav we can use the procedure there-exists. For example, if
we want to know if anyone under 50 years of age receives a salary over $50,000,
we write the predicate age<50&salary>50000? as follows:

(define age<50&salciry>50000?
(unlist
(lambda (neune id age yr-emp supervisor salciry)
(and (< age 50) (> salary 50000)))))

Then we apply the procedure there-exists and get

((there-exists age<50&8alary>50000?) set 10-17) =^ #t

since Mr. Fox is 41 years old and receives a salary of $52,200.


We now consider an example using both for-all and there-exists. We
say that namel precedes name2 if namel comes before name2 in the lexico-
graphic ordering. Using setlO-17 again, we ask whether the following state-
ment is true: "For all employees, either the employee's name precedes his/her

104 Relational Calculus 333


supervisor's name, or there exists einother employee whose name precedes that
of the first employee and whose supervisor's name precedes the name of the
first employee's supervisor." If the first employee's name is denoted by n and
his/her supervisor's neime is denoted by s. then we have asked that either n
precedes s. or there is another person whose name is denoted by n* and whose
supervisor's name is denoted by s*. for which n* precedes n and s* precedes
s. We can now express this statement in terms of there-exists and lor-aill
as follows:

((for-all
(unlist
(lambda (n i a y s p)
(or (8triiig<? n s)
((there-exists
(unlist
(lambda (n* i» a* y* s* p*)
(and (8tring<? n* n) (string<? s* s)))))
8etlO-17)))))
setlO-17)

The parameters for the inner lambda expression had to be chosen to be vari-
ables different from those of the first lambda, for if we had used n cind s as

the parameters in the inner lambda expression, we would have hcui

(and (string<? n n) (8tring<? s s))

This would always be false and is certednly not what we weuit. Although the
names selected for parameters in a lambda expression are generally arbitrary,
it is necessary to watch for name conflicts in combining such procedures as
lor-all and there-exists. (Incidentally, the expression above returns false

since the conditions are not met by the employee Wilson whose supervisor is

August.)
The two procedures for-all and there-exists return true or false de-
pending on the predicate and the set constructed from the data table. We
shall refer to that set as the table, even though it is a set. To obtain the
names and ages of cill persons in Table 10.17. we write

(define nametage
(unlist
(laimbda (name id age yr-emp superYisor salary)
(list name age))))

SS4 Sorting and Searching


If we then call the procedure set-map as follows

(set-map nameftage set 10-17)

we get a table of all of the names in Table 10.17 followed by the field of ages.
The other fields have not been included. We call this new table the projection
of Table 10.17 onto the name and age fields. In a similar way, we can obtain
projections of Table 10.17 onto any of its fields.

Sometimes we want to get the actual rows of the table for which some
predicate is true. In particular, suppose we want to get the data from the
table about all people who are over 45 years of age. The data we want for

each one are the Id, the Age, the Year Employed, and the Salary. We write a
predicate over-45? as follows:

(define over-45?
(unlist
(lambda (name id age yr-emp supervisor salary)
(> age 45))))

We next invoke the procedure set-builder to get the subset over-45-set


of setlO-17 consisting of all entries for which the age is greater than 45.

(define over45-set
((set-builder over-45? the-empty-set) setlO-17))

To get the desired output, namely, the Id, Age, Year Employed, and Salary,
for each of the selected employees, we project the set over45-set onto the

four desired fields using set-map and then pass the resulting set to set->list
to construct the new table.

(set->list
(set -map
(unlist
(lambda (name id age yr-emp supervisor salary)
(list id age yr-emp salary)))
over45-set))
=> ((1888 54 1965 65230)
(2451 46 1970 41050)
(1990 55 1965 63700)
(2455 46 1970 41050))

The procedure set-map builds a new set consisting of parts of the rows of
the given table, then the list representation of the set contains no repetitions.
In our previous example, had we asked for the retrieved data to be the age.

10.4 Relational Calculus 335


Jones, M.A.

Jones, J.P.

Fox Williams August

Blue Wilson

White Black

Figure 10.24 Organizational chart for Table 10.17

year employed, and the salary, we would have had two rows like (46 1970
41050), but set-map would have included only one of them in the set. To
avoid such loss of information, at least one of the fields over which you project
should identify each individual uniquely.
In Table 10.17, each person has an immediate supervisor. The organiza-
tional chart for the company is given in Figure 10.24. White is under imme-
diate supervision of Smith but remotely under Fox, J. P. Jones, and M. A,
Jones. Blue is under the immediate supervision of Williams, but remotely
under J. P. Jones and M. A. Jones. We can ask the question: "Who is the
closest common supervisorfor both White and Blue?" Going up the tree from

these two,we see that their closest common supervisor is J. P. Jones. We shall
write a program to find the closest common supervisor of two members in a
supervision tree determined by a table such as Table 10.17. We make the
eissumption that each person has at most one immediate supervisor and that
the tree is connected so that there is one person at the very top.
The program will first start at one of the two members and build the path
up the chart until it goes as high as it can. The path will be a list consisting
of the names starting with one member, say. White, and containing (in order)
all of the names on the path until the top, M. A. Jones. To build this path, we

336 Sorting and Searching


Program 10.25 find-supervisor

(define find-supervisor
(unlist
(lambda (name id age yr-emp supervisor salcury)
(lambda (v) (if (string=? name v) supervisor «f)))))

need the procedure build-path, which takes a name like Smith in the table,
determines his supervisor (Fox), and adds Smith to the path, then determines
Fox's supervisor, and so on. When a person finally has no supervisor, that
person is added to the path, and the path terminates. The procedure that
determines a person's supervisor is called find-supervisor. For Table 10.17,
its definition is given in Program 10.25. When given the entries in a row of
the table, find-supervisor returns a procedure that takes the name of a
person, and if that person's name is in the given line, it returns that person's
supervisor; otherwise it returns false.
The strategy we use to find the closest common supervisor for two people,
say X and y, once their two path-lists to the top have been found is to start at
the rear of each path-list, where the two path-lists have the same person (in

our case, M. A. Jones) and move simultaneously toward the front in both lists

until the corresponding elements in the two lists differ. The preceding name
(the last one for which the two lists agree) is the closest common supervisor.
In Figure 10.24, the two paths to White and Blue split apart at J. P. Jones, so
he is their closest common supervisor. It is easier to cdr down a list from the
front to the back, so we reverse each of the two path-lists found for x and y,
so that they now agree in their first elements. The procedure f ind-ccs then
cdr's down the lists comparing their first elements. When these first disagree,
the previous one is returned as the closest common supervisor. The code
for the procedure closest-coinmon-supervisor Program 10.26. is given in
When reading the code for closest-conunon-supervisor, keep in mind that
when closest-coinmon-supervisor is applied below, the parameter test-
procedure is bound to find-supervisor. Since the tables we use are sets of
lists, we use the set operations pick and residue defined in Chapter 8 as the

selectors for sets.

When the procedure closest-conunon-supervisor is called with the argu-

ments White and Blue, we get

10.4 Relational Calculus 337


Program 10.26 closest-common-supervisor

(define closest -common-supervisor


(letrec
((f ind-ccs
(lambda (pathl path2)
(let ((restl (cdr pathl)) (rest2 (cdr path2)))
(if (string=? (car restl) (c«ur rest2))
(find-ccs restl rest2)
(car pathl))))))
(lambda (test-procedure)
(lambda (table)
(letrec
((build-path
(lambda (tbl u)
(if (empty-set? tbl)
(list u)
(let ((next (pick tbl)))
(let ((v ((test-procedure next) u)))
(if (not v)
(build-path ((residue next) tbl) u)
(cons u (build-path table v) ))))))))
(Ickfflbda (x y)
(find-ccs
(reverse (build-path table x))
(reverse (build-path table y) ))))))))

[1] (((closest-common-supervisor find-supervisor)


setlO-17)
"White, Thomas P." "Blue, Benjamin J.")
"Jones, John Paul"

In this chapter, we have demonstrated how data are handled using lists,

vectors, and sets. We chose the data structure for storing the data that was
most convenient for the application we had in mind. For a binary search, a
vector was used. For sorting, vectors or lists were used to store the data. For
using the relational calculus to search in a table, we found sets of lists to be
a convenient data structure. We have so far developed these important data
types to store data. In later chapters, we shall introduce several more useful
data types, such as the one called objects with which we shall implement
stacks and queues (Chapter 12) and the one called streams (Chapter 15).

338 Sorting and Searching


)

Exercises

Exercise 10.7: set-builder-map


There is some redundancy in using both set-builder and set-map to find the
projection of the set of those over 45 onto the fields Id, Age, Year Employed,
and Salary. We used an invocation of the form

(set-map proc ( (set-builder pred base-set) some-set))

Instead we can construct a procedure set-builder-map, which first deter-


mines if pred of an element returns true, and if so it then adjoins the proc of
that element to the result. Its call structure is

( (set -builder-map pred proc base-set) some-set)

For example, we can restate the earlier example using set-builder-map.

( (set-builder-map
over-45?
(unlist
(lambda (name id age yx-emp supervisor salary)
(list id age yr-emp salairy)))
the-empty-set
setlO-17)

Define the procedure set -builder-map.

Exercise 10.8
Write a procedure that accesses the data in Table 10.17 and returns the names
and numbers of all employees who are over 40 years of age, were
identification
hired before 1975, and whose salaries are over $43,000.

Exercise 10.9
Table 10.27 contains the monthly sales for January and February for the
employees listed according to their identification numbers, arranged in in-

creasing order. These are the same employees whose personnel data are given
in Table 10.17. The owner of the company wants a table that contains the
employees' names in alphabetical order and their January sales. Modify the
sorting, searching, and relational procedures given in this chapter to produce

10.4 Relational Calculus 339


the desired table. You may find it convenient to add some additional argu-
ments (predicates, test procedures, tables, etc.) to some of the procedures
given above.

Id Jan-Sales Feb-Sales
(define table 10-27 '( (1888 22300 33000)
(1990 61080 49320)
(2221 41000 52200)
(2324 25550 31500)
(2400 31010 25250)
(2451 28800 16500)
(2455 72050 50010)
(3195 60500 40220)
(3403 31100 22500)
(3620 31100 22500)
(3630 26300 19400)))

Table 10.27 Sales for January and February

Exercise 10.10
The procedure closest-common-supervisor defined in Program 10.26 com-
putes the paths using the local procedure build-path, which recursively pro-
duces the path-list. This path-list is later reversed when it is used as an ar-
gument to f ind-ccs. Rewrite the definition of closest-common-supervisor
so that build-path also has an accumulator as an argument and builds the
list iteratively. The final value of the accumulator is then the desired list in
the order from the top of the tree to the bottom, and reversing it is no longer
necessary when it is used as an argument to f ind-ccs.

Exercise 10.11: List of Vectors


This experiment assumes a list of vectors representation of tablelO-17.

[1] (define age (lambda (vec) (vector-ref vec 2)))


[2] (define age-test? (lambda (vec) (> (age vec) 25)))
[3] ((andmap-c age-test?) tablelO-17)
«t

Redo the examples of Section 10.4 using this representation. What are its
advantages and disadvantages?

340 Sorting and Searching


!

11 Mutation

11.1 Overview

In Chapter 9, we used the mutator vector-set! to change the entries in a


vector. The use of this and related mutators allowed us to write our programs
in a different style. With side effects on vectors, we showed in Chapter 10
that we can achieve better communication between the program parts and
we can define procedures that run more efficiently. In this chapter, we study
several other mutators. First, we look at a mutator that changes the binding
of a variable, and then we look at mutators that modify lists.

11.2 Assignment and State

A variable can be globally bound to a value using define. Furthermore, a


variable can be locally bound to a value using lambda, let, and letrec. Once
any of these bindings has been made, we may want to change the value to
which a variable is bound. Scheme provides a special form with keyword set
for this purpose. If the variable var is already bound to some value (either
globally or locally) and we wish to change the value to which var is bound to
be the value of the expression val, all we have to write is

(set! var val)

and Scheme evaluates val and binds that value to var. Scheme does not
specify what a set! expression returns, so the value returned is implementation
dependent. In this book, we suppress the value returned by the invocation of
.

a set! expression and write the next prompt. The use of set ! is illustrated in
the following:

[1] (define f (lambda (x) (+ x 10)))


[2] (f 5)
15
[3] (set! f (lambda (x) (* x 10)))

[4] (f 5)
50
[5] (let ((f (lambda (x) (+ x 100))))
(writeln (f 5))
(set! f (lambda (x) (* x 100)))
(f 5))

105
500
[6] (f 5)

50

In this example, the define expression is used to bind f to the procedure that
adds 10 to its argument. In [2], f applied to 5 returns 15. In [3], a set!

expression is used to rebind f to the procedure that multiplies its argument


by 10. We see the effect of this rebinding in [4] when the application of f to
5 now returns 50. The let expression in [5] locally binds f to a procedure
that adds 100 to its argument. In the body of the let expression, (f 5) is

written to the screen and produces 105. Then a set! expression is used to
rebind f to the procedure that multiplies its argument by 100. Observe that
this rebinding affects only the local binding of the variable f . Then the value
of (f 5) is returned, producing the value 500. In [6] , f is again applied to 5,

and the global binding of f is used to give the value 50. The set! expression
within the let expression affected only the local binding of f
We may think of the set! expression looking up the variable var in the
relevant environment (local or global, determined by the lexical scoping) and
actually replacing the value to which var is bound with the value of val.

Although implementations of Scheme generally allow us to change the binding


of a globally defined variable by defining it again using define, we do not
recommend doing so. Such changes in the bindings should be done with
set!. However, before a variable can be rebound using set!, it must have
been bound to some value either globally using define or locally using lambda
or one of the other binding forms.
We can use set ! in the following implementation of stacks. A stack is a
data structure used to store objects. An object can be put into the stack

342 Mutatton
with an operator called push! that takes the object as its argument. The last

object put into the stack is removed by the operator pop It is characteristic! .

of stacks that the last object that was entered is the first object removed.
This property is described with the name LIFO, which stands for "last in,

first out." Thus, if we push the numbers 10, 20, and 30 onto the stack in the
given order, on the first pop, 30 is removed, on the second pop, 20 is removed,
and on the third pop, 10 is removed.
We now look at one way of implementing stacks (we shall return to the
subject again in Chapter 12 for a better implementation). For our present
purposes, we implement a stack as a list of objects. The car of the list will

be the top of the stack. We next describe several procedures that perform
operations on a stack. The procedure empty? tests whether the stack is the
empty list. The procedure top returns the top of the stack, that is, the car
of the stack. The procedure push adds its argument to the top of the stack.
!

The procedure pop! removes the top of the stack. Finally, the procedure
print-stack prints the whole stack. The definitions of these procedures are
given in Program 11.1.
We have elected to print a stack with the word TOP preceding it to show
:

which end is the top of the stack. Thus the stack containing the numbers 1,
2, 3, and 4, with 1 on top will be printed as

TOP: 12 3 4

With the definitions given above, we perform the following experiment:

[1] (push! 'a)

[2] (push! 'b)

[3] (push! 'c)


[4] (top)
c
[5] (pop!)
[6] (print-stack)
TOP: b a

We next illustrate the use of set! in a discussion of memoizing. Let us


suppose that we want to find the value of a procedure proc that on each
invocation requires a long computation before it returns an answer. We also

assume that we call this procedure often with various arguments. It would
save time if we could store each new value that the procedure computes in a
table, and each time the procedure is called, the table is first checked to see if

the procedure has already been invoked with that argument. Then the already

11.2 Assignment and State 34S


)

Program 11.1 Procedures defining a stack

(define stk '())

(define empty?
(lambda ()

(null? stk)))

(define top
(lambda ()
(if (empty?)
(error "top: The stack is empty.")
(car stk))))

(define print-stack
(lambda ()

(display "TOP: ")

(for-each (lambda (i) (display x) (display " ")) stk)


(newline))

(define push!
(lambda (a)
(set! stk (cons a stk))))

(define pop!
(lambda ()

(if (empty?)
(error "pop!: The stack is empty.")
(set! stk (cdr stk)))))

computed value is returned rather than recomputing it. If it has not already
been computed, then it is computed, and the new value is both returned and
entered into the table. Since we are assuming that the computation of proc
is a relatively long process, it is more efficient to look the value up in the
table rather than recompute it. This process of making a table of values that
have already been computed and searching through this table each time the
procedure is called to see whether the value has already been computed for

the current argument is referred to as memoizing the procedure.

The data structure that we use to serve as a table in which to store the
already-computed values is a list, which we call table. Each entry in table is

a dotted pair (arg . val), in which val is the value of (proc arg). Thus if

344 Mutation
the procedure is the Fibonacci procedure fib and we have already computed
fib for arguments 2, 4, and 6, then table is ((2 . 1) (4 . 3) (6 . 8)).
We can also use proper lists as the entries in the table; in that case, table
would look like ((2 1) (4 3) (6 8)). The dotted-pair representation has
the advantage of being more efficient, since in building the table, only one con-
sing operation is necessary to build the dotted pair (cons 2 1), whereas two
consing operations are necessary to build the list (cons 2 (cons 1 '())).
Similarly, to obtain the second item in the dotted pair, we use cdr, whereas
in the list case, we first have to take the cdr and then take the car. We use
the procedure 1st to access the first item and 2nd to access the second item
of a list. If we are using lists of two items, 1st is then car, and 2nd is cadr.
If the list has three items, we also use 3rd instead of caddr, and if the list

has four items, we include 4th for cadddr. These are easier to read than the
multiple car/cdr chains.
To look up whether fib has already been computed for argument 4, the
procedure lookup is used. The procedure lookup has four parameters, an
object obj, a list of pairs table, and two procedures: success-proc, which
takes one argument, and f ailure-proc, which takes no arguments. If there is

a pair in table that has obj as its first element, then we say that the lookup
succeeded, and we invoke success-proc on that pair. If there no pair
is

in table that has obj as its first element, our lookup failed, and we invoke
f ailure-proc. These two procedures, success-proc and f ailure-proc, are
known as the success continuation and the failure continuation, respectively,
because they tell how to continue the computation when we either find obj

in table or fail to find it. For example:

(lookup 4 '((1 1 1) (2 4 8) (4 16 64) (6 36 216))


(lambda (pr) (2nd pr))
(lambda 0)) ==» 16

and

(lookup 3 '((11 1) (2 4 8) (4 16 64) (6 36 216))


(lambda (pr) (2nd pr))
(lambda () 0)) =>

In these examples, table is a list of items, each item being a list of three
numbers. The success continuation tells us to return the second element in
the pair. The failure continuation tells us to return 0. In the first example,
4 is found to be the first element in the third pair, so 16 is returned. In the

11.2 Assignment and State 345


second example, 3 is not found as the first element of any of the pairs, so is

returned.

The definition of lookup is straightforward. The strategy used in this defi-


nition is to search the list of pairs until we either find a pair starting with ob j
or reach the end of the list. Since obj, success-proc, and failure-proc do
not change in the recursion, we begin with a letrec expression to define a local
procedure, lookup, which has only the one parameter table. Because of lex-
ical scoping,we can use the same name lookup for both the local procedure
and the global procedure. Program 11.2 shows the definition of lookup.

Program 11.2 lookup

(define lookup
(lambda (obj table success-proc failure-proc)
(letrec ((lookup (lambda (table)
(if (null? table)
(failure-proc)
(let ((pr (car table)))
(if (equal? (car pr) obj)
(success-proc pr)
(lookup (cdr table))))))))
(lookup table))))

We illustrate the use of lookup in the definition of the Scheme procedure


assoc, which has two parameters, obj and a list of pairs table. If a pair
in table has obj as its first element, that pair is returned; otherwise false is

returned. For example:

(assoc 4 '((1) (2 1) (3 1 5) (4 3) (6 8))) (4 3)

(assoc 5 '((1) (2 1) (3 1 5) (4 3) (6 8))) #f

The code for assoc is easily written using lookup. We have only to take the
identity procedure as the success continuation and the constant procedure
that always returns false as the failure continuation. See Program 11.3.
We are now ready to write the procedure memoize, which takes a procedure
proc as its parameter and returns another procedure that is the memoized

346 Mutation
Program 11.3 assoc

(define assoc
(lambda (obj table)
(lookup obj table (lambda (pr) pr) (lambda () #f))))

Program 11.4 memoize

(define memoize
(leimbda (proc)
(let ((table '()))
(leunbda (arg)
(lookup arg table
(leunbda (pr) (cdr pr))
(lambda ()

(let ((val (proc arg)))


(set! table (cons (cons arg val) table))
val)))))))

version of proc. The definition of memoize is presented in Program 11.4.^


To memoize the Fibonacci procedure fib, we first define fib as before:

(define fib
(lambda (n)
(if « n 2)
n
(+ (fib (- n D)
(fib (- n 2))))))

We now show two different ways of approaching the memoization of fib. The
first way, which does not lead to the most efficient way of computing it, is to

define f ib-m as follows:

(define fib-m (memoize fib))

Evaluate (fib-m 6) first. Since (fib 6) has not yet been computed, fib is

computed recursively to get its value 8. This value is added to the table in the

^ The line (lambda (pr) f^cdr pr)) in the definition of memoize can be replaced by cdr.
Can you explain why?

11.2 Assignment and State 347


!

Program 11.5 memo -fib

(define memo-fib
(sHoize (lambda :n)

(if « n 2)
n
(+ (memo--fib (- n D)
(memo--fib (- n 2)))))))

set! line of code for memoize, and table is now bound to ((6 . 8)). When
(f ib-m 6) is evaluated again, the new binding is in effect. In this way, set
changes the value of the lexical binding of the variable table.
If (fib-m 6) is called again, the lookup with argument 6 produces the
pair (6 . 8) and by taking its cdr, we get the answer 8. If this is now re-

peated with (fib-m 100), the first time it is called, the tree recursion will

involve approximately 3(1.7^°°) operations, which is more than 10^^. If, how-
ever, (fib-m 100) is called a second time, the answer is found with just one
lookup in the table, a significant improvement in efficiency since the table

contains only two items at this point. If the table does get large after many
procedure calls with different arguments, the iteration lookup makes the
in

search in the table take time, so there is a trade-off that must be weighed be-
tween the efficiency of the procedure evaluation and the table lookup. Later,
we shall consider the use of vectors to represent the table of stored values.
Random access into the table for our lookups makes the memoizing process
more efficient than iteration through the table from its beginning.
A more practical use for the memoizing procedure can be made if we use
it in the recursive invocations of fib that are made in the definition of the
procedure fib itself. In computing (fib 6) recursively, one computes fib for
arguments 5, 4, 3, 2, and 1. Suppose that the first time any one of these is

found, its (.arg . va/)-pair is added to table. Then whenever fib is called
with this same argument in the tree recursion, the value is already known
from the table lookup and need not be recomputed. This will save time ui
the original computation of (fib 6), and if we call (fib 7), it is the sum of
(fib 6) and (fib 5), both of which are already in the table. This is a great
saving in time. Program 11.5 shows how we write this memoized version of
fib.

It is possible to get a feeling for how much memoizing improves the efficiency
of a computation by using the "timer" defined Program 10.19. Below is in

an experiment involving computations of the memoized procedures defined

348 Mutation
above:

[1] (timer fib 20)


Time = 7.69, Answer = 6765
[2] (timer fib 25)
Time = 85.19, Answer = 75025
[3] (timer fib-m 20)
Time = 7.69, Answer = 6765
[4] (timer fib-m 20)
Time = 0, Answer = 6765
[5] (timer fib-m 25)
Time = 85.25, Answer = 75025
[6] (timer fib-m 25)
Time = 0, Answer = 75025
[7] (timer memo-fib 20)
Time = 0, Answer = 6765
[8] (timer memo-fib 25)
Time = 0, Answer = 75025
[9] (timer memo-fib 100)
Time = 0.22, Answer = 354224848179261915075
[10] (timer memo-fib 100)
Time = 0, Answer = 354224848179261915075

Exercise

Exercise 11.1
To see how dramatically memoizing reduces the number of recursive invoca-
tions of fib, trace the procedures fib and memo-fib by placing (display n)
(display " ") before the if expressions. Then invoke the traced procedures
with arguments 6, 7, and 10. Be sure that you understand the results.

Because the elements of a vector can be accessed randomly with equal ease,
vectors can be used effectively for making tables. When a list is used as the
data structure for a table and the table lookup is done with assoc or with
lookup, the iterative search tests each entry until the correct one is found. If

we are looking for an entry that is not near the beginning of the list, the search
is costly in time. In a vector, we can access any entry with equal facility. Let
us rewrite memoize for a procedure that takes a nonnegative integer as its

argument. This time table is a vector in which we store values. We must fix
a largest value for the arguments that we store in the table, since we must

11.2 Assignment and State 349


specify the length of the vector in advance. ^ Let us suppose that memoization
will take place for arguments that are at most equal to some number mzut-
arg, and then we can take (addl meix-arg) as the length of the vector. To
find the value of the procedure proc for a given argument arg, we must first

determine whether that value has already been entered into the table. We
use vector-ref for the lookup and item-stored be (vector-rel table
let

arg) . The test to heis been stored for the argument


determine whether a value
arg is (null? item-stored). If the procedure we aie memoizing takes on
the value ( ) we would not be able to distinguish it from the ( ) s used to fill
, '

the original table.To avoid having to put restrictions on the values assumed
by the procedure, we enter (list val) instead of val into the table whenever
a new procedure value is added. Then item-stored finds a list containing
the value, and to get the value itself, we must take (car item-stored). The
definition of vector-memoize is given in Program 11.6.

Program 11.6 vector-memoize

(define vector-meBoize
(lajBbda (max-arg)
(lambda (proc)
(let ((table (Make-vector (addl max-arg) '())))
(lambda (eurg)

(if (> arg mcix-arg)


(proc arg)
(let ((item-stored (vector-ref table arg)))
(if (pair? item-stored)
(car item-stored)
(let ((val (proc arg)))
(vector-set ! table curg (list val))
val)))))))))

This memoizing procedure can be used in exactly the same way as the one
we defined earlier for numerical procedures like fib. It is more efficient when
many entries have been made into the table since the random access of the
vector makes lookup faster.

We can use this memoizing procedure to compute fibonacci numbers by

^ This is a disadviuitage of using vectors instead of lists as the data structure for the table.
We shall look at a way of removing this restriction when we discuss hashing in Chapter 12.

350 Mutation
calling the procedure memo-fib, now defined by:

(define memo-fib
( (vector-memoize 100)
(lambda (n)
(if « n 2)
n
(+ (memo-fib (- n 1)) (memo-fib (- n 2)))))))

This allows table lookup up to argument 100.


Let us summarize what we have learned about set ! in this extended ex-
ample. A set! expression is a special form with the following syntax:

(set ! vc.r val)

First, Scheme evaluates the expression val. Then Scheme looks for the lexical
binding of var, and the value of that binding is changed so that var is bound
to the value of val. If var is not bound to some value, then in many implemen-
tations, the set! expression returns an error message, for set ! changes only
existing bindings; it does not create new bindings. It is important to realize
that set ! does this rebinding as a side effect, Scheme set returns an
and in !

unspecified value, so that diff^erent implementations of Scheme could return


different things, and you should not rely on using the value returned in your
programs. The fact that set! does its rebinding as a side effect means that
you can use set! in a begin expression (implicit or explicit) before the last

clause and accomplish the rebinding.


We next illustrate the use of set! by showing how we can simulate an
imperative style of programming. Imperative-style programs consist of state-
ments executed sequentially, each one of which performs a certain action on
the variables or provides input or output. For example, an assignment state-
ment that assigns a value 10 to a variable a is given by (set! a 10). An
output statement that prints the value of the variable a on the screen is given
by (writeln a). Branching is accomplished using a conditional statement,
which we can do using either a cond or an if expression. If we want to ex-
ecute a statement that is not the next one in the sequential order, we use a

"goto"-type statement, which we accomplish by invoking a thunk, that is, a


procedure of no arguments. This has the making the next statement
effect of

to be executed be the first statement in the body of the thunk. We show


such a program by rewriting the definition of the procedure member? in this
imperative style. Recall first an iterative version of member?:

11.2 Assignment and State 351


(define member?
(lambda (item Is)
(cond
((null? Is) #f)
((equal? (car Is) item) #t)
(else (member? item (cdr Is))))))

An imperative version of member? is given in Program 11.7.

Program 11.7 Imperative version of member?

(define member?
(lambda (item Is)
(let ((goto (lambda (label)
(label))))
(letrec
( (start
(lambda ()

(cond
((null? Is) «f)
((equal? (car Is) item) #t)
(else (goto reduce)))))
(reduce
(lambda ()

(set ! Is (cdr Is))


(goto start))))
(goto start)))))

The variable that is assigned values by the statements in the program is

Is. The procedure goto invokes the thunk, which is its argument. The letrec

expression defines the thunks, which control the order in which the statements
are executed in the program. In the last line of the program, we see that the
first thunk invoked is steirt. The body of this thunk is a cond expression
that has three clauses. In the first of these, the condition tests whether the
list Is is empty. If so, #f is returned. The second condition in the thunk
start tests whether the first value in the list Is is item, in which case the
value #t is returned. The else clause in the thunk start moves control to the

body of reduce, where the list Is is assigned the value (cdr Is) and control
is moved back to start. It is important to realize that no variable is passed

to start when control is moved to start. Instead, the value assigned to the

352 Mutation
.

Program 11.8 while-proc

(define while-proc
(lambda (pred-th body-th)
(letrec ((loop (lambda ()

(if (pred-th)
(begin
(body-th)
(loop))))))
(loop))))

variable Is is changed by the set ! statement, and that is the value of Is used
on the next invocation of start. In functional programming, new values are
passed as arguments to procedures, whereas in this imperative programming
style, the new values of variables are given by assignment statements using
mutation procedures such as set !

An advantage of using a computer instead of hand calculation is the pos-


sibility of doing repetitive operations a large number of times with few in-

structions. This is done in a language like Scheme by repetitively invoking a


procedure with different arguments until a termination condition is reached.
One method of repeating certain operations in imperative-style programming
uses a while loop. We can illustrate the use of while by introducing the proce-
dure while-proc, which takes as parameters a predicate thunk pred-th and
a body thunk body-th. As long as the invocation of pred-th returns true,
body-th is invoked followed by an invocation of loop. It is assumed that in
the body of body-th, mutations will occur that eventually will make the invo-
cation of pred-th false. When that happens, the loop is completed. The while
loop depends on side effects to produce the desired results. The code for the
procedure while-proc is presented in Program 11.8. We have implemented
while-proc here as a procedure that takes two thunks as arguments. In the
exercises in Chapter 14, we shall implement a while expression as a special
form that has the predicate and the body themselves as subexpressions rather
than thunks made out of them.
We show an example of the use of while-proc by giving another definition

of member?, this time in an imperative style using a while loop. Here, we


introduce a local boolean variable ans that will be initialized to #f . In the
loop, the predicate thunk pred-th tests whether the list Is is empty or the
variable ans is true. As long as neither one is true, the body thunk body-th is
invoked. This tests whether the first value in the list Is is item. If it is, then

11.2 Assignment and State 353


ams is assigned the value true. Otherwise, the list Is is assigned the value
(cdr Is). The loop then repeats the invocation of the predicate thunk, and
if it is body thunk is invoked again. If it is false, the loop returns
true, the
something, but since it was called within an implicit begin expression, the

value it returns is ignored and the value of ans is returned. This definition of
member? follows:

(define member?
(lambda (item Is)
(let ((ans #f))
(while-proc
(lambda (not (or (null? Is) ans)))
(lambda
(if (equal? (car Is) item)
(set ! ans #t)
(set! Is (cdr Is)))))
ans)))

In this imperative style, recursion can be done only by explicitly building


the return table. Using set! in Scheme, we can simulate that behavior. In
Program 2.8, we defined the procedure swapper recursively. We first repro-
duce a recursive definition and then implement it in this imperative style,
replacing recursion with the explicit construction of the return table, which
we implement as a stack.

(define swapper
(lambda (a b Is)
(letrec ((loop (lambda (Is*)
(cond
((null? Is*) '())
((equal? (car Is*) a)
(cons b (loop (cdr Is*))))
((equal? (car Is*) b)
(cons a (loop (cdr Is*))))
(else
(cons (car Is*) (loop (cdr Is*))))))))
(loop Is))))

Most languages have a looping mechanism that repeats some operation until
a terminating condition is satisfied. We once again use the procedure nhile-
proc for that purpose. Notice that in the recursive program for swapper,
until Is* is empty, something is consed onto the recursive invocation of loop

354 Mutation
)

with argument (cdr Is*). Thus, that something is added to a return table
on each loop. We accomplish this in our imperative-style program by pushing
that something onto a stack, and when Is* is finally empty, we repeatedly
cons the top of the stack onto our answer and pop the stack until the stack
is empty. Thus our code will have two loops; the first pushes the appropriate
thing onto the stack until the list is empty, and the second pops the stack until
it is empty. We assume here that the stack stk has been globally defined and
is empty when the procedure swapper is called.

Program 11.9 swapper

(define swapper
(leunbda (a b Is)
(let ((Is* Is) (ans '()))
(while-proc
(lambda () (not (null? Is*)))
(lambda ()

(cond
((equal? (car Is*) a) (push! b))
((equal? (car Is*) b) (push! a))
(else (push! (car Is*))))
; (print-stack)
(set! Is* (cdr Is*))))
(while-proc
(lambda () (not (empty?)))
(lambda
(set! ans (cons (top) ans))
; (writeln "Answer = " ans)
(pop !

; (print -stack)
))
ans)))

Program 11.9 contains the imperative-style code for swapper. We have


included some print-stack and writeln expressions to obtain a trace of the
program. Semicolons have been placed in front of these output expressions
to show that they are not part of the swapper program. To get the trace,

remove the semicolons. An example of an application of swapper is given in

11.2 Assignment and State 355


[1] (swapper 12 '(123123))
TOP: 2
TOP: 12
TOP: 3 1 2
TOP: 2 3 1 2
TOP: 12 3 12
TOP: 3 1 2 3 1 2
Answer = (3)
TOP: 12 3 12
Answer = (13)
TOP: 2 3 1 2
Answer =(2 13)
TOP: 3 1 2
Answer =(3213)
TOP: 12
Answer =(13213)
TOP: 2
Answer =(213213)
TOP:
(213213)

Figure 11.10 Trace of the imperative-style swapper

Figure 11.10. The stack grows during the first loop, and the answer is being
built before each pop of the stack during the second loop.
In this example, the stack was defined globally and given the name stk.
Each of the stack operations referred to this stack in its definition. If we had
needed two stacks, say stkl and stk2, we would have had to define two sets

of stack operations and use the right ones with each of the two stacks. This
is an inconvenient way of working with stacks, so we shall look further into
this matter in Chapter 12 and develop a better way of implementing stacks
using what is known as object-oriented programming.
The stack is an object that changes with each push! and pop!. An object
that changes with time is said to be in a given state between changes. We
say that such an object has state. Its state can be described by certain
descriptors, called its state variables. In the case of the stack, we can consider
its list representation as a state variable that completely describes the stack.
The mutators such as set and vector-set and the procedures derived from
! !

them such as push! and pop! are used to change the state of objects. In most
of our Scheme programs, we can use procedure applications and recursion and
avoid the use of mutators altogether. There will be times, however, when we
find it convenient to use mutators.

356 Mutation
In mathematics and logic, a function is a rule that assigns to each of its

arguments a certain value, and the presentation in Section 8.5 developed this
point of view. We have been using the term procedure to describe the programs
we have been writing in Scheme. It would have been appropriate to use the
term function as long as we do not have side effects and the value returned by
the function is completely determined by its arguments; that is, the function
returns the same value every time it is invoked with the same arguments. This
is no longer the case when side effects are present. It is more accurate to use
the term procedure instead oi function since side effects are allowed, and this
is the custom in Scheme.

Exercises

Exercise 11.2: pascal-trieingle


In the Pascal triangle, each number is the sum of the two numbers in the line

above it and on each side of it. The first six lines of the triangle are shown
below:

1
1 1

1 2 1

13 3 1

14 6 4 1

1 5 10 10 5 1

Using a zero-based counting system and denoting the number in the nth
row and the column by (pascal-triemgle n k), (pascal-tricingle 4
kth.

2) is 6, and (pascal-triamgle 5 1) is 5. The algorithm that we use to


build the triangle line by line says that (pascal -triangle n k) is the sum
of (pascal-trieuigle (- n 1) (- k 1)) and (pascal-triangle (- n 1)
k). We consider that each row of the triangle is continued with a zero at each
end. Use this algorithm to define the procedure pascal-triangle, which we
use in several exercises in this section. Analyze this algorithm to determine the
number of additions performed when (pascal-trieingle n k) is computed.
Test your procedure on:

(pascal-triangle 10 5) => 252


(pascal-triangle 12 6) ^
^ 924
3432
(pascal-triangle 14 7)
(pascal-triangle 16 8) ==> 12870

11.2 Assignment and State 357


Exercise 11.3: timer2
Write the definition of a procedure timer 2 that finds the time elapsed from
the time a procedure proc is called to the time when the value is returned,
assuming that the procedure proc is a procedure of two arguments. Test
your procedure on the procedure pascal-triemgle, defined in the preceding
exercise, when the following procedure calls are made: (pascal-triangle
10 5), (pascal-trisoigle 12 6), (pascal-triangle 14 7), and (pascal-
triangle 16 8).

Exercise 11. 4: combinations


It can be shown that (pascal-triangle n k) represents the number of dif-

ferent ways k objects can be selected from a list of n distinct objects. This
number is often denoted by [^). The notation n! is used for the factorial of
n, which we compute with the procedure fact. It can be shown that the
number (^) can be computed using the formula f.,(^_f^\,
- Write the definition
of a procedure combinations that uses this formula instead of the algorithm
given in Exercise 11.2. Compare it with pascal-triangle by timing it for the
values of the arguments given in Exercise 11.2. Also compute (combinations
100 50).

Exercise 11.5: lookup2


Write the definition of a procedure lookup2 that takes five arguments: two
Scheme objects objl and obj2, and a list of triples trilist and a success
and a failure continuation. It searches through the list from beginning to end
looking for a triple in which the first element is ob j 1 and the second element is
obj2. If such a triple is found, it passes that triple to the success continuation.
Otherwise, the failure continuation is invoked. Test your procedure on:

(looJnip2 'a 'c'((a b 5) (a c 7) (b c 9))


(lambda (tr) tr) (lambda ()'())) =* (a c 7)
(lookup2 'a 'c '((a b 5) (c a 7) (b c 9))
(lambda (tr) tr) (lambda ()'())) => ()

Exercise 11.6: memoize2


Write the definition of the procedure memoize2, which memoizes a procedure
proc of two arguments.

Exercise 11.7
Memoize the procedure pascal-triangle in Exercise 11.2 to define a pro-

cedure memo-pascal-triangle in a manner analogous to the definition of

358 Mutation
)

Program 11.11 Mystery program for Exercise 11.10

(define nystery
(lambda (a b Is)
(let ((Is* Is) (ans '()) (goto (lambda (label) (label))))
(letrec
( (push
(Isuabda
(cond
((null? Is*) (goto pop))
((eq? (czu: Is*) a) (push! b) (goto reduce))
((eq? (car Is*) b) (push! a) (goto reduce))
(else (push! (car Is*)) (goto reduce)))))
(reduce
(Isunbda
(set! Is* (cdr Is*))
(goto push)))
(pop
(lambda ()
(cond
((empty?) eins)

(else
(set! ans (cons (top) ans))
(pop !

(goto pop))))))
(goto push)))))

memo-fib in this section. Time both a call of pascal-triangle and a call of


memo-pascal-triangle on each of the arguments used in Exercise 11.2.

Exercise 11.8: timer*


Define a procedure timer* that times a procedure of an arbitrary number of
arguments. For example, if proc is a procedure of four arguments and we
want to time the application (proc 1 2 3), we would call (timer* proc
10 2 3). Use the unrestricted lambda and apply.
Exercise 11.9
In the imperative-styleprogram for swapper, the stack and its operations were
defined nonlocally. Rewrite this program with the stack and its operations
defined locally within the definition of svapper and test your procedure on
the example given in Figure 11.10.

11.2 Assignment and State 359


Exercise 11.10
In the imperative-style program for swapper, we used while loops to repeat
certain steps when a given condition is true. In some languages, while loops
are not implemented so another device must be used. Such languages often
use a "goto" statement as a means of returning control to a previous step in
the program. As in the first imperative version of member? in Program 11.7,
we can simulate a goto statement by invoking a procedure of no arguments
(a thunk). Program 11.11 is a mystery program that is written in imperative
style and invokes various thunks to move the control to the body of the thunks.
Assume that a global stack stk is initially empty. What is returned when we
invoke:

(mystery 'a 'z '(crazy))

11.3 Box-and-Pointer Representation of Cons Cells

The box-and-pointer representation gives us a convenient graphical way of


visualizing the objects constructed using cons. An object that is not a pair,
such as a number, a symbol, or a boolean, is denoted by enclosing the object in
a box we put a square or rectangle around the object). For example, we
(i.e.,

represent the number 5 by enclosing the numeral 5 in a box. The constructor


cons produces a pair represented by a cons cell, which is a double box (a
horizontal rectangle divided into two boxes by a vertical line) with a pointer
(arrow) emerging from the center of each of the two boxes. The pointer
emerging from the center of the box on the left points to the box containing the
car of the pair represented by the cons cell. The pointer from the center of the
box on the right points to the box containing the cdr of the pair represented
by the cons cell. Figure 11.12(a) shows the box-and-pointer representation of
the improper list (or dotted pair) (cons 3 4). The pointer from the left side
points to the ceir, which is 3, and the pointer from the right side points to the

cdr, which is 4. When (cons 3 4) is entered into Scheme, the improper list

(3 , 4) is returned. We call the pointer from the left side of a cons cell the
car pointer and the pointer from the right side of a cons cell the cdr pointer.
The value of (cons 3 (cons 4 5)) is represented by two cons cells, one
for each cons. Figure 11.12(b) shows the box-and-pointer configuration for

this value. The car pointer of the first number 3, and


cons cell points to the
the cdr pointer of the first cons cell points to the second cons cell. The car
pointer of the second cons cell points to the number 4, and the cdr pointer
of the second cons cell points to the number 5. In this way, we can build up

360 Mutation
I
I
-j
— >[A' —
1 I

>^ 5

yt

(cons 3 4) (cons 3 (cons 4 5))


(a) (b)

Figure 11.12 Box-and-pointer diagrams

3L >r yr

3 3 l4 5

(cons 3 '()) (cons 3 (cons 4 (cons 5 '())))

(a) (b)

Figure 11.13 Box-and-pointer diagrams for proper lists

the box-and-pointer representations of the values of more complicated cons


expressions.
We now look at the representation of a proper list. We begin with the
list (cons 3 '
()), for which Scheme displays (3). Once again a cons cell is
created by cons, and this time the car pointer points to the number 3. But
how shall we represent the fact that the cdr pointer points to () ? We indicate
that the cdr is the empty list by drawing a diagonal line in the right half of

the cons cell. This is illustrated in Figure 11.13(a).


The list (cons 3 (cons 4 (cons 5 '()))), which appears on the screen
as (3 4 5), is represented as the linked cells in Figure 11.13(b). Another
interesting list to consider is

(cons (cons 3 '()) (cons 4 (cons 5 '())))

which appears on the screen as ((3) 4 5). The box-and-pointer represen-

11.3 Box- and- Pointer Representation of Cons Cells 361


tation contains four cons cells as illustrated in Figure 11.14. The first cons
creates a cell in which the car pointer points to the cons cell created by the
second cons, in which the car pointer points to 3 and the cdr pointer indicates
( ) . The cdr pointer of the first cons cell points to the cons cell created by
the third cons. The car pointer in the third cons cell points to 4, and its cdr
pointer points to the cons cell created by the fourth cons. In this fourth cons
cell, and the cdr pointer indicates (). Thus each
the car pointer points to 5,

cons in an expression creates a new cons cell in which the car pointer points
to the car part and the cdr pointer points to the cdr part of the cell.

»
3

(cons (cons 3 '()) (cons 4 (cons 5 '())))

Figure 11.14 Box-and-pointer diagram

If we define a to be (cons 3 '


()) by writing

(define a (cons 3 '()))

we can indicate this binding by a pointer from the name a to the cons cell

created by cons, as illustrated in Figure 11.15(a). If we now use set! to


change this binding, say

(set! a (cons 4 (cons 5 '())))

we can think of this as disconnecting the pointer from a to the linked cells
representing (cons 3 '()) and connecting it to the linked cells representing
(cons 4 (cons 5 '())), as illustrated in Figure 11.15(b).

S62 Mutation
>

a —

(define a (cons 3 '()))

(a)

4 5

(set! a (cons 4 (cons 5 '())))

(b)

Figure 11.15 Representing define and set!

Now suppose that a and b are defined as:

(define a (cons 1 (cons 2 '())))

(define b (cons (cons 3 '()) (cons 4 (cons 5 '()))))

as illustrated in Figure 11.16(a,b). We next define c to be:

(define c (cons a (cdr b)))

The cons in the definition of c creates a cons cell (to which c points) in which
the car pointer points to the same cell as does a and the cdr pointer points
to the same cell as does the cdr pointer of the cons cell to which b points.
This is illustrated in Figure 11.16(c). It is clear from this representation that

a and b have not been changed when we defined c, and a => (1 2), b =*
((3) 4 5), and c ==» ((1 2) 4 5).
The procedures set-car! set-cdr , ! , and append! which we discuss next,
,

actually do change the objects to which they are applied. For example, when
we same definitions of a and b given above and
use the illustrated in Fig-

ure 11.16(a,b), and invoke

(set-car! b a)

11.3 Box- and- Pointer Representation of Cons Cells 363


a
/"
(a)

/"
(b)

(c)

Figure 11.16 (define c (cons a (cdr b)))

then the car pointer of the cons cell to which b points is disconnected and
is made to point to the same linked cell as does a. This is illustrated in
Figure 11.17. Now:

b =» (Cl 2) 4 5)
a =* (1 2)

Thus b has been changed by set-car! we can say that set-car!


so that
caused a mutation in the list set-car in
structure of b. This enables us to use I

begin expressions since this mutation is a side effect. Let us compare b and c.
They are equal?, but not eq?. Also the application of set-car had the effect I

of disconnecting the previous car of b (see the dotted box in Figure 11.17)
which is now "garbage" to be recycled in the next "garbage collection." In
general, cells are garbage if they are not pointed to by nongarbage.
The invocation (set-cdr ! pair value) does the same kind of reconnecting
of the cdr pointer of pair so that it points to ua/ite. The box-and-pointer
diagrams in Figure 11.18 illustrate c and d defined by

(define c (cons 1 (cons 2 (cons 3 '()))))

(define d (cons 4 (cons 5 (cons 6 '()))))

364 Mutation
a >
/
^
5 |r

5K

Figure 11.17 (set-car! b a) in Figure 11.16

Let us first define h to be

(define w (cons c d))

so that w => ((1 2 3) 4 6 6). (See Figure 11.18.)


We pause to make an important observation about the predicate eq? . Two
items are the same in the sense of eq? if they point to the same object. From
Figure 11.18, we see that

(eq? (car w) c) #t

since they both point to the same chain of linked cells.

If we next call

(set-cdr! c d)

the cdr pointer of c is changed to refer to d (Figure 11.19) and now c =* (14
5 6). But the side effects of (set-cdr! c d) extend to all objects that have
pointers to c; now w =^ ((1456) 456). Thus care must be taken when
using procedures like set-cair ! and set-cdr! that cause mutations in the list

structure that unexpected or unwanted changes in other data objects do not

11.3 Box- and- Pointer Representation of Cons Cells 365


p

w 1
w w
{/

1 2 3

> —-^ H /
V >r

4 5 6

Figure 11.18 (eq? (car w) c) => #t

c ->
7^
— ji
^ /!i

1 i

I
2 J j
1

d
r

— — ,/ 1

[4 5 6

w >

Figure 11.19 (set-cdr! c d) in Figure 11.18

occur. The two procedures set-cao" and set-cdr cause mutations in


! ! the list

structure of data objects, but the values that they return are unspecified and
may differ in different implementations of Scheme. Thus to write programs
that are portable (run in various implementations of Scheme), it is necessary
to avoid using the values returned by these procedures.

S66 Mutation
u >

Figure 11.20 (define x (append c d))

The procedure append was defined in Program 4.1 as:

(define append
(lambda (Isl ls2)
(if (null? Isl)
ls2
(cons (cau: Isl) (append (cdr Isl) ls2)))))

If c and d are again defined as in Figure 11.18, and we define

(define u (cons c (cons d '())))

and

(define x (append c d))

then append makes a copy of c and changes the cdr pointer of the last cons
cell in this copy to point to d. (See Figure 11.20.) Then:

11.3 Box- and- Pointer Representation of Cons Cells 367


Program 11.21 last-pair

(define last- pair


(lambda (x)
(if (pair ? (cdr i))
(last -pair (cdr x))
x)))

Program 11.22 append!

(define append!
(lambda (Isl ls2)
(if (pair? Isl)
(begin
(set-cdr! (last-pair Isl) ls2)
Isl)
ls2)))

X (1 2 3 4 5 6)
c > (1 2 3)
d (4 5 6)
u ((3 L 2 3) (4 5 6))

In making the copy of c, x had to create three cons cells.


The procedure append! offers a more efficient way of appending one list to
another. However, it has side effects that must be considered, so it should not
be used indiscriminately. Let us begin by defining the procedure last -pair
(see Program 11.21), which takes as its argument a nonempty list and returns
the list consisting of the last value in the list. For example:

(last-pair '(1 2 3)) (3)

We then define append! in Program 11.22. Here last-pair cdr's down the
list Isl until it reaches the last pair in Isl. Then set-cdr! redirects the cdr
pointer to ls2 instead of the empty list. The last line in the begin expression
returns this mutated list Isl. For example, if we apply append! to the two
lists c and d defined above by writing

(define y (append! c d))

368 Mutation
>

\N
c — .^ — > >

y-

1 2 3

i
A
U w ^ >
~y
/"
y

/ X —
4 5

w
6

1 2 3

11
U k
W
- W

/
Figure 11.23 'define y (append! C i))
< ( in Figure 11. 2()

then y is obtained by connecting the cdr pointer of the Icist cons cell in c to
d. (See Figure 11.23.) We now have

y ^ (1 2 3 4 5 6)
c => (1 2 3 4 5 6)
d =^ (4 5 6)
X ^ (1 2 3 4 5 6)
u =* ((1 :> 3 4 5 6) (4 5 6))

The last result is a side effect of using append! on c, for the c, which also
appears in the definition of u, has been mutated. The value of x is not changed
because it originally makes a copy of c and has no pointer to c.Thus each
time we have a choice of using one of the procedures set-cao: !
, set-cdr or !
,

append ! , we must decide whether we want a copy of the original lists made
by using suitable procedures of cons, Ceir, cdr, and append or mutations of
the original lists taking into account the possible side effects. We must be

11.3 Box- and- Pointer Representation of Cons Cells 369


^^
^2
^ '

-^ j_
—1

u j]

Figure 11.24 Box-and-pointer diagram for Exercise 11.12

careful that undesirable side effects do not occur when using set-Cca- ! , set-
cdr!, and append!. In the next chapter, we shall see examples where these
procedures can safely be used because the variables affected are local and the
side effects can be controlled.

Exercises

Exercise 11.11
Draw a box-and-pointer diagram for the following:

(let ((x (list 1 2 3)))


(let ((y (list 4 5 6)))
(let ((z (cons x y)))
(set-cdr! x y)
z)))

Exercise 11.12
Write a let expression in the style of Exercise 11.11 that generates the entire
structure shown in the box-and-pointer diagram in Figure 11.24.

Exercise 11.13

a. Draw a box-and-pointer diagram for the following:

(let ((x (cons 11)))


(set-caur! x x)
(set-cdr! x x)
x)

370 Mutation
4

b. Define a procedure that recognizes such cons cells.

c. Define a procedure that takes a list as its argument and removes all occur-
rences of such cons cells.

Exercise 11.1
Conduct the following experiment, explaining the results:

[1] (define mystery


(lambdaCx)
(let ((box (last-pair x)))
(set-cdr! box x)
x)))
[2] (define ans (mystery (list 'a 'b 'c 'd)))
[3] ans

Exercise 11.15
Let us consider only flat lists in this exercise. We know that we can write a
procedure that determines the length of a list. We allow, however, that the

cdr of the last cell of the list might point back to some portion of the list. For
example,

(let ((i (list 'a 'b 'c 'd 'e)))


(set-cdr! (last-pair x) (cdr (cdr x)))
x)

We can print such lists by invoking (writeln x); however, the printing of the
list will not terminate. Redefine writeln so that if it discovers one of these
lists, it prints something appropriate. For example,

(writeln x) ==^ (abcdecde ...)

Hint: Define a predicate cycle? which determines if a list is a flat cycle. Also,
reconstruct a list like (abcdecde ...) that has the string " ..." as
the last of its nine elements.

Exercise 11.16: efface


Create the box-and-pointer diagrams for x, y, z, a, a*, b, b*, c, and c* before
and after the invocation of efface in test-efface.

11.3 Box-and-Pointer Representation of Cons Cells 371


(define efface
(lambda (x Is)
(cond
((null? Is) '())
((equal? (car Is) x) (cdr Is))
(else (let ((z (efface x (cdr Is))))
(set-cdr! Is z)
Is)))))

(define test-efface
(lambda
(let ((x (cons 1 '())))
(let ((y (cons 2 x)))
(let ((z (cons 3 y)))
(let ((a (cons 4 z)) (a* (cons 40 z)))
(let ((b (cons 5 a)) (b* (cons 50 a)))
(let ((c (cons 6 b)) (c* (cons 60 b)))
(writeln x y z a a* b b* c c*)
(efface 3 c)
(writeln x y z a a* b b* c c*)))))))))

Exercise 11.17
Using the definition of efface from Exercise 11.16, describe the behavior of
(test-efface2) and (test-elf ace3). Explain the difference.

(define test-efface2
(lambda
(let ((Is (list 5 4 3 2 1)))
(writeln (efface 3 Is))
Is)))

(define test-efface3
(lambda
(let ((Is (list 5 4 3 2 1)))
(writeln (efface 5 Is))
Is)))

Exercise 11.18: smudge


Create the box-and-pointer diagrams for x, y, z, a, a*, b, b*, c, and c* before
and after the invocation of smudge in test-smudge.

372 Mutation
(define smudge
(Icunbda (x Is)
(letrec
((smudge/x
(lambda (Is*)
(cond
((null? (cdr Is*)) Is*)
((equal? (car Is*) x) (shift-down Is* (cdr Is*)))
(else (smudge/x (cdr Is*)))))))
(if (null? Is)
Is
(begin
(smudge/x Is)
Is)))))

(define shift-down
(lambda (boxl box2)
(set-car! boxl (car box2))
(set-cdr! boxl (cdr box2))))

(define test-smudge
(lambda ()

(let ((x (cons 1 '())))


(let ((y (cons 2 x)))
(let ((z (cons 3 y)))
(let ((a (cons 4 z)) (a* (cons 40 z)))
(let ((b (cons 5 a)) (b* (cons 50 a)))
(let ((c (cons 6 b)) (c* (cons 60 b)))
(writeln x y z a a* b b* c c*)
(smudge 3c)
(writeln x y z a a* b b* c c*)))))))))

Exercise 11.19: count-pairs


The procedure count-pairs counts the number of cons cells in a data struc-
ture. It is defined using the global variable seen-pairs* and the helping
predicate dont-count?.

(define *seen-pairs* '())

ll.S Box- and- Pointer Representation of Cons Cells SIS


.

(define coxint -pairs


(Izunbda (pr)
(if (dont-coxint? pr)

(begin
(set! seen-pairs* (cons pr *8een-pairs*))
(addl (+ (coiont -pairs (c«ir pr))
(count -pairs (cdr pr))))))))

(define dont- count?


(lambda (s)
(or (not (pair? s)) (member? s *seen-pairs*))))

a. Create a box-and-pointer diagram for y just prior to the invocation of


count-pairs in the procedure test-count -pairs.

(define test-count-pairs
(lambda ()

(let ((x (cons 'a (cons 'b (cons 'c '())))))


(let ((y (cons x (cons x (cons x x)))))
(set-cdr! (last-pair x) x)
(writeln (count-pairs y))
(count -pairs y)))))

b. Explain the behavior of test-connt-pairs. Why are the answers differ-


ent? Rewrite count -pairs functionally so that the two answers are the
same. Hint: Look back at the definition of vector-insert sort ! in Pro-

gram 10.5.

c. Rewrite count -pairs using local state, which gets changed with set !

Here is a skeleton:

(define count -pairs


(lambda (pr)
(covmt -pairs/seen pr '())))

(define count -pairs/seen


(lambda (pr seen-pairs)
(letrec
((count (lambda (pr) ...)))
(count pr))))

374 Mutation
d. Rewrite count-pairs using private local state. Hint: Look at the skele-
ton below. We must set seen-pairs back to the empty list just prior to
invoking count. Here is a skeleton:

(define count -pairs


(let ((seen-pairs "any list of pairs"))
(letrec
((count (lambda (pr) ...)))
(lambda (pr)
(set! seen-pairs '())
(count pr)))))

e. Rewrite count-pairs using private local state, setting seen-pairs to the


empty list after invoking count instead of before invoking count. This
way we know that seen-pairs is always the empty list before and after
invoking count -pairs.

The next seven problems are related. Work them in order and you will learn

about what computers cannot do.

Exercise 11.20: Turing Tapes


Consider a list that grows in both directions: (... cbaxy...). We call
such a list an unbounded tape, or just tape. We use a positive number of
O's as left and right borders to indicate where the interesting information on
the tape resides: (... OcbaxyO...). Any symbol would work as the
border symbol; no border value can appear within the interesting information
on the tape, for if it did then it would indicate a border. Included as part
of the data abstraction of a tape is a location on the tape. Tapes can be
read only a character at a time. For the purposes of this discussion, x is the
character being read on the above tape.
There are four procedures defined over tapes. The first one is at, which
takes a tape and returns the character being read. The second one is over-
write, which takes a character, c, and a tape, and returns an equivalent tape
except that the character being read is replaced by c. We can characterize
the relation between overwrite and at with the following equation. Let t be
a tape and let c be a character; then:

(at (overwrite c t)) = c

The and fourth procedures are left and right. These take a tape
third
and return an equivalent tape except that the character being read is the one

11.3 Box- and- Pointer Representation of Cons Cells 375


just to the left (or right) of the one that was previously being read. In our
example, this would mean that the character being read is now a (or y). Since

the tape is unbounded in both directions, there is no concern about falling off

the tape. We have the identities:

(left (right tape)) = tape = (right (left tape))

In our use of tapes, we always do overwrite followed by either left or


right but not both. We refer to this operation as reconfiguring the tape. In
order to reconfigure a tape, we need a character to write and a direction in
which to move:

(define reconfigure
(lambda (tape character direction)
(if (eq? direction 'left)
(left (overwrite chciracter tape))
(right (overwrite character tape)))))

We now consider a possible representation of tapes. In this representation


a tape is composed of two non-null, finite lists. We call these two lists left part
and right part. The left part contains everything to the left of where we are
reading until the left end of the tape, but it is reversed. In our example, that
is (a b c 0). The right part contains everything from where we are reading
until the right end of the tape. In our example above, that is (x y 0). Thus,
the tape is represented by the list ((a b c 0) (x y 0)). We can now define
at and overwrite:

(define at
(lanbda (tape)
(let ((right-part (2nd tape)))
(.car right-paurt))))

(define overwrite
(lambda (chau: tape)
(let (deft-part (Ist tape)) (right-paurt (2nd tape)))
(let ((new-right-part (cons cheur (cdr right-part))))
(list left-part new-right-part)))))

We have only to define the procedures left and right. Let us consider
what is involved in moving to the right. In our example this would mean that
we are looking at y. If that is so, then the right part would become (y 0).
What would happen to the x? Since it is now to the left of where we are

376 Mutation
)

reading, it would be moved into the left part and would become the first item
in the left part. Here is a first try at right:

(define right
(lambda (tape)
(let (deft-part (1st tape)) (right -part (2nd tape)))
(let ((new-left-part (cons (car right -part) left-part))
(new-right-peirt (cdr right-part)))
(list new-lef t-petrt new-right-part)) ) ))

This is very close to correct, but it might violate the restriction that the left

part and the right part must be non-null. Consider invoking right on the
tape ((y x a b c 0) (0)). Using this incorrect version of right, the new
tape would become ((OyxabcO) ()), and that violates the non-null
condition that each part must satisfy. If new-right-part is the empty list,

we must replace it by (0), which represents the right end of the tape. Here
is the improved version of right:

(define right
(lambda (tape)
(let (deft-part (1st tape)) (right -part (2nd tape)))
(let ((new-left-part (cons (ceir right -part) left-peirt))
(new-right-part (cdr right-part)))
(list new-lef t-peurt (check-null new-right-pairt))))))

(define check-null
(Icimbda (part)
(if (null? part)
(list 0)
part)))

Write the procedure left, and test reconfigure with the procedure below:

(define test-reconfigure
(Isunbda
(let ((tapel (list (list 'a 'b 'c 0) (list 'x 'y 0))))
(let ((tape2 (reconfigure tapel 'u 'right))
(tapeS (reconfigure tapel 'd 'left)))
(let ((tape4 (reconfigure tape2 'v 'right))
(tapes (reconfigure tape3 'e 'left)))
(let ((tape6 (reconfigure tape4 'w 'right))
(tape? (reconfigure tapeB 'f 'left)))
(let ((tapes (reconfigure tape6 'x 'right))
(tape9 (reconfigure tape? 'g 'left)))
(list tapes tape9) ) ))) ))

11.3 Box- and- Pointer Representation of Cons Cells 377


Exercise 11.21: list->tape, tape->list
Define a pair of procedures that builds an interface for handling tapes. The
first procedure, list->tape, takes a list, Is, of characters that contains no
O's and produces a tape, t, with the condition that Issame as (right
is the
t) minus trailing zeros. For example, if Is is (x y), then (list->tape Is)
returns ((0) (x y 0)). The procedure tape->list takes a tape and returns
a list. The resultant list does not keep track of where on the tape it is reading.
Hence, it is not always the case that (list->tape (tape->list t)) = t;
however, it is always the case that (tape->list (list->tape Is)) = Is.
For example, if the tape, t, is ((a b c 0) (x y 0)), then (tape->list t)
is (c b a X y), but (list->tape (c b a x y)) is ((0) (c b a x y 0)),
'

not ((a b c 0) (x y 0)). Not only must the left part be reversed, but no
O's should appear in the resultant list. Rewrite test-reconfigure so that it

uses list->tape and tape->list.

Exercise 11.22
In the procedure test-reconf iguxe from the previous exercise, we used
tapel twice. Generally that does not happen. More frequently, a tape is

used as an argument in an iterative program. Consider the following experi-


ment:

[1] (define shifter


(letrec
((shift-to-0
(lambda (tape)
(let ((c (at tape)))
(cond
((equal? c 0) tape)
(else (shift-to-0 (reconfigure tape c 'right))))))))
shift-to-0))
[2] (shifter (list (list 0) (list 'a 'b 'c 0)))

When the tape is used in this fashion, we no longer need to make a new copy
each time we reconfigure the tape. For example, we can redefine overwrite
to change the value that at returns just by using set-car!:

(define overwrite
(lambda (char tape)
(let ((right-part (2nd tape)))
(set-csir! right -part char)
tape)))

378 Mutation
)

In changing the definition of overwrite, we exchanged one invocation of set-


car! for three uses of cons and one use of cdr, but test-reconfigure no

longer produces the same result. Why? Redefine right and left to use as
few invocations of cons as possible. Test shifter as in [2].

Exercise 11.23
We can write interesting procedures that begin with an empty tape (i.e,

(list->tape '())). Test the procedures below using an empty tape and
determine which ones do not halt:

(define busy-beaver
(letrec
( (loopright
(lambda (tape)
(let ((c (at tape)))
(cond
((equal? c 'a)
(loopright (reconfigure tape 'a 'right)))
(else (maybe-done (reconfigure tape 'a 'right)))))))
(maybe-done
(lambda (tape)
(let ((c (at tape)))
(cond
((equal? c 'a) (reconfigure tape 'a 'right))
(else (continue (reconfigure tape 'a 'left)))))))
(continue
(lambda (tape)
(let ((c (at tape)))
(cond
((equal? c 'a)
(maybe-done (reconfigure tape 'a 'left)))
(else (loopright (reconfigure tape 'a 'right))))))))
loopright)

(define endless-growth
(letrec
((loop
(leunbda (tape)
(let ((c (at tape)))
(cond
((equal? c 0)
(loop (reconfigure tape 'a 'right))))))))
loop))

11.3 Box-and-Pointer Representation of Cons Cells 379


(define perpetual-motion
(letrec
((this-way
(lambda (tape)
(let ((c (at tape)))
(cond
((equal? c 'a)
(that-way (reconfigure tape 'right)))
(else (that-way (reconfigure tape 'a 'right)))))))
(that-way
(lambda (tape)
(let ((c (at tape)))
(cond
((equal? c 'a)

(this-way (reconfigure tape 'left)))


(else (this-way (reconfigure tape 'a 'left))))))))
this-way))

(define pendulum
(letrec
( (loopright

(Icifflbda (tape)

(let ((c (at tape)))


(cond
((equal? c 'a)
(loopright (reconf igrire tape 'a 'right)))
(else (loopleft (reconfigure tape 'a 'left)))))))
(loopleft
(lambda (tape)
(let ((c (at tape)))
(cond
((equal? c 'a)
(loopleft (reconfigure tape 'a 'left)))
(else (loopright (reconfigure tape 'a 'right))))))))
loopright))

Exercise 11.24
Each of the procedures in the previous exercise looks about the same. Each
takes a tape as an argument. Then it reconfigures the tape according to
the current character and either returns the reconfigured tape or passes it

along to another procedure. We can think of each cond line as a list of five
elements. For example, the first cond line of continue in busy-beaver could
be represented by: (continue a a left maybe-done). We can interpret
this line as follows: From the state of continue, if there is an a, overwrite it

380 Mutation
)

with an a,move left, and consider only the lines that start with raaybe-done.
The entire busy-beaver procedure could be represented by a list of all the
transcribed cond lines:

(define busy-beaver-lines
'((loopright a a right loopright)
(loopright a right maybe-done)
(maybe-done a a right halt)
(maybe-done a left continue)
(continue a a left maybe-done)
(continue a right loopright)))

We use the convention that we start the computation at (car (car busy-
beaver-lines)). We also assume that halt is self-explanatory.
Using the representation of tapes that we have developed thus far, define a
procedure run-lines that takes a set of lines, like the busy-beaver-lines,
and a tape and returns the same result as (busy-beaver tape). The proce-
dure below will get you started. You need only define the procedures called
by run-lines.

(define run-lines
(lambda (lines tape)
(letrec
((driver
(lambda (state tape)
(if (eq? state 'halt)
tape
(let ((matching-line
(find-line state (at tape) lines)))
(driver
(next-state matching-line)
(reconfigure
tape
(next-char matching-line)
(next-direction matching-line) ))))))
(driver (current-state (car lines)) tape))))

Such a set of lines is called a Turing machine, named for Alan M. Turing.
Turing claimed that with a small set of characters, including the 0, he could
use his machines to compute whatever a computer could. Then he showed
that no one can write a procedure test-lines, like run-lines, that takes

11.3 Box- and- Pointer Representation of Cons Cells 381


an arbitrary machine and an arbitrary tape and determines whether (run-
lines machine tape) halts. This result is so important that it has been
given a name, the halting problem. All this was done in 1936!

Exercise 11.25
Often it is possible to remove a test by careful design. In the definition of re-
configure, there is a superfluous test. Rewrite busy-beaver replacing 'left
by left and 'right by right, so that a revised definition of reconfigure
works.

Exercise 11.26: New Representation


Consider a representation of tapes that keeps what it is reading separately.
For example, we might choose a list of three elements

(at left-part right-part-less-at)

Then we could redefine overwrite and right cis follows:

(define overwrite
(lambda (char tape)
(let ((left (2nd tape)) (right (3rd tape)))
(list char left right))))

(define right
(lambda (tape)
(let ((char (1st tape))
(left (2ud tape))
(right (3rd tape)))
(list (car right)
(cons char left)
(check-null (cdr right))))))

Use this representation of tapes and test busy-beaver. Then redefine all
necessary procedures so that the use of cons is minimal.

382 Mutation
12 Object-Oriented Programming

12.1 Overview

A different perspective on computing is provided by object-oriented program-


ming. In this style of programming, certain objects are defined that respond
to messages passed to them. Figuratively, we can think of an object as a
computer dedicated to solving a particular type of problem. The input is

the message passed to the object, the object does the computation, and the
output is the value returned by the object. In this chapter, we see how such
objects are defined, and we illustrate the use of objects to define such data
structures as stacks and queues.

12.2 Boxes, Counters, Accumulators, and Gauges

In Chapters 3 and 5, the concept of data abstraction was discussed and il-

lustrated. We saw there that we can write programs that are independent
of the representation of the data and are based on certain predefined basic
procedures, including the constructors and selectors used on the data type.
The actual representation of the data was then used only in defining these
basic procedures. We develop the idea of data abstraction further by defining
certain objects that are combined with certain operations. It is not necessary
for users to know how these objects and operators are implemented in order
to use them. They only have to know the interface. An example of such an
object is a stack that has associated with it such operations as push! and
pop ! . This offers a degree of security in the handling of data and makes it

possible to change the internal representation of the object without the user's
being aware of any changes. Before looking at stacks and queues, we introduce
the Ccise expression, which makes it easier for us to define the various objects
we shall study.

12.2.1 The Case Expression

Scheme provides a special form with keyword case that selects one of a se-

quence of clauses to evaluate based upon the value of an argument (or mes-
sage) that it is passed. To see how case is used, let us first look at a procedure
that tells us whether a letter is a vowel or a consonant. We can define it as

(define vowel-or-consoneint
(lambda (letter)
(cond
((or (eq? letter 'a)
(eq? letter 'e)
(eq? letter 'i)
(eq? letter 'o)
(eq? letter 'u))
'vowel)
(else 'consonant))))

This procedure can also be defined using the special form case as follows:

(define vowel-or-consonant
(leUDbda (letter)
(case letter
((a e i u) 'vowel)
(else 'consonant))))

The value of letter is matched with each of the items (keys) in the list in
the first clause of the case expression. If there is a match, the expression
following the list of keys is evaluated and returned as the value of the case
expression. Thus if letter evaluates to one of a, e, i, o, or u, vowel is

returned. Otherwise, the next clause is evaluated, and since in this case it is

the else clause, consonant is returned. In case letter evaluates to one of the
five vowels, it is more convenient to use the case expression, which matches it

with the possible key values rather than the cond expression, which must list

a separate test for each possibility.


The syntax of case is

384 Object-Oriented Programming


(case target
(.keys expri expT2 )
(else expri expr2 .))

where target is an expression that is evaluated and its value is compared


with the keys. Each clause begins with keys, which is a list of items each of
which is matched (using eqv?) with the value of target to decide which of the
clauses will be selected for evaluation. When the first such match is found, the
expressions expr . . . following the keys are evaluated in order and the value
of the last is returned (there is an implicit begin following each keys). If no
match is found and the optional else clause is present, then the expressions
expr ... in the else clause are evaluated. If no else clause is present, then
some unspecified value is returned. It is good programming style always to
include an else clause even if only for reporting an error.
Below are some additional simple examples demonstrating the use of case:

[1] (case 'b


((a) (display "a was selected: ") (cons 'a '()))
((b) (display "b was selected: ") (cons 'b '()))
((c) (display "c was selected: ") (cons 'c '()))
(else (display "None were selected.")))
b was selected: (b)

[2] (case (remainder 35 10)


((2468) "positive and even")
((13579) "positive and odd")
((-2 -4 -6 -8) "negative and even")
((-1 -3 -5 -7 -9) "negative and odd")
(else "zero"))
"positive and odd"

In the various objects we shall define in this chapter, we shall use internal
representations of the data, which are not supposed to be apparent to the user.
In order to secure the data structures used, we introduce, in Program 12.1,
the procedure f or-eff ect-only, which evaluates its operand to perform the
side effects and then returns the string "unspecified value". Following our
usual convention, we will not display "unspecified value".

12.2.1 Boxes, Counters, Accumulators, and Gauges 385


,

Program 12.1 for-eflect-only

(define f or-eff ect-only


(lambda (it em- ignored)
"unspecified value"))

12.2.2 Boxes

A box is a place in which a value can be stored until it is needed later. A new
box containing a given initial value is created by the procedure box-maker.
There are five operations that we shall perform on a box. We use one of
these operations to put a value into the box and another to show the value
in the box. The operation that puts a value into the box is called update!
and the operation that shows the value in the box is called show. Another
useful operation, called swap! ,
puts a new value into the box and returns the
old contents of the box. The operation called reset resets the value stored
!

in the box to its initial value. The ability to perform a reset operation is
somewhat unusual. The operation type, specified for all objects, tells what
kind of object is being sent a message. In this case the type is "box". In
general, the operations that are performed on an object are called methods.
In the case of a box, there are five methods: update !
, show, swap !
, reset !

and type.
The objects, such as boxes, are themselves procedures. To apply one of
themethods to an object, we invoke the object on the (quoted) name of the
method followed by any additional arguments appropriate for that method.
We then say that we send the name of the method and any additional argu-
ments as a message to the object. We can use the call structure:

(object ' m.ethod-nam.€ operand ...)

where object is sent the message consisting of the quoted method name and
zero ormore operands. On the other hand, we find it more suggestive and,
in fact,more flexible to introduce the procedure send, which is used to send
the message to the object. When we use send, we use the call structure

(send object ' m,ethod-name operand ...)

The following shows a typical construction of a box box-a that is initialized

386 Object-Oriented Programming


with (+ 3 4) and a box box-b that is initialized with 5. We shall describe

the actual mechanism for constructing boxes after looking at the example.

[I] (define box-a (box-maker (+ 3 4)))


[2] (define boi-b (box-maker 5))
[3] (send box-a 'show)
7

[4] (send box-b 'show)


5
[5] (send box-a 'update! 3)
[6] (send box-a 'show)
3
[7] (send box-b 'update! (send box-a 'swap! (send box-b 'show)))
[8] (send box-a 'show)
5
[9] (send box-b 'show)
3
[10] (send box-a 'reset!)
[II] (send box-a 'show)
7
[12] (send box-a 'type)
"box"
[13] (send box-a 'update 27)
Error: Bad method neune: update sent to object of box type.

In [3] , in order to see what iswe send the message show


stored in box-a,
to box-a, and in [5] , in order to in box-a, we send
change the value stored
it the message update! and the new value 3. In [13], we forgot to include

the exclamation mark on the word update, and an error was signaled. The
sending of these quoted method names and arguments as messages to the
objects leads to a style of programming referred to as message-passing style.
In Program 12.2, we define box-maker. It takes as its argument an initial

value stored in the box. It returns a procedure that takes an arbitrary num-
ber of arguments and is hence defined using the unrestricted launbda whose
parameter list is denoted by msg. Each invocation of box-maker returns an
object that we refer to as a box. Thus, in our experiment presented above,
box-a and box-b are examples (or instances) of boxes. Also, in [3] , the mes-
sage consists of the single item ' show, whereas in [5] , the message consists
of two items, the method name 'update! and the operand 3. In the code
given below for box-maker, we use 1st and 2nd to denote car and cadr.

12.2.2 Boxes, Counters, Accumulators, and Gauges 387


Program 12.2 box-maker

(define box-maker
(leunbda (init- value)
(let ((contents init-value))
(lambda msg
(case (1st msg)
((type) "box")
((show) contents)
((update!) (for-ef feet-only (set! contents (2nd msg))))
((swap!) (let ((ans contents))
(set! contents (2nd msg))
ans) )

((reset!) (for-ef feet-only (set! contents init-value)))


(else (delegate base-object msg)))))))

Program 12.3 delegate

(define delegate
(lambda (obj msg)
(apply obj msg)))

respectively. We also use msg to denote the message, delegate is defined in


Program 12.3.

In order to be able to reset the box to its initial value, init-value, it is

necessary to preserve that value. Thus a local variable contents is introduced


to hold the current value stored in the box. It is initialized with init-value.
In the case clause that matches swap !
, a let expression binds ans to the
current contents of the box. Then set! puts the new value into the box,
but the old value ans that was stored in the box is Whenever
returned.
the else clause is reached, no match was found for the method name, so the
message is passed on (or delegatedy to another object, which attempts to
respond to it. (We find that it is more suggestive to use the procedure name

^ When an object cannot respond to a message, there are mechanisms other than delegation

which have been developed. One common mechanism is inheritance. We have chosen to
use delegation instead of inheritance; however, aJl programs expressible with inheritauice
are also expressible with delegation.

388 Object-Oriented Programming


)

Program 12.4 base-object

(define base-object
(lambda msg
(case (1st msg)
((type) "base-object")
(else invalid-method-name-indicator) ) )

Program 12.5 send

(define send
(lambda args
(let ((object , (car args)) (messag e (cdr args)))
(let ((try (apply object messag e)))
(if (eq? inval id-method-name- indicator try)
(error "Bad method name:" (car nessage) 1

"sent to object of 1

(object 'type)
"type.")
try)))))

delegate instead of apply to pass the message on to another object, although


the two procedures delegate and apply behave the same by our simplification
rule.) In this case, the object to which the message is delegated is the base-
object, which returns invalid-method-naiine-indicator, which is bound to
the string "uiLknown".

(define invalid-method-name-indicator "iinknown")

The procedure send then generates the appropriate invocation of error. The
definitions of base-object and send are contained in Programs 12.4 and 12.5.

We shall define many diff'erent types of objects in this chapter using object
makers similar to box-maker. These will each contain an else clause that
must handle method names for which there is no match. One of the major
advantages of using send is that all these else clauses will have exactly the
same call structure

(else (delegate object msg))

and send takes the appropriate action. When writing the definitions of the
object makers and when using the objects, we must remember that:

12.2.2 Boxes, Counters, Accumulators , and Gauges 389


1. Every object should respond to the method name type.
2. When no match is found for a method name, the else clause should delegate
the message to some object, which in some cases may be base-object.

3. Use send to pass messages to objects.

In this implementation of a box, the data structure used to store a value in


the box is just a variable. The user is not concerned with this fact when using
the box to store the value. We could have used a different data structure,
such as a cons cell, m which to store the value. In the program below for box-
maker, init-value is initially stored in the car position of a cons cell, which
we denote by cell. The procedure set-car! is used to change the value
stored in the box. This alternative version of box-maker is in Program 12.6.

Program 12.6 box-maker (Alternative)

(define box-maker
(lambda (init-value)
(let ((cell (cons init-value "any value")))
(lambda msg
(case (1st msg)
((type) "box")
((show) (car cell))
((update!) (for-ef feet-only (set-car! cell (2nd msg))))
((swap!) (let ((sms (cju: cell)))
(set-car! cell (2nd msg))
ans))
((reset!) (for-ef feet-only (set-car! cell init-value)))
(else (delegate base-object msg)))))))

12.2.3 Counters

A counter is an object that stores an initial value and each time it is called,

the stored value is changed according to some fixed rule. The counter has two
arguments: the initial value stored and the procedure describing the action
to be taken each time the counter is updated. For example, (counter-maker
10 subl) is a counter with initial value 10 that decrements the counter by
1 when it is updated. The counter responds to the method names: type,
update !
, show, and reset !The definition of counter-maker follows:
.

390 Object-Oriented Programming


Program 12.7 counter-maker (Methods Disabled)

(define counter-maker
(lambda (init-v«ilue \in«iry-proc)
(let ((total (box-meJcer init-value)))
(lambda msg
(case (1st msg)
((type) "counter")
((update!) (let ((result (imary-proc (send total 'show))))
(send total 'update! result)))
((swap!) (delegate base-object msg))
(else (delegate total msg)))))))

The counter locally defines the box total, which contains the initial value
stored in the counter. When the counter receives the message consisting of the
method name update!, the unary update procedure unsury-proc is applied
to the value stored in the box total to obtain the new value which is then
stored in total. For example, if we wanted the counter to count up by 1 each
time it is updated, we can use addl as the unary update procedure. To create
a counter with initial value that increases the stored value by 6 each time
it is updated, we write:

(coimter-meJcer (launbda (x) (+ 5 x)))

The counter is not supposed to respond to swap ! . Thus if such messages are
sent to a counter, they are delegated to base-object rather than to a box,
which does respond to swap ! . Since the counter responds to the messages
show and reset ! the same as the box total, the else clause merely passes
these messages to total. Thus the message show displays the value currently
stored in the counter, and reset! resets the counter to its initial value. The
fact that the response of the counter to these messages can be found by passing
them to the box is called delegation. The work of the counter is "delegated"
to the behavior of the box.
The approach of catching the method names that are to be disabled, like
swap!, is way of supporting the interface. Another alternative is to
only one
catch all method names to be enabled. Thus, we can rewrite count er-
the
metker using this view. As long as we delegate to the base object all the
method names that are meaningless, we can use either approach. On one
hand we are throwing the illegal method names out (i.e., disabling them), and
on the other, we are delegating the legal ones (i.e., enabling them). In any

12. 2. S Boxes, Counters, Accumulators, and Gauges 391


Program 12.8 counter-maker (Methods Enabled)

(define counter-maker
(lambda (init -value unary-proc)
(let ((total (box-maker init-value)))
(lambda msg
(case (1st msg)
((type) "counter")
((update!) (send total 'update!
(unary-proc (send total 'show))))
((shoB reset) (delegate total msg))
(else (delegate base-object msg)))))))

event, both have the same effect, and each has aspects that recommend it. If

we are delegating to an object with many legal method names, and only a few
illegal ones, then we should disable illegal method names; otherwise we are
free to choose to enable legal method names. A version of counter-maker,
which enables legal method names, is presented in Program 12.8.

12.2.4 Accumulators

An accumulator is an object that has the initial value init-value. Each time
it receives a message consisting of the method name update! and a value v,

the binary update procedure binary-pro c is applied to the value stored in


the accumulator and v; the result is the new value stored in the accumulator.

For example, if ace is an accumulator that initially stores the value 100 and
has subtraction (-) as its binary update procedure, it is defined by

(define ace (accumulator-maker 100 -))

and

(send ace 'update! 10)

causes the number 90 to be stored in ace. If we then update ace with 25, we
write

(send ace 'update! 25)

and the number 65 is stored in the accumulator.

392 Object-Oriented Programming


) !

Program 12.9 accumulator-maiker

(define accumulator-meiker
(IcUDbda (init-value binary-proc)
(let ((total (box-maker init-value)))
(leimbda msg
(case (1st msg)
((type) "accumulator")
((update!
(send total 'update!
(binary-proc (send total 'show) (2nd msg))))
((swap!) (delegate base-object msg))
(else (delegate total msg)))))))

The accumulator uses a box, called total, to store its values. In addition to
responding to the message consisting of update ! and a value, it uses delegation
to pass such messages as show and reset ! to the box total. Program 12.9
contains the code for accumulator-maJcer.

12.2.5 Gauges

A gauge is the last object to be defined in this section. A gauge is similar to


a counter, but it has two unary update procedures, one to count up and the
other to count down. The one to count up is called unary-proc-up, and the
one to count down is called unary-proc-down. The gauge responds to two
update messages up! and down!. It stores its values in a box called total.
When the gauge receives the message up !
, the update procedure unary-proc-
up is invoked on the value stored in total to get the new value stored in total.
Similarly, when the gauge receives the message down!, the update procedure
unary-proc-down is invoked on the value stored in total to get the new value
stored in total. The gauge also responds to the messages show and reset
by delegation from total. For example, to create a gauge g with initial value
10, which either adds 1 or subtracts 1, we write

(define g (gauge-maker 10 addl subl))

and

(send g 'up!)

causes the number 11 to be stored in g, while

12.2.5 Boxes, Counters, Accumulators, and Gauges 393


.

Program 12.10 gauge-maker

(define gauge-mciker
(lambda (init- value unaury-proc-up uneory-proc-down)
(let ((total (box-maker init-value)))
(lambda msg
(case (1st msg)
((type) "gauge")
((up!) (send total 'update!
(unsury-proc-up (send total 'show))))
((down!) (send total 'update!
(unary-proc-down (send total 'show))))
((swap! update!) (delegate base-object msg))
(else (delegate total msg)))))))

(send g 'down!)

returns the number stored in g to 10. Program 12.10 contains the definition
of gauge-maker.

Exercises

Exercise 12.1: acc-max


Define an accumulator acc-meuc that has initial value and each time it is

updated, it compares the value stored with a new value and stores the majc-
imum of the two. Then test acc-meix by updating it in succession with the
numbers 3, 7, 2, 4, 10, 1, 5 and find the maocimum by passing acc-max the
shov message.

Exercise 12.2: double-box-meJter


Define a procedure double-box-maOcer that takes two arguments, iteml and
item2, and stores these values in two boxes, the left and right, respectively.
An instance of double-box-m€dcer responds to the following messages: show-
lelt, show-right, update-lelt !
, update-right •
, and reset !

Exercise 12.3: accumulator-maJcer, gauge-meiker


In the definitions of accmnulator-maker and gauge-meJcer method names
that are illegal have been disabled. Rewrite the last two lines of each of these

394 Object-Oriented Programming


procedures so that instead of disabling illegal method names, we enable legal
method names and disable all others.

Exercise 12. 4^ restricted-counter-maker


Our implementation of counter-maker places no restrictions on the possible
values that can be stored in the counter. Define restricted-counter-maker
to take an additional argument, a predicate pred. No value is stored in a
restricted counter unless it satisfies the predicate. If a value fails to satisfy

the predicate, then a reset occurs. For example, if the predicate is (lambda
(n) (and (> n 0) (< n 100))) and we try to bring the restricted counter
up to 105, it will reset to its initial value.

Exercise 12.5
Define the hour hand of a 12-hour clock as a restricted counter. (See the
preceding exercise.)

Exercise 12.6
Define a 12-hour clock that has both a minute and an hour hand. This clock
is to be constructed from two objects. One of them will be the 12-hour
clock, which displays only its hour hand, and the other, the minute hand,
will be built using a modified restricted counter. Such a counter is created
using modif ied-restricted-counter-meOcer, which includes an additional
argument. This new argument is a reset procedure that is invoked in place of
the built-in reset in the restricted-counter-maker. When the minute hand
of the clock is about to pass to 60 minutes, the reset procedure is used not
only to reset the minute hand to but also to update the hour hand. Do not
forget to initialize the clock. The new clock is itself to be an object created by
the procedure of one argument, clock-maQter, that responds to two messages:
show and update!. (See the preceding exercise.)

Exercise 12.7
As was done in Chapters 8 and 9, tag the objects by adding object-tag as
"object". Then define the simple procedures object? and make-object.
Wrap meike-object around (lambda msg ) and redefine send. . . .

Exercise 12.8
Is it possible to implement an accumulator with a counter-maker instead of
a box-maker? Is it possible to implement a counter with an accumulator-
mcQcer instead of a box-maker?

12.3 Stacks 395


12.3 Stacks

As we saw in Chapter 11, a stack is an ordered collection of items into which


new items may be inserted at one end and from which items may be removed
from the same end. The end at which items may be inserted or removed is
called the top of the stack. The image that is often conjured up when thinking
of a stack is the rack of trays in a cafeteria, in which one takes the top one,
and trays are added from the top. As the stack builds up, the item that weis
put on first is buried deeper and deeper, and as things are removed from the
stack, the one that was put on first is the last one to be removed. The item
that was added to the stack last is the first one to be removed. Thus a stack
is referred to as a last-in-first- out data structure, or a LIFO.

The stack has several methods associated with it;

• empty?, which tests whether the stack is empty.

• push !
, which adds an item to the top of the stack.
• top, which returns the item at the top of the stack.

• pop !
, which removes an item from the top of stack.
• size, which returns the number of items on the stack.

• print, which prints the items on the stack.

An experiment with stacks is given in Figure 12.11. The two stacks, r and
s, are created in [1] and [2]. In the definitions of r and s, we see that
stack-maker is a thunk, that is, a procedure of no arguments. Its definition

is given in Program 12.12.

In the code for stack-maker, we used a list as the internal representation


of the stack. The user need never know how it is represented, for if we change
the representation, we can alter the definitions of the methods so that when
their names are passed as messages to the stack, the results seen by the user
are the same as those produced by the above code. Even when the stack is
printed, it does not show the internal representation of the stack.

Exercise

Exercise 12.9
In arithmetic, parentheses are used to form groupings of numbers and oper-
ators. For example, one writes 3*(4 + 2). In more complicated expressions,
three different kinds of separators are used to form groupings: parentheses '(',

')', brackets '[',']', and braces '{','}'. Here is an expression that uses all three

396 Object-Oriented Programming


) )

[I] (define r (stack-aaker)


[2] (define s (stack-maker))
[3] (send s 'print)
TOP:
[4] (send r 'print)
TOP:
[5] (send s 'eapty?)
tt
[6] (send s 'push! 'a)
[7] (send s 'push! 'b)
[8] (send s 'push! 'c)
[9] (send s 'top)
c
[10] (send s 'print)
TOP: c b a
[II] (send s 'empty?)
tf
[12] (send r 'empty?)
#t
[13] (send r 'push! 'd)
[14] (send s 'size)
3
[15] (send s 'pop!
[16] (send s 'pop !

[17] (send s 'print)


TOP: a
[18] (send r 'print)
TOP: d

Figure 12.11 Using stack operations

kinds of grouping symbols:

13 + 5*{[14-3*(12-7)]- 15}

Write a program that will scan a mathematical expression made up of the


four basic operations -r,
— .=^. and / and the three kinds of separators and
test whether the separators are correctly nested. The examples (3 — 4] and
(5 — [2 + 4) + l] are not correctly nested. This is a natural problem for the use

of a stack, for whenever a left-grouping symbol is encountered, it is pushed


onto the stack, and whenever a right-grouping symbol is encountered, the
stack popped and the left symbol that comes off the stack is compared to
is

the right symbol just encountered. If they are of different types, the nesting
is not correct. You can model the arithmetic expression as a list of numbers,

12.3 Stacks 397


Program 12.12 stack-maker

(define stack-meJ^er
(lambda
(let ((stk '()))
(lambda msg
(case (Ist msg)
((type) "stack")
((empty?) (null? stk))
((push!) (for-ef feet-only
(set! stk (cons (2nd msg) stk))))
((top) (if (null? stk)
(error "top: The stack is empty.")
(car stk)))
((pop!) (for-ef feet-only
(if (null? stk)
(error "pop!: The stack is empty.")
(set! stk (cdr stk)))))
((size) (length stk))
((print) (display "TOP: ")
(f or-each
(Icunbda (x)
(display x)
(display " "))
stk)
(newline))
(else (delegate base-object msg)))))))

operators, and grouping symbols. Since Scheme uses these symbols as special
characters, one cannot usethem as grouping symbols in the list modeling the
arithmetic expression. Thus use the strings "(",")", "C", "]", "{", and "}"
in place of the grouping symbols. The above arithmetic expression, in this

representation, looks like

(13 + 5 * "{" "[" 14 - 3 * "(" 12-7 ")" "]" - 15 "}")

Test your program on the examples given here and on several additional tests
you devise, some correctly and others incorrectly nested.

398 Object-Oriented Programming


12.4 Queues

A queue is an ordered collection of items into which items are inserted at


one end, called the rear, and from which items are removed at the other end,

called the front. People waiting in line for service normally form a queue in
which new people join the line at the rear and people are served from the
front. Similarly, processes waiting to be run on a computer are put into a
queue to await their turn. Stacks are called LIFO lists because the last one in
is the first one out. Queues are called FIFO lists because the first one in is the
first one out. Adding an item to the rear of the queue is called enqueuing the

item, and removing an item from the front of the queue is called dequeuing.
We implement a queue as an object with the following methods:

• empty?, which tests whether the queue is empty.


• enqueue !
, which adds an item to the rear of the queue.
• front, which returns the Hem at the front of the queue.

• dequeue !
, which removes the item from the front of the queue.
• size, which returns the number of items in the queue.

• print, which prints the items in the queue.

Our first implementation of a queue will imitate the way we implemented


a stack. The data structure we choose for the queue is a list, with the first

element of the list To dequeue an element, we essen-


the front of the queue.
tially take the cdr of the list. To enqueue an element, we must put it at the

end of the list, so we can make a list of the element and append that onto
the end of the queue. The code for such an implementation is presented in
Program 12.13.

The implementation using lists as the data structure for the queue produces
the results we want, but it does it inefficiently. The trouble is that when we
enqueue an item, we use append! which must cdr down q until the last pair
,

and then we attach the cdr pointer to the list containing the new item. The
longer the queue, the more "expensive" it is to cdr down q to get to the last
pair. It would be better to have an implementation that could attach the new

item to the end of the queue without having to cdr down the whole queue.
We accomplish this by introducing a second pointer called rear, which points
to the last cons cell in the queue. When the queue is empty, the pointer q
points to a cell formed by (cons '
() '
() ), and rear also points to that cell.

Only the cdr of q is used.

12.4 Queues 399


) !

Program 12.13 queue-maker

(define queue-m8iker
(lambda ()

(let ((q '()))


(lambda msg
(case (1st msg)
((type) "queue")
((empty?) (null? q)
((enqueue!) (for-ef feet-only
(let ((list-of-item (cons (2nd msg) '())))
(if (null? q)
(set! q list-of-item)
(append! q list-of-item)))))
((front) (if (null? q)
(error "front: The queue is empty.")
(car q)))
((dequeue!) (for-ef feet-only
(if (null? q)
(error "dequeue!: The queue is empty.")
(set! q (cdr q)))))
((size) (length q))
((print) (display "FRONT: ")
(for-each
(lambda (i) (display x) (display " "))

q)
(newline))
(else (delegate base-object msg)))))))

Figure 12.14(a) shows a box-and-pointer representation of such a queue that


has in it the numbers 1 and 2, with 1 at the front. Figure 12.14(b) shows
how the new item 3 is added to the queue by setting the cdr of rear to be
(cons 3 '
() ) and then setting rear itself to point to the last cons cell in the
list. Our new definition of queue-maker is given in Program 12.15. A sample
session using a queue is given in Figure 12.16.

Exercises

Exercise 12.10
Add a message to the queue defined in Program 12.15 called enqueue-list

400 Object- Oriented Programming


^ rear ^--^

/ w w

(a)

1 2

\^
rear
^
/ p F w
/
(b)
>' >r >'

I 2 3

Figure 12.14 Box-and-pointer diagram for a queue

that takes as an argument a list Is and enqueues each of the elements of the
list to the queue preserving their order. For example, if the queue a contains
the elements 1, 2, 3, with 1 at the front, and if Is is (list 4 5 6), then
after invocation of (send a 'enqueue-list ! Is), the queue a contains the
elements 1, 2, 3, 4, 5, 6 with 1 at the front. Do not use append!. Why?

Exercise 12.11
Revise the definition of queue-maker in Program 12.15 to include a message
enqueue-maoiy ! that enqueues any number of items at one time. For example,
(send a ' enqueue-many ! 'x 'y 'z) has the same effect as

(begin
(send a 'enqueue! 'x)
(send a 'enqueue! 'y)
(send a 'enqueue! 'z))

Exercise 12.12: queue->list


Define a procedure queue->list that takes as its argument a queue q, with
size disabled, and returns a list of the elements in q without destroying the
queue. In order to do this, one can first enqueue a unique element such as
(list '
0). Then cons and also enqueue
the front of the queue onto the list,

the front onto the queue. Now dequeue the queue, so that what was at the
front is now at the rear of the queue. Repeat this operation of consing the front
of the queue to the list, enqueuing the front of the queue so that it is at the
rear, and then dequeuing the queue, until the unique element you enqueued

12.4 Queues 401


) ) ) ) )

Program 12.15 queue-maier

(define queue-maker
(lambda ()

(let ((q (cons ' () '())))


(let ((rear q)
(lambda msg
(case (1st msg)
((type) "queue")
((empty?) (eq? rear q)
((enqueue!) (for-ef feet-only
(let ((list-of-item (cons (2nd msg) '())))
(set-cdr! zeax list-of-item)
(set! rear list-of-item))))
((front) (if (eq? rear q)
(error "front: The queue is empty.")
(car (cdr q))))
((dequeue!) (for-ef feet-only
(if (eq? rear q)
(error "dequeue!: The queue is empty.")
(let ((front-cell (cdr q) )

(set-cdr! q (cdr front-cell))


(if (eq? front-cell rear)
(set! rear q))))))
((size) (length (cdr q) )
((print) (display "FRONT: ")

(f or-each
(lajnbda (x)
(display i)
(display " "))
(cdr q))
(newline)
(else (delegate base-object msg))))))))

reaches the front. When it is dequeued, you have a list of the elements that
are in the queue, and the queue is intact.

Exercise 12.13
Rework the previous problem with the method name size enabled.

402 Object- Onented Programming


[I] (define q ( queue -mzJcer))
[2] (send q 'empty?)
#t
[3] (send q 'enqueue! 1)
[4] (send q 'enqueue! 2)
[5] (send q 'enqueue! 3)
[6] (send q 'size)
3
[7] (send q 'front)
1

[8] (send q 'print)


FRONT: 12 3
[9] (send q 'empty?)
»f
[10] (send q 'dequeue!)
[II] (send q 'print)
FRONT: 2 3

Figure 12.16 Using queue operations

Exercise 12.14
In the first version of a queue given in this section, the message enqueue!
contains the code (append! q list-of-item). Discuss the correctness and
the efficiency of the code for a queue if that line of code is replaced by (append
q list-of-item) or by (set! q (append q list-ol-item)).

12.5 Circular Lists

In the previous sections, we defined both the stack and the queue as objects.
we used lists. In the case of the
In the internal representation of these objects,
queue, we used pointers to keep track of the front and the rear of the queue.
There is another way of treating stacks and queues that is more elegant. It
makes use of a data type known as a circular list. In this section, we first
implement circular lists as objects and then use them to define both the stack
and the queue, making use of delegation to take advantage of the properties
of the circular list.

In an ordinary list, the cdr pointer of the last cons cell points to the empty
list. This is denoted by placing a diagonal line in the right hand side of the
last cons cell. If, instead, the cdr pointer of the last cons cell of the list points
back to the first cons cell in the list, we say that the list is a circular list. The

12.5 Circular Lists 403


> I

marker
1

1 1

/
r
^

(a)
<
b a

marker
——
/
M _
1

1
^

1

r^ ^^ n^ -^ 1
(b)
d c b a

Figure 12.17 Box-and-pointer diagrams for a circular list

box and pointer diagram for a circular list containing the three items c, b.

and a is shown in Figure 12.17a. Note that marker is a pointer to the cons
cell whose car is a. Then to make the list circular, (cdr marker) points back

to the cell whose car is c. To add an item d to this circular list, we cons d to
(cdr meorker) and then reset the cdr pointer of marker to point to the cons
cell with d as its car. (See Figure 12.17b.) Thus inserting d into a nonempty
circular list can be accomplished by invoking:

(set-cdr! Barker (cons 'd (cdr Barker)))

Similarly, to remove d from the resulting circular list, we note that (cdr (cdr
marker)) does not contain the item d. so we only have to write

(set-cdr! marker (cdr (cdr marker)))

to get back to the circular list in Figure 12.17a.


we make an ordinary list circular by letting marker be a pointer
In general,
to the end of the list. Then we set the cdr pointer of marker to point to the

beginning of the list. The item to which the cdr pointer of marker points
is referred to as the head of the circular list. As an object, a circular list
responds to the following messages:

• empty?, which tests whether the circular list is empty.


• insert ! . which adds an item to the circular list.

404 Object- Onented Programmtng


• head, which returns the head of the circular list, that is, the item that is

just past the marker.

• delete !
, which removes the head of the circular list.

• move!, which shifts the marker to point to the head of the circular list,

thus making a new item the head.

• size, which returns the number of items in the circular list.

• print, which displays the circular list.

The code for circular-list-meJcer is given in Program 12.18. Initially,

meirker is locally defined to be the empty list, and when the method name
empty? is received, it tests whether msorker is the empty The message
list.

sent to insert an item into the circular list consists of two parts, the method
name insert! and the item to be inserted. There are two cases to consider
when inserting an item. If the list is empty, we first make a list consisting of
the item to be inserted and then change marker to point to that list. Then
we have to make the list circular, so we make the cdr pointer of meirker point
back to meirker itself. We now have a circular list containing only the one
item we inserted.
On the other hand, if the list is not empty, we use the fact that (cdr
meirker) points back to the head of the list when we cons the item to be
inserted (that is, (2nd msg)) onto (cdr meirker). Once we have added the
new item to the head of the list, we reset the cdr pointer of maorker to point

to the cell containing the new item, which becomes the new head of the list.

We use the word head in spite of the fact that a circular list does not have a
head or a tail. However, we may think of the cdr pointer of the cons cell to
which mairker points as pointing back to the head of the list to make the list

circular. And we may think of marker itself as pointing to the last cell in the
list.

If the list is empty when a delete message ! is received, an error is signaled.


If the list contains only one item (that is, if (cdr marker) points back to
meirker itself), then msurker is set equal to the empty list. Otherwise, we again
refer to the "head" of the list as the cons cell to which (cdr marker) points.
Then we reset the cdr pointer of marker to point to (cdr (cdr meo-ker)).
When we found the size of such objects as stacks and queues, we used
the procedure length on their internal list representations. This requires
cdring down the list while counting. We have given a more eflficient way of
doing this by keeping the size in a gauge and incrementing or decrementing
it appropriately when we insert or delete something from the circular list.

We have to be careful in writing the code for a circular list that we do


not get into an infinite loop, going around the circle of pointers indefinitely.

12.5 Circular Lists 405


) )

Program 12.18 circular-list-maker

(define circular-list-maker
(lambda ()
(let ((meurker '())
(size-gauge (gauge-maker addl subl)))
(lambda msg
(case (1st msg)
((type) 'circular list")
((empty?) (null? marker))
((insert !) (send size-gauge 'up!)
(for-ef feet-only
(if (null? marker)
(begin
(set marker (cons (2nd msg)
I
'
()))
(set-cdr! marker marker))
(set-cdr! marker (cons (2nd msg] (cdr marker))))))
( (head) (if (null? marker)
(error "head: The list is empty.")
(ceir (cdr meurker))))
((delete !) (for-ef feet-only
(if (null? marker)
(error "delete!: The eirculeu: list is empty.")
(begin
(send size-gauge 'donnl)
(if (eq? maurker (cdr marker))
(set ! marker '
())
(set-cdr! marker (cdr (cdr marker))))))))
( (move ! (for-effeet-only
(if (null? marker)
(error "move!: The circular list is empty.")
(set! marker (cdr marker)))))
((size) [send size-gauge 'shoff))
((print) (if (not (null? marker))
(let ((next (cdr marker)))
(set-cdr! marker '())
(for-eaeh (leunbda (x) (display x) (display " "))
next)
(set-cdr! marker next)))
(newline)
(else (delegate base-object msg)))))))

4O6 Object-Oriented Programming


Program 12.19 stack-maiker

(define stack-maker
(laabda ()

(let ((c (circuleur-list-msJcer)))


(lanbda nsg
(case (1st msg)
((type) "stack")
((push!) (send c 'insert! (2nd nsg)))
((pop!) (send c 'delete!))
((top) (send c 'head))
((print) (display "TOP: ") (send c 'print))
((insert! head delete! nove ! ) (delegate base-object msg))
(else (delegate c msg)))))))

In order to avoid this in the case of print, we use the trick of temporarily
resetting the cdr pointer of meirker to point to the empty list. Then the
list is no longer circular, and we can use f or-each without fear of looping
indefinitely.

We are now ready to look at the definitions of stack and queue making use
of a circular list. In implementing the stack, a circular list is used and the
marker stays fixed. When the stack receives a push! message, it sends it to
the circular list as an insert ! message. Similarly, the pop! message is sent to
the circular list as a delete! message. When the print message is received
by the stack, the word TOP: is first printed, and then the message is sent to the
circular list. The stack messages size and empty? are delegated to the circular
list. The code for stack-maker using a circular list is in Program 12.19.

The queue-maiker is similarly defined in terms of a circular list, but this


time, the marker is moved each time an item is inserted, so that it points to

the cell containing the new item. Again, most of the queue operations are
delegated to the circular list. The code for queue-maker making use of a
circular list is given in Program 12.20.

This is an elegant way of implementing both the stack-madcer and the


queue-msQcer. They take advantage of delegation by passing messages on to
the circular list. The circular list was flexible enough because we were able to
move the mairker to keep track of certain cells. Notice that we have gained
in eflficiency by making use of the internal gauge in the circular list to keep
the size of the stacks or queues. The circular list is, in general, a useful data
structure.

12.5 Circular Lists 407


Program 12.20 queue-maker

(define queue-maker
(lambda ()

(let ((c (circular-list-maker)))


(lambda msg
(case (1st msg)
((type) "queue")
((enqueue!) (send c 'insert! (2nd msg)) (send c 'move!))
((dequeue!) (send c 'delete!))
((front) (send c 'head))
((print) (display "FRONT: ") (send c 'print))
((insert! head delete! move!) (delegate base-object msg))
(else (delegate c msg)))))))

Exercises

Exercise 12.15
Redefine the stack-maker and queue-maLker procedures presented in Pro-
grams 12.19 and 12.20 so that, instead of the illegal method names being
disabled, the legal method names are enabled.

Exercise 12.16
Draw the box-and-pointer diagrams for a stack implemented using a circular
list. Start with the empty stack, push on the items a, b, c, and d, and then

pop these four items. Show the box and pointer diagrams for the successive
stages as the stack increases and decreases in size.

Exercise 12.17
Make the same sequence of box and pointer diagrams as in the previous ex-

ercise but this time for a queue.

Exercise 12.18
Redefine circular-list-maker in Program 12.18 keeping a local variable
that is initialized to zero to keep the size of the circular list without using a
gauge. Then do it without any local variables.

Exercise 12.19
When building a circular list, it is not necessary to build a circular structure.
Instead, the method names, which rely on the circular structure, must be

408 Object-Oriented Programming


redefined. For example, if before, the cdr of mcirker was a cell c, then using
a simple list, it would be necessary to test (null? (cdr marker)) and then
return c. This approach has a cost because there is an additional local variable
to maintain, which requires setting and testing. However, the benefit is that
no structures are built that can unintentionally enter infinite loops. Redefine
circulax-list-maker without actually using an explicitly circular structure.

Exercise 12.20
Add amethod reverse to the circular-list-maJcer that reverses the cir-
cular list in such a way that the cdr pointer of each cons cell is changed to
point to the previous cell in the list instead of the next cell. The diagram
in Figure 12.21 shows a circular list containing four items before and after
reversing. As in the diagram, be sure your method moves the marker.

marker

marker
^ \^
i i i

>r y f y r >r

a b c d
\ \ \

Figure 12.21 Reversing a circular list

12.6 Buckets and Hash Tables

In Chapter 11, we used a computed by procedures


table to store the values
by memoizing those procedures. The values were retrieved from the table by
calling a procedure lookup. In this section, we construct objects that have
the properties of tables. These objects are called buckets. We also present a
second way of storing data using hash tables, which are vectors in which the

12.6 Buckets and Hash Tables 409


entry for each index is a bucket. In this way, large amounts of data can be
stored in relatively small vectors.
Buckets respond to two messages:

• update!, which adds (or alters) a bucket entry.

• lookup, which retrieves a bucket entry.

A bucket is a structure like a stack or queue whose internal representation can


be thought of as a flat list. Unlike a stack or queue, the order in which things
are entered into a bucket is unimportant, and a bucket can only get bigger.
An entry in a bucket (much the same as in a table) consists of two parts:
the key and its associated value. When we memoize the Fibonacci procedure,
each table entry consists of the procedure's argument and the value of the
procedure when called with that argument. In our bucket, the procedure's
argument would be the key, and the value of the procedure for that argument
would be the associated value.

When we update a bucket, if the key is present, then the value associated
with the key is the argument to an updating procedure. The value returned
by this invocation of the updating procedure determines the new value to be
associated with this key. If the key is not present, then the new value to be
associated with this key is determined by invoking an initializing procedure.

The message lookup is like the procedure lookup introduced in the previ-
ous chapter for tables. In that use, we invoke (lookup key table success
fail), and in the object-oriented view, we invoke (send bucket 'lookup
key success fail). Thus if there is a value associated with key, that value
is passed to success, and if key is not in the table, fail is invoked on zero
arguments.
For update! messages there is some similarity with lookup because there
are separate responses to the existence or nonexistence of the key in the ta-
ble. The call structure for update! is (send bucket 'update! key proc-
if -present proc-if-absent). Again a search of the bucket for the key
occurs. If key exists with associated value, vai, that value is replaced with
the result of evaluating (proc-if -present val). If key does not exist, it is

added with the associated value (proc-if-absent key). A typical session


with a bucket is given as an example in Figure 12.22. Program 12.23 is an
implementation of a bucket-maker.
Recall that we defined memoize in the previous chapter as a mechanism for

improving the efficiency of any single-argument procedure proc. We can use


the bucket mechanism to obtain another version of nemoize (Program 12.24).
The key will be the argument, n, and its associated value will be the value of
(proc n).

410 Object-Oriented Programming


[I] (define b (bucket-maker))
[2] (send b 'lookup 'a (lambda (x) x) (lambda () 'no))
no
[3] (send b 'update! 'a (lambda (x) (addl x)) (lambda (x) 0))
[4] (send b 'lookup 'a (leunbda (x) x) (lambda () 'no))

[5] (send b 'update! 'a (lambda (x) (addl x)) (lambda (x) 0))
[6] (send b 'lookup 'a (lambda (x) x) (lambda () 'no))
1

[7] (send b 'update! 'q (lambda (x) (+ 2 x)) (lambda (x) 1000))
[8] (send b 'lookup 'q (lambda (x) x) (lambda 'no))
1000
[9] (send b 'update! 'q (lambda (x) (+ 2 x)) (lambda (x) 1000))
[10] (send b 'lookup 'q (lambda (x) x) (lambda 'no))
1002
[II] (send b 'update! 'q
(leimbda (x)
(send b 'lookup 'a (lambda (y) (- x y)) (lambda () 'no)))
(lambda (y) 'no))
[12] (send b 'lookup 'q (lambda (x) x) (lambda () 'no))
1001

Figure 12.22 Using bucket operations

Exercise

Exercise 12.21
The two invocations memoize can be simplified to one by adding
of send in
a new method name to bucket-meiker (see Programs 12.23 and 12.24) that
combines the update and lookup into one operation and thus avoids one of the
two searches. Rewrite bucket-meiker to run the definition of raemoize below.

(define memoize
(lambda (proc)
(let ((bucket (bucket-meiker)))
(leuabda (arg)
(send bucket 'update! -lookup arg (lambda (val) val) proc)))))

Requiring no upper bound on the size of a bucket has its own cost. As
the bucket gets bigger, we discover that the search for updating and looking
information up in the bucket gets more and more expensive. Let us consider
another program that uses a bucket. Suppose we have a list of strings, like

12.6 Buckets and Hash Tables 411


) )

Program 12.23 bucket-maker

(define bucket-maker
(lanbda ()

(let ((table '()))


(lambda msg
(case (1st msg)
((type) "bucket")
((lookup)
(let ((key (2nd msg)) (succ (3rd msg)) (fail (4th msg)))
(lookup key table (lambda (pr) (succ (cdr pr))) fail)))
((update!
(for-ef feet-only
(let ((key (2nd msg))
(updater (3rd msg))
(initializer (4th msg)))
(lookup key table
(lambda (pr)
(set-cdr! pr (updater (cdr pr))))
(lambda
(let ((pr (cons key (initializer key))))
(set! table (cons pr table))))))))
(else (delegate base-object msg)))))))

Program 12.24 memoize

(define memoize
(lambda (proc)
(let ((bucket (bucket-maker))
(lambda (arg)
(send bucket 'update! arg (lambda (val) val) proc)
(send bucket 'lookup <urg

(lambda (val) val) (lambda «f ))))))

the contents of a book. We would like to find the word count frequency of
the articles a, an, and the and possibly some others. We could solve this as
follows:

412 Object-Oriented Programming


(define word-frequency
(lambda (string-list)
(let ( (b (bucket-maker)))
(f or-each
(lambda (s) (send b 'update! s addl (lambda (s) 1)))
string-list)
b)))

This defines the procedure word-frequency, which, when passed a text (a


list of strings), returns a bucket that has each of the different strings in the
text as a key and the number of times that string appears in the text as its

associated value. Now suppose that the variable string-list is bound to


some text; for example, the text might start with ("four" "score" "and"
"seven" "years" "ago" "our" "fathers" ...). By writing

(define word-frequency-bucket (word-frequency string-list))

we define a bucket, called word-frequency-bucket, that contains each of


the different words in our text as keys and the frequency of that word as its

associated value. To see how many times the three strings "a", "an", and
"the" appear in the text, we write:

(map
(lambda (s)
(cons s (send word-frequency-bucket 'lookup s
(lambda (v) v)
(lambda () 0))))
'("a" "an" "the"))

This returns a list of the form (("a" . 7) ("an" . 0) ("the" . 10)).


Ifwe were maintaining a frequency count for a book with 1,000 different
words, then the bucket would be a list of 1,000 items, and searching it would
be expensive. We next show how to avoid this problem.
We have now seen two ways of handling the building of tables for such
purposes as memoizing. The first method was to use lists or buckets, which
has the disadvantage that when the table gets long, lookup becomes a costly
operation. Themethod was to use a large vector so that each entry can
other
be stored with a unique index and can be accessed randomly. This has the
disadvantage that the vector has a predetermined fixed length and can hold a
limited number of entries. We are now ready to look at a surprisingly simple
solution to avoid the long searches and to allow for an unlimited number of

12.6 Buckets and Hash Tables 413


entries. We create a vector that holds one bucket per index. This way we can
partition the pairs by placing individual keys and their associated values in a
bucket as a function of what the key is.

A word frequency problem would be to associate


rather naive solution to the
a bucket with each letter. This would create 26 buckets, and if we were lucky,
the average length of each bucket would be 1000/26 (approximately 40). Then
we could use the first or last letter of a string to determine which bucket to
update. Of what they are, the z-bucket will
course, words in English being
not carry its load. The choice of function and the length of the vector vary
with the nature of the data being stored. The function must take a key and
replace it by some nonnegative integer that can reference the vector. This
function is called a hash function because it hashes up the data and turns
them into integers, which are then used to access the vector. The important
point here is that we want the hash function to spread the data evenly in the
buckets. For the Fibonacci numbers example, a reasonable hash function is

the remainder with the size of the vector. Here is how to create hash tables.

Program 12.25 hash-table-maker

(define hash-table-maker
(lambda (size hash-fn)
(let ((v ((vector-generator (lambda (i) (bucket--maker))) size)))
(lambda msg
(case (1st msg)
((type) "hash table")
(else
(delegate (vector-ref v (hash-fn (2nd msg))) msg)))))))

An empty bucket is placed at each index of the vector, v. Then, using the
key, an index is determined by applying the hash-fn to the key. The value
at that index is a bucket that responds to the same messages as heish tables.
By same information is
delegating to the bucket the original message, the
forwarded to the bucket. We now write the new definition of memoize using
a hash table in Program 12.26. In order to write this new memoize we only
need to supply arguments to hash-table-maker. Everything else remains
unchanged. This version of memoize is restricted to numerical data since
its associated hash function invokes remainder on its argument. The hash
function can be as general as the problem for which it is being used demands.

414 Object-Oriented Programming


Program 12.26 memoize

(define memoize
(let ((hashf (lambda (x^ (remainder x 1000))))
(let ((h (hash-table-maker 1000 hashf)))
(lambda (proc)
(lambda (arg)
(send h 'update arg (lambda (v) v) proc)
(send h 'lookup arg (lambda (v) v) (lambda #f)))))))

Similarly we can rewrite word-frequency by including a hash table where


we earlier had a bucket. Forthis, we need a way of converting the first letter

of each string into an integer. The code that assigns to each keyboard char-
acter a unique integer (see Appendix Al) provides us with just the help we
need. Scheme has a procedure string-ref that takes a string and an inte-
ger as arguments and returns the character in the string having that integer
as its index. Scheme also has the procedure char->integer, which takes a
character as its argument and returns the integer associated with that char-
acter. We shall study the character data type more fully in Chapter 15. We
use these two procedures now to define the hash function for the procedure
word-frequency:

(define word-frequency
(let ((naive-hash-function
(lambda (s)
(remainder (char->integer (string-ref s 0)) 26))))
(let ((h (hash-table-maker 26 naive-hash-function)))
(lambda (string-list)
(f or-each

(lambda (s) (send h 'update! s addl (lambda (s) 1)))


string-list)
h))))

A popular hash function for strings is one that sums the (char->integer
(string-ref s i)) for i = to (subl (string-length s)) and finds the
remainder with the length of the vector. The important aspect of the choice
of hash function is that it must spread the data randomly into the buckets so
that each bucket carries its load.
The advantage of hash tables is that when order is not important, a table
can be stored in a vector so that retrieval and updating are far more effi-

cient than in simple linear search. The disadvantage is that we rely on a

12.6 Buckets and Hash Tables 415


hash function that cannot know in advance what the data will look like. To
demonstrate this, consider the following definition of new-bucket-maker:

(define neH-bucket-maker
(lambda ()

(hash-table-maker 1 (lambda (d) 0))))

This hash table is as inefficient as a bucket. We chose the vector too small,
and we chose the hash function too naively. Of course, this would never be
done. In practice, most systems discourage the user from worrying about the
size of the hash table or the nature of the hash function.

Exercises

Exercise 12.22
Construct a list of strings from some paragraph in this section, and run word-
frequency over that list. Determine how many of each of the articles o, an,
and the were used.

Exercise 12.23
Using the list of strings from the previous exercise, introduce a hash function
that uses a large prime number for the vector length and uses the sum of
integers corresponding to characters hash function as described in this section.

Exercise 12.24
Include a message re-initialize ! in the definition of bucket-maker and
hash-table-maker. In both cases, this method returns the object to its

initial state.

Exercise 12.25
Lists that can only grow can get expensive.

a. Include a remove ! message in bucket-maker that removes the key and its
associated value from a bucket. The operation guarantees that if b is a
bucket, then the following expression is always false.

4I6 Object-Oriented Programming


.

(begin
(send b 'remove! key)
(send b 'lookup key (lambda (v) #t) (lambda () #f)))

is always false.

b. Include a remove! message in hash-table-maker that removes the key


and its associated value from the hash table. If b is a hash table, then the
expression above is always false.

Exercise 12.26: store!


Define a procedure store! that takes a hash table (or bucket), a key, and a
value and is defined so that if b is a hash table (or bucket), then

(begin
(store! b key value)
(send b 'lookup key (leuobda (v) (equal? value v)) (lambda () #f)))

is always true. Do this without adding any new messages to hash-table-


maker (or bucket-maker).

Exercise 12.27
Include an image message in bucket-maker whose value is a list of the key-
value pairs. Design it so that in the event of a subsequent update to an existing
key, that update will not mutate the list previously returned by the image
message. If b is a bucket and (send b 'lookup key number? (lambda ()

#1)) is true then

(let ((prs (send b 'image)))


(send b 'update! key addl (lambda (k) 0))
(= (cdr (assoc key prs)) (cdr (assoc key (send b 'image)))))

is always false.

Exercise 12.28
Using the previous exercise, include an image message in hash-table-maker
whose value is a list of key- value pairs. Design it so that in the event of a
subsequent update to an existing key, that update will not mutate the list

previously returned by the image method. If b is a hash table and (send


b 'lookup key number? (lambda () #f)) is true, then the equation of the
previous exercise holds. Hint: You may be tempted to use append, but here
is an example where if you defined bucket -maker correctly, you should be
able to use append !

12.6 Buckets and Hash Tables 4^7


)

The next four problems are related. Work them in order and you will discover

an interesting generalization of delegation.

Exercise 12.29: theater-maker


Consider the definition of theater-maJter below. When entering a theater,
there is usually a line to purchase tickets. Sometimes what is showing at the
theater attracts a massive audience. When that happens, the doors to the
theater may close while there is still a line to purchase tickets. By using a
gauge for modeling the flow of patrons into the loge and a ticket queue where
each patron waits, we can model these facets of a theater. What are the
advantages of using delegate in the else clause of theater-maker? What are
the disadvantages?

(define theater-maker
(lEunbda (capacity)
(let ((ticket-line ( queue -maker )
(vacancies (gauge-maker capacity addl subl)))
(lambda msg
(case (1st msg)
((type) "theater")
((enter!) (if (zero? (send vacancies 'show))
(display "doors closed")
(begin
(send ticket-line 'dequeue!)
(send vaceuicies 'down!))))
((leave!) (if (< (send vacancies 'show) capacity)
(send vacancies 'up!)
(error "leave!: The theater is empty.")))
(else (delegate ticket-line msg)))))))

Exercise 12.30
In theater-maker, suppose we would like to know how many seats are vacant
for the next showing. We cannot find this out without introducing a message,
say show, in the definition of theater-maker. See the code below. Why must
we include the extra message?

(define theater-maker
(lambda (capacity)
(let ((ticket-line (queue-maker))
(vacancies (gauge-maker capacity addl subl)))
(lambda msg
(case (1st msg)
((type) "theater")

j^l8 Object-Oriented Programming


((enter!) (if (zero? (send vacancies 'show))
(display "doors closed")
(begin
(send ticket-line 'dequeue!)
(send vacancies 'dosn!))))
((leave!) (if (< (send vacancies 'show) capacity)
(send vacancies 'up!)
(error "leave!: The theater is empty.")))
((show) (send vacancies 'show))
(else (delegate ticket-line msg) ))))))

We ticket-line and vacancies. The default


have two active objects:
line has (delegate ticket-line msg). This means that we do not have
(delegate vacancies msg). We are only allowing one default. With double
delegation, we can have two defaults. If the message is not applicable to the
first default, it tries the second. So far, we have only seen objects with single
delegation. In this exercise, we build objects with multiple delegation.
We introduce a binary function, combine, that, like compose, takes two
procedures (in this case, objects) as parameters and returns a procedure as a
value.

Program 12.27 combine

(define combine
(lambda (f g)

(lambda msg
(let ((f-try (delegate f msg)))
(if (eq? invalid-method--name- indicator f-try)
(delegate g msg)
f-try)))))

The returned procedure will delegate a message, in order, to the two objects
until it finds one that does not return invalid-method-name-indicator. If

f is not such an object, it invokes g. The procedure combine takes only two
arguments. Rewrite combine to take two or more arguments. Below we have
changed theater-maker to use combine so that the vacancies messages will

be delegated too. The result will be multiple delegation, which will delegate

show messages without the additional line in theater-maker.

12.6 Buckets and Hash Tables 419


(define theater-maker
(lambda (capacity)
(let ((ticket-line (queue-maker))
(vacancies (gauge-maker capacity addl subl)))
(lambda msg
(case (Ist msg)
((type) "theater")
((enter!) (if (zero? (send vacancies 'show))
(display "doors closed")
(begin
(send ticket -line 'dequeue!)
(send vacancies 'down!))))
((leave!) (if (< (send vacemcies 'show) capacity)
(send vacancies 'up!)
(error "leave! The theater is empty.")))
(else (delegate (combine ticket-line vacancies) msg)))))))

Exercise 12.31
The multiple delegation used with combine in the previous exercise is some-
times dangerous because method names are symbols. What would happen
if show were used instead of front as the message for looking at the first

element in a queue? Consider both expressions:

(delegate (combine ticket-line vacancies) msg)

and
(delegate (combine vacancies ticket-line) msg)

Exercise 12.32
In the interests of security, we would like to disable some operations from
delegation. For example, we would like to keep anyone from resetting the
gauge. This would correspond to yelling "fire," clearing the loge before the
showing, and then allowing just the current contents of the ticket line to enter
the loge. That would not be fair. The patrons who were already in the
loge would have paid without receiving any entertainment. Or perhaps an
update! message might be sent so that everyone might think the loge was
full. Such skullduggery is possible with the current configuration of theater-

maker. However, if we form a list of those "unfriendly" messages and disable


them, we can keep these theaters from allowing such nefarious acts. Below is
a partial solution where we have disabled reset! and update!. Rewrite the
definition of theater-maker below to include all of the messages that should
be disabled:

ji20 Object-Oriented Programming


(define theater-maker
(lambda (capacity)
(let ((ticket-line (queue-maker))
(vacancies (gauge-maOcer capacity addl subl)))
(lambda msg
(case (1st msg)
((type) "theater")
((enter!) (if (zero? (send vacemcies 'show))
(display "doors closed")
(begin
(send ticket-line 'dequeue!)
(send vacancies 'down!))))
((leave!) (if (< (send vaceuicies 'show) capacity)
(send vaczmcies 'up!)
(error "leave!: The theater is empty.")))
((reset! update!) (delegate base-object msg))
(else (delegate (combine ticket-line vacancies) msg)))))))

The next six problems are related. Work them in order, and you will discover
some interesting generalizations of objects as we have defined them in this
chapter.

Exercise 12.33
Consider a new definition of send.

Program 12.28 send

(define send
(lambda args
(let ((try (apply (car args) args)))
(if (eq? inval id-method-name- indicator try)
(let ( (object (car args)) (message (cdr args )))
(error "Bad method name :
" (car message)
"sent to object of"
(object object ' type)
"type."))
try))))

According to this definition, uses of send are the same as before, but each mes-
sage includes the receiver of the message as the first element of the message.
Here is an example that uses this send to build counter-maker:

12.6 Buckets and Hash Tables 421


(define counter-meiker
(lambda (init-value uneiry-proc)
(let ((total (box-maker init-value)))
(lambda message
(let ((self (cair message)) (msg (cdr message)))
(case (1st msg)
((type) "counter")
((update!) (let ((result (unaury-proc (send total 'show))))
(send total 'update! result)))
((swap!) (delegate base-object message))
(else (delegate total message))))))))

The variable message contains the receiver as its car and the original msg as
its cdr. When we delegate, we use the whole message. Rewrite box-maker
so that this definition of coxmter-maker works. Be sure to redefine base-
object.

Exercise 12.34
Below is the definition of cartesicoi-point-maker:

(define cartesian-point-maker
(lambda (x-coord y-coord)
(lambda message
(let ((self (ceur message)) (msg (cdr message)))
(case (1st msg)
((type) "Ccurtesiam point")
((distance) (sqrt (+ (squaire x-coord) (squzure y-coord))))
((closer?) (< (send self 'disteince) (send (2nd msg) 'distance)))
(else (delegate base-object message)))))))

Fill in the gaps in the experiment below:

[1] (define cpl (caurtesian-point-maker 3.0 4.0))


[2] (send cpl 'distance)
7

[3] (define cp2 (cartesian-point-maker 1.0 6.0))


[4] (send cp2 'distance)
7

[5] (send cpl 'closer? cp2)


7

[6] (send cp2 'closer? cpl)


7

422 Object-Oriented Programming


Exercise 12.35
Using the definitions of the previous exercise, we add a new kind of point.
In the Cartesian point, we found the distance to the origin as a straight line.
In this definition, we determine the distance as the sum of two straight lines:
the distance to the x-axis and the distance to the y-axis. This type of point
is called a Manhattan point because it is reminiscent of distances traveled in
cities. Below is the definition of manliat tan-point -maker:

(define manhat tan-point -maker


(lambda (x-coord y-coord)
(let ( (p (cartesian-point -maker x-coord y-coord)))
(Icunbda message
(let ((self (car message)) (msg (cdr message)))
(case (1st msg)
((type) "Manhattsm point")
((distance) (+ x-coord y-coord))
(else (delegate p message))))))))

With we have refined cartesian-point -maker by determining


this definition,

the distance diff"erently. The determination of which of two points is closer to


the origin stays the same, but if the point is a Manhattan point, it determines
its distance to the origin by summing instead of finding the square root of the
sum of squares. Fill in the gaps in the experiment below:

[7] (define mpl (manhattan-point-maker 6.0 1.0))


[8] (send mpl 'disteince)
7
[9] (send cp2 'closer? mpl)
•?

[10] (send mpl 'closer? cp2)


7

Exercise 12.36
Suppose that we always create points at the origin (0,0). Then we could add
a method name moveto ! that would take an x-coordinate and a y-coordinate
as arguments. In addition, we want a list of the two current coordinates.
Add method names current-coordinates and moveto to the definition
the !

of cartesian-origin-maker below. We have left the type eis "Cartesian


point" because it still is a point eis one would find in the plane.

12.6 Buckets and Hash Tables 423


(define cartesi8m-origin-maker
(lambda ()
(let ((x-coord 0) (y-coord 0))
(lambda message
(let ((self (car message)) (msg (cdr message)))
(case (car msg)
((type) "Cartesian point")
((distance) (sqrt (+ (square x-coord) (squeure y-coord))))
((closer?) (< (send self 'distance) (send (2nd msg) 'distemce))'
(else (delegate base-object message))))))))

Exercise 12.37
Using the definition of your solution to cartesizin-origiii-meLker from the
previous exercise, fill in the gaps in the experiment below.

[1] (define cpl (cartesian-origin-maker))


[2] (send cpl 'distance)
7

[3] (send cpl 'current-coordinates)


•?

[4] (send cpl 'moveto! 6.0 1.0)


7
[5] (send cpl 'distance)
7
[6] (send cpl 'current -coordinates)
7

Exercise 12.38
Fill in manhattsm-origin-maker below, but do
the rest of the definition of
not use p. Test closer? on a Manhattan point and a Cartesian point that
have both been moved to (6.0,1.0).

(define manhattan-origin-maker
(lambda ()
(let ((p (cartesian-origin-maker)))
(lambda message
(let ((self (car message)) (msg (cdr message)))
(case (1st msg)
((type) "M2mhat tan point")
((distance) ? )

(else (delegate p message))))))))

424 Object-Oriented Programming


13 Simulation:
Objects in Action

13.1 Overview

One of the many uses of computers is in simulation, that is, in the modeling
of real-world phenomena with the computer to study how varying the con-
ditions (parameters) affects the behavior of the system. We generally select
characteristics of the system to be modeled and define objects and actions
that enable a computer program to mimic the real world. If the real-world
behavior is adequately described in the computer program, the results of the
program should predict what happens in the real-world situation. In many
we are studying is unwieldy and does not lend it-
instances, the actual system
self to experimentation. By using computer simulation, we can see the effects
of parameter changes without tinkering with the actual system. Simulation
is extensively used in decision making in government, business, and industry.
We shall illustrate the use of object-oriented programming in a simulation
of a gasoline station. We have selected this example because we can build a
fairly realistic model for it with relatively few parameters. It also gives us an
opportunity to use a number of the data structures introduced in Chapter 12,

such as queues, boxes, counters, and accumulators.

13.2 Randomness

Simulation problems often deal with phenomena that involve uncertainty. A


number we use will have values that are generated ran-
of the variables that
domly. We already used such randomly generated values when we generated
lists of numbers to be sorted in Chapter 10. We say that simulations that
make use of randomness to approximate the values of certain variables are us-
ing Monte Carlo methods. For our gas station simulation, we describe three
different random number generators: uniform, exponential, and normal.

In our gas station simulation, the customers have a choice of full service
or self-service. We estimate the percentage of the customers that choose self-

service, say, 75%, so that 25% select full service. Then when a customer
arrives, we must decide whether he wants self or full service. For lack of
better information, we could toss two coins, and if both come up heads, we
assume that he wants full service. We accomplish this coin-tossing ploy in the

computer by generating a number that is equally likely to assume any integer


value from through number is
99. If that less than 75, we assume the cus-
tomer wants self-service. The number that takes values in the range from
through 99 and is equally likely to eissume any such value is an example of a
uniformly distributed random variable. In general, a variable whose value is

determined by chance (tossing a coin or a simulation of such an act) is called


a random variable. If the values it takes on are all in some fixed interval and
any value in that interval is equally likely to be assumed, we say the ran-
dom variable has a uniform, distribution. The procedure random, introduced
in Chapter 10, generates a uniformly distributed "random" variable with a
nonnegative integer value less than n when (random n) is called. To generate
a decimal number between and 1, excluding but including 1, we can use
the procedure unif-rand-var-O-l in Program 13.1.

Program 13.1 unif-rand-var-O-l

(define unif-rand-veur-O-l
(let ((big 1000000))
(lambda ()

(/ (+ 1 (random big)) big))))

In an actual gas we cannot always tell exactly when the next cus-
station,
tomer will arrive. The variable that tells us when the customers arrive is also
a random variable. It is shown in probability theory that the time between
the successive arrival of customers is a random variable with an exponential
distribution.^ (See Program 13.2.) That is, the time between the arrival of
successive customers can be taken to be a log - where a is the average time

^ See, for example, Feller 1950, p. 218.

426 Simulation: Objects in Action


between arrivals and u is a uniformly distributed random number with values
between and 1, not including 0. We omitted the value to be able to take
the logarithm of the random number. We can then define a procedure called
arrival-time-generator, which randomly generates the arrival time of the

next customer, using the parameter av-arr-time, which is set when the pro-
gram is initialized, and rounding the resulting time to be an integer with 1

minute as its smallest value.

Program 13.2 exponential-random-variable

(def ine exponent ial-random-var iable


(lambda (mean)
(* mean (- (log (unif-rand-var-0-1))))))

Program 13.3 arrival-time-generator

(define airrival-time-generator
(Ijunbda (av-arr-time)
(+ 1 (round (exponential -random-variable (- av-arr-time 1))))))

The number of gallons of gasoline that a customer buys is also a random


variable. It seems reasonable to assume that the number of gallons of gasoline
that a customer buys will cluster around some average value. Very few buy
1 or 2 gallons and very few buy large quantities above 25 gallons. It might
be appropriate for us to assume that the average number of gallons that a
customer buys is buy between 8 and 16 gallons. This
12 gallons and that most
leads us to we draw a graph showing the number of gallons
assume that if

bought on the horizontal axis and the number of people who bought that
many gallons on the vertical axis, we would get the well-known bell-shaped
curve known as the normal distribution. This distribution has the property
that its highest probability is at the mean (or average) value m, which we
shall take as 12 gallons. There is also the standard deviation s, a number
that indicates the fatness of the bell-shaped curve. About two-thirds of the
purchases fall in an interval, which extends a distance s on each side of the
mean m. In our case, we shall take the standard deviation to be 4 gallons, so
that about two-thirds of the customers purchase between 8 and 16 gallons.

We have seen how to get a uniformly distributed pseudo-random variable

13.2 Randomness 4^7


between and 1. Let us call this uniformly distributed random variable u,
and let random numbers: ui,U2,
us generate 12 such Ui2- We can then
. .
.
,

simulate a normally distributed random variable v with mean m and standard


deviation s by using the formula^
12

m + s yj(u,- — .5)
t=i

The Greek letter sigma, ^, we form the sum of terms of the


indicates that
form {ui — .5) for the index i going from 1 to 12. Program 13.4 generates the
normally distributed random variable. Program 13.5 generates the number of
gallons of gasoline purchased.

Program 13.4 nonnal-ramdom-vaariable

(define normal-random-variable
(lambda (meein std-dev)
(letrec ((compute (lambda (i)
(if (zero? i)

(+ (- (unif-rand-var-0-1) .5)
(compute (subl i) ))))))
(+ mean (* std-dev (compute 12))))))

Program 13.5 gallons-generator

(define gallons-generator
(lambda ()
(maix 1 (round (normal -remdom-vzuri able 12 4)))))

^ We always use
1 2 uniform random v2Lriables to generate a normal random vau-iable because

itcan be shown that the mean of any uniform r2indom v^lriables u on the interval to 1 is .5,
and its standeird deviation is l/\/T2. By adding 12 independent uniform rjuidom variables,
we get an approximation to a normal reindom veiriable with meain .5 and stand2Lrd deviation
1. We subtract .5 in each term to get the meam to be and multiply the resulting sum by
5 to get a steindard deviation of s 2uid add m to get a meem of m.

428 Simulation: Objects in Action


)

Exercises

Exercise 13.1
In the game of odds and evens, two coins are tossed. If the result of a toss is

one head and one tail, we call it odds. Otherwise, we call it evens. Write a
program to simulate this game for 1000 tosses and determine the number of
odds and evens that occur.

Exercise 13.2
While riding in a car with a friend, he proposes a wager that involves keeping
track of the last two digits of the license plates of passing cars. He bets you
$10 that within the next twenty cars that pass, at least two passing cars will

have the same two-digit number as the last two digits in their license plates.

This may be any number from 00 to 99. Develop a program to simulate this
game to determine whether you should take the bet. Perform the simulation
100 times and determine the amount of money you would have won or lost.

Exercise 13.3
The random number generator implemented in most computers generates
what are known as pseudorandom numbers rather than true random numbers.
Footnote 1 in Section 10.2.5 describes an algorithm for such a pseudorandom
number generator. Here is a Scheme implementation of that algorithm:

(define random-maker
(lambda (m a seed)
(lambda (n)
(let ((u (/ seed m)))
(set! seed (modulo (* a seed) m)
(floor (* n u))))))

(define remdom-time (lambda () 1000))

(define random
(random-maker (- (expt 2 31) 1) (expt 7 5) (random-time)))

If your implementation of Scheme has a procedure that gives you the time
of day, it should be used instead of random- time. If not, our naive definition
will suffice. Consider these two ways to test a random number generator.

a. Divide the range from to 99 into k equal parts. Generate n random


numbers in that range and count how many fall into each part. The fraction

13.2 Randomness 429


of those that fall in each part should be approximately the same and equal
tof.
b. Count the number of upward runs (that is, sequential runs of random
numbers in ascending order) of length k and the number of downward runs
(that is, sequential runs of random numbers in descending order) of length
Ar when a large number of random numbers is generated. For each value of
k, the counts should be approximately the same.

Implement these two tests in Scheme, and run them using the random
number generator defined above. If your implementation of Scheme has a
random number generator, test it too. Interpret the results.

13.3 The Gas Station Simulation

Our simulation concerns a gasoline station that has two lanes leading to self-
service pumps and two lanes leading to full-service pumps. This is not an
uncommon configuration in small stations. To run the simulation, we specify
the following initialization parameters:

• close-time: The closing time in hours assuming that the station opens
when the clock reads hours.

• %-self -service: The percentage of the customers who choose self-service.

• av-airr-time: The average time interval in minutes that passes between


the arrival of customers. The customers arrive at random times, but the
average time between arrivals is what is estimated here.

• prolit-sell: The profit that the station owner makes on each gallon of
self-service gas sold.

• profit-full: The profit that the station owner makes on each gallon of
full-service gas sold.

• eztra-timeCself-punp: The average time spent at the self-service pump


in excess of the time actually pumping the gas (e.g., cleaning windows,
paying for purchase, etc.).

• extra-time Wull-pump: The average time spent at the full-service pump


time actually pumping gas (e.g., waiting for service, paying
in excess of the
for purchase, etc.).

• pump-rate: The number of gallons of gasoline per minute that the pumps
deliver.

4S0 Simulation: Objects in Action


Gas Station
2 full-service pumps aock
2 self-service pumps

C\ closing time

arrival time

Serve next ciistomer Enqueue next customer Serve next customer


at each pump in shortest pump queue at each pump
of desired service type

Dequeue customer Dequeue customer


served and record Serve next customer served and record
purchase data at each pump purchase data

Update clock Dequeue customer Update clock


served and record
purchase data

Output statistical
{

Update clock data for day


and
arrival time

Figure 13.6 Flow^chart for the gas station simulation

Figure 13.6 shows a flowchart or diagram of the gas station simulation. The
box on the top shows the gas station with four pumps. Moving downward, we
pass through various diamond-shaped boxes, which correspond to conditionals
or branches. Each branch follows a customer through the wait in the pump
queue until he or she is served and is dequeued and the purchase is recorded.
At closing time, those customers still in line are served but no new ones are
admitted. When all are served, the statistical data for the day are printed
out.

To implement this gas station simulation, we use several types of objects,


each made by an object maker. Figure 13.7 shows the various objects and
the method names to which they respond. It also shows the lines of com-

13.3 The Gas Station Simulation 431


: 1

station

Tf^t^r%rt V service
wnicn serve pump
5lll — ^ITIT^fV*? pump
«iprvp V pump

pump7

empty queue of customers


queue of customers
^i/e queue of customers
check w
queue of customers

customer

CTQllrtnc >^ gallons generator


^A w
service
1V/VV/J.\J

service

nuTnV>er~of counter
w
totalwait!
max— wait'
—— accumulator
accumulator
V t r*t Q 1 — T^fr*Ti 1
> accumulator
rpnort report procedure

Figure 13.7 Objects and messages used in the simulation

munication between the objects, that is, the objects that send messages to
The gas station itself is an
other objects. object named station created by
station-maker (see Program 13.10). The object station responds, in ways
that will be described later, to four messages: report, which-serve, all-
empty?, and serve. Each pump in the gas station is an object created by the
procedure pump-maker (see Program 13.11). The pump object, which man-
ages its queue of customers who are waiting for service, responds to the four
messages: empty?, (referring to its queue of customers), enqueue!, size, and
check. The customers are objects created by the procedure customer-maker
(see Program 13.12). Each customer responds to the messages: gallons and
record. We also use objects created by the procedure service-maiker (see

Program 13.13). These objects, which are associated with each customer,
know the type of service (self or full) and the profit per gallon and use that
information to store information about the customer's purchase.

432 Simulation: Objects in Action


We use several other objects that are created by object makers defined in
Chapter 12. For example, the object clock, which keeps time in minutes, is

a counter that has initial value zero and update procedure addl. The object
arrival is a box that stores the arrival time of the next customer. Each
pump has a queue denoted by q and made by queue-meiker in Chapter 12.

The total amount of time a customer spends at the pump is kept by a box
object called timer. Finally, for each type of service (self and full), we keep
a record of the following four items: (1) A tally is kept of the total number
of customers in a counter object called number-of .
(2) The total waiting
time for all customers is kept in an accumulator object called total-wait.
(3) The maximum waiting time for all customers is kept in an accumulator
object called max-wait. (4) The total profit is kept in an accumulator object
called total-profit.
The values of the eight initialization parameters are passed to the pro-
cedure simulation-setupftrun, defined in Program 13.8, which starts the
simulation by invoking the procedure simulation with four arguments. The
first operand simulation invokes station-maker, which itself takes
peissed to

six arguments. The second and third operands passed to station-maker are
the two services that set up the mechanism for recording the data we want
to collect in our simulation. The rest of the code is clarified when we look at
the definitions of the object makers given below. The second operand passed
to simulation creates the clock.

The management of the gas station simulation is done by the procedure


simulation that is invoked by simulation-setupftrun. We see the progress
of the customer through the station illustrated in the flowchart of Figure 13.6
reflected in the code for the procedure simulation, given in Program 13.9.

The value bound to the parameter station in the procedure simulation


is an object created by the procedure station-meiker, for which the code is

given in Program 13.10. Looking at the code for simulation, we see that the
box aurrival stores the sum of the current clock time plus the time increment
until the next customer arrives, which is generated by the airrival-time-
generator. We enter loop and assume that it is not yet closing time and
that it is Then the station is passed
the arrival time of the next customer.
the two-part message in which the first part is which-serve and the second
part is a customer, created by invoking the procedure customer-maker (Pro-
gram 13.12) with the two arguments, the arrival time stored in arrival and
clock. From station-maker in Program 13.10, we see that the customer
so created is enqueued to the pump with the shortest queue that gives the
desired kind of service (full or self).
Next, station is sent the message serve. This causes each of the four

13.3 The Gas Station Simulation 4^3


Program 13.8 simulation-setup&mn

(define simulation-setuptrvm
(lambda (close-time 7,-self-service av-arr-time
profit-self profit-full
extra-timeJself-pump extra-timeCfull-pump pump-rate)
(let ((self-service (service-maker "Self" profit-self))
(full-service (service-maker "Full" profit-full)))
(simulation
(station-maker
'/,-self -service

self-service
full-service
extra-timeJself-pump
extra-time8full-pump
pump-rate)
(counter -maker addl)
av-eirr-time
(* 60 close-time)))))

pumps to check its queues. Looking at the code for pump-maker, we see that
when pump is passed the message check and the queue q associated with that
pump is not empty, the pump refers to its timer. Whenever a customer is

finished and dequeued, the timer is reset to -1. Thus if the timer is found to
store the value -1 when the message check is received, it updates the timer
to store the total time the customer at the front of the queue spends at the
pump. This total time includes both the actual pumping time and the average
extra time at the pump (one of the initial parameters). From then on, each
time pump receives the message check, the number stored in timer is reduced
by one, until the value stored is zero. When that happens and pump is passed
the message check, the customer has completed what had to be done at the
pump, and that customer is dequeued.
The dequeued customer is then passed a two-part message. The first part
is the method name record, and the second part is the variable service,

which is bound to an object that is either full-serv or self-serv created


by service-maker (Program 13.13) and passed into pump-maker when the
object pump was created. The service stores in itself the kind of service (full
or self) at that pump and the profit per gallon at that pump, and it keeps the
following statistics:

1. a tally of the number of customers using that kind of service.

434 Simulation: Objects in Action


)

Program 13.9 simulation

(define simulation
(lambda (station clock av-arr-time close-time)
(let ((arrival
(box-maker (+ (send clock 'show)
(arrival -time-generator av-eu:r-t ime) ) ) ) )

(letrec
((loop
(lambda ()

(if (= (send clock 'show) close-time)


(prepare-for-closing)
(begin
(if (= (send clock 'show) (send arrival 'show))
(begin
(send station 'which-serve
(customer-maker (send arrival 'show) clock))
(send station 'serve)
(send arrival 'update!
(+ (send clock 'show)
(arrival -time-generator av-eu:r-time)))
(send clock 'update!))
(begin
(send station 'serve)
(send clock 'update!)))
(loop)))))
(prepare-for-closing
(lambda ()

(if (send station 'all-empty?)


(send station 'report)
(begin
(send station 'serve)
(send clock 'update!)
(prepare-for-closing) ) ) ) )

(loop)))))

2. the total waiting time of all customers who have passed through the queues
for that kind of service,

3. the maximum, waiting time for all such customers, and


4. the total profit so far at the pump for that kind of service.

13.3 The Gas Station Simulation 4^5


Program 13.10 station-mcLker

(define station-maker
(let ((check (lambda (p) (send p 'check)))
(all-empty? (andmap-c (lambda (p) (send p 'empty?))))
(shorter (lambda (pi p2)
(if (< (send pi 'size) (send p2 'size)) pi p2))))
(lambda (7,-self self-serv full-serv eitra-time-self extra-time-full pump-rate)
(let ((selfs (list (pump-maker extra-time-self pump-rate self-serv)
(pump-maker extra-time-self pump-rate self-serv)))
(fulls (list (pump-maker extra-time-full pump-rate full-serv)
(pump-maker extra-time-full pump-rate full-serv))))
(lambda msg
(case (1st msg)
((type) "station")
((report) (send self-serv 'report) (send full-serv 'report))
( (which-serve)
(let ( (piimp (apply shorter (if (< (random 100) '/.-self)

selfs
fulls))))
(send piimp 'enqueue! (2nd msg))))
((all-empty?) (and (all-empty? selfs) (all-empty? fulls)))
((serve) (for-each check selfs) (for-each check fulls))
(else (delegate base-object msg))))))))

When customer receives the method name record and the service object,
we see in the code for customer-maker in Program 13.12 that each of the
above statistics is updated with the information for the dequeued customer.
Then if the pump's queue is empty, its timer is reset to -1; otherwise it is

updated to show the total time at the pump for the customer who is now at
the front of the queue.

Returning again to the code for simulation, we have completed (send


station '
serve) so clock
, is updated and airrival is updated to show when
the next customer will arrive. When it is not the arrival time for a new
customer, we only pass the message serve to station and then update clock.

When clock shows that it is closing time, we enter the loop prepare-f or-
closing. If at least one of the queues is not empty, those customers in the
queues are served as above, but no new customers are enqueued. Finally,

when all queues are empty, the message report is passed to station. In

436 Simulation: Objects in Action


Program 13.11 pump-maker

(define piunp-maker
(lambda (extra-time pump-rate service)
(let ((q (queue-maker)))
(let ((increment (lambda
(let ((gallons (send (send q 'front) 'gallons)))
(ceiling (+ extra-time (/ gallons pump-rate))))))
(timer (box-maker -1)))
(lambda msg
(case (1st msg)
((type) "pump")
((check) (if (not (send q 'empty?))
(let ((c (send timer 'show)))
(cond
((negative? c) (send timer 'update! (increment)))
((zero? c) (let ((customer (send q 'front)))
(send q 'dequeue!)
(send customer 'record service)
(if (send q 'empty?)
(send timer 'reset!)
(send timer 'update! (increment)))))
(else (send timer 'update!
(subl (send timer 'show))))))))
(else (delegate q msg))))))))

the code for station-maker (Program 13.10), we see that the message re-
port is sent to both objects self-serve and full-serv. In service-maker
(Program 13.13), the procedure report is invoked with the final values of

the statistics storedwhen each customer was dequeued. The procedure re-
port, defined in Program 13.14, displays this information. We shall run the
simulation for various parameters and see how this information is displayed.
We have taken a quick walk through the gais station simulation to illus-
trate the use of objects in asomewhat longer program than others we have
been studying. The program contains many interesting and subtle features
that are worth studying in detail until they are fully understood. The time
spent in coming to grips with each of the steps will contribute much to your
development as a programmer.
Let us now run the simulation. We call simulation-setup&run with the
following parameters:

13.3 The Gas Station Simulation 437


)

Program 13.12 customer-maker

(define custoaer-Baker
(lambda (eirrival-time clock)
(let ((gallons-puBped (gallons-generator)))
(lambda msg
(case (1st msg)
((type) "customer")
((gallons) gallons-pumped)
((record) (let ((service (2nd msg))
(wait (- (send clock 'show) arrival-time)))
(send service 'niamber-of !

(send service 'total-wait! wait)


(send service 'max-wait! wait)
(send service 'total-prof it ! gallons-pvi^ed)))
(else (delegate base-object msg)))))))

close-time: 12 hours
'/.-self-service: 759c
av-arr-time 4 min.
profit-self So. 10 per gallon
profit-full So. 10 per gallon
extra-timeCself-pvimp: 5 min.
extra-timeCfull-pump: 8 min.
pump-rate: 4 gallons per min.

We then have:

[1] (simulation-setup&nin 12 75 4 . 1 . 1 5 8 4)
Self -Service:
The number of customers is 111
The average wait is 11
The maximum wait is 20
The total profit is 127.3
Full-Service:
The number of customers is 37
The average wait is 13
The maximum wait is 21
The total profit is 46.8

The program for our simulation does produce the information we want,
but it does not have a user-friendly interface in the sense that the user must
know what the parameters are and the order in which to put them when the

438 Simulatton: Objects in Action


)

Program 13.13 service-maker

(define service-meiker
(lambda (full-or-self profit)
(let ((number-of (counter-maker addl))
(total-wait (accumulator-meiker +)
(msix-wait (accumulator-maker max))
(total -prof it (accumulator-meiker +)))
(leunbda msg
(case (Ist msg)
((type) "service")
((number-of!) (send number-of 'update!))
((total-wait!) (send total-wait 'update! (2nd msg)))
((max-wait!) (send max-wait 'update! (2nd msg)))
((total -prof it!)
(send total-profit 'update! (* profit (2nd msg))))
((report) (for-ef feet-only
(report full-or-self
(send number-of 'show)
(send total-wait 'show)
(send mcix-wait 'show)
(send total -prof it 'show))))
(else (delegate base-object msg)))))))

Program 13.14 report

(define report
(Ijuabda (full-or-self num-cust total-wait meuc-wait profit)
(if (zero? num-cust)
(writeln " There were no " full-or-self "-Service customers.")
(begin
(writeln full-or-self "-Service:")
(writeln " The nvimber of customers is " niim-cust)
(writeln " The average wait is " (round (/ total-wait num-cust)))
(writeln " The meucimum wait is " msuc-wait)
(writeln " The total profit is " profit)))))

procedure simulation-setupftrun is called. There are so many parameters


that it is hard to remember their order. Thus we design an interface to

1S.3 The Gas Station Simulation 439


Program 13.15 prompt-read

(define prompt-read
(lambda (prompt)
(display prompt)
(display " ")

(read)))

the program that will prompt the reader for the information needed to run
the simulation. For that purpose, we use the procedure prompt-read, which
prints its argument (the prompt) to the screen eind waits for a response to be
read from the keyboard. It then returns that response. The code for prompt-
read is in Program 13.15. An example illustrating the use of prompt-read
is

[2] (let ((hours


(prompt-read
"Elnter the number of hours the station is open:")))
(writeln "The station is open " hours " hours."))
Enter the number of hours the station is open: 12

The station is open 12 hours.

We build our user-friendly interface by first constructing a list of all of the


prompts that we wcint to display. We name this list station-prompts:

(define station-prompts
'("Elnter the number of hours the station is open:"
"Enter the percentage of self-service customers:"
"Enter the average time in minutes between arrivals:"
"Enter the profit per gallon from self-service customers:"
"Enter the profit per gallon from full-service ciistomers:"
"Enter the extra time at the pump for self-service customers:"
"Enter the extra time at the pump for full-service customers:"
"Enter the delivery rate of the pumps in gallons per minute:"))

We next write a program, gas-station-simulator, with a loop in it that ap-


plies prompt-read to each prompt in the list and builds a list of the responses
in the order in which they are entered. It then applies simulation-setup*run
to the arguments in that list using the procedure apply. The code for gas-
station-simulator is presented in Program 13.16

440 Simulation: Objects tn Action


Program 13.16 gas-station-simulator

(define gas-station-simulator
(letrec
((loop (lambda (Is)
(if (null? Is)
'()

(let ((v (prompt-read (car Is))))


(cons V (loop (cdr Is))))))))
(lambda
(apply simulation-setupftrun (loop station-prompts)))))

[3] (gas-station-simulator)
Enter the number of hours the station is open: 12
Enter the percentage of self-service customers: 75
Enter the average time in minutes between arrivals: 4
Enter the profit per gallon from self-service customers: .10
Enter the profit per gallon from full-service customers: .10
Enter the extra time at the pump for self-service customers: 2
Enter the extra time at the pump for full-service customers: 4
Enter the delivery rate of the pumps in gallons per minute: 4
Self-Service:
The number of customers is 110
The average wait is 6
The mciximum wait is 12
The total profit is 120.70
Full-Service:
The number of customers is 35
The average wait is 8
The mciximum wait is 10
The total profit is 44.40

Figure 13.17 The simulation input and output

The result of invoking gas-station-simulator is given in Figure 13.17.


The experiment records the data entered in a clearly readable form, as well
as the output of the simulation.

We can now use this simulation to see what happens when certain parame-
ters are changed. For example, ifwe keep all of the parameters fixed except the
average arrival time, we can see what happens when the time between arrivals
decreases. We kept the station open 12 hours, 75% of the customers selected

13.3 The Gas Station Simulation 44i


Average Arrival Time: 8 4 3 2 1

Self-Service:
The number of customers is 60 110 131 192 274
The average wait is 6 6 7 9 84
The maximum wait is 9 12 14 20 169
The total profit is 70.00 123.10 160.40 232.20 334.90
Full-Service:
The number of customers is 22 35 40 53 86
The average wait is 9 9 8 9 9
The majcimum wait is 11 11 12 17 18
The total profit is 28.10 44.40 45.70 66.10 100.40

Table 13.18 Gas station simulatiion with varying average arrival time
I

self-service, the profit per gallon on self-service sales was $0.10 and for full-
service sales was $0.10, the extra wait at the pump for self-service customers
was 2 minutes and for full-service customers was 4 minutes, and the pumps
delivered gasoline at the rate of 4 gallons per minute. The simulation was run
with the average time in minutes between the arrival of successive customers
taken to be 8, 4, 3, 2, and 1. The results are summarized in Table 13.18.
When the time between arrivals is large and long queues do not form at
the pumps, it is faster to use self-service. As the queues get longer with
more frequent arrivals, the smaller volume of full-service customers allows the
attendants to keep the waiting time relatively short, whereas the self-service
customers pile up and ultimately take hours to get out. Although a wait of
more than an hour is generally considered intolerable, there were times during
the oil crisis of 1978 when people did stay for hours in gas station queues which
extended for blocks around the station. In normal times, the station would
have to provide more lanes or lose the business, and a larger percentage of
customers would opt for full service.

In this simulation, after the data were entered, the program printed out a
summary of the day's business. It is possible to write simulations showing on
the screen a picture of the cars entering and advancing through the queues,
as well as the current values of the variables. There are many creative ways
in which simulations can be written using the tools developed in this text.

442 Simulation: Objects in Action


Exercises

Exercise 13.4
Write a simulation of the following experiment. Two dice are thrown n times.
A record is kept of how many times each possible sum comes up. Since each
die is a cube with the numbers from 1 to 6 on its faces, the possible face sums
are the integers from 2 to 12. The output should be a table with the face sum
column and the percentage of times that sum came up in the second
in the first

column. Run the experiment with n taking the values 100, 200, 400, and 800.
To record the results of each toss, use 11 counters (as defined in Section 12.2.3),
one for each possible face sum, and increment the counter each time that
sum appears. Compare your results with the theoretical percentages, which
are 2.78%, 5.56%, 8.33%, 11.11%, 13.89%, 16.67%, 13.89%, 11.11%, 8.33%,
5.56%, and 2.78%. These are easily found by counting the number of ways a
given face sum can be obtained, dividing by 36 (the total number of ways two
dice can come up), and multiplying by 100. For example, a 7 can come up in
six ways, so ^ x 100% = 16.67%.

Exercise 13.5: Estimation of tt

We can use simulation to compute the value of tt. For this we use the fact that

a circle of radius 1 has area equal to tt. We shall play the following game. We
throw a dart at a square board with sides of length 2 feet. The area of this
board is 4 square feet (see Figure 13.19). We assume that the dart is equally
likely to land at any point on the square. Thus the percentage of time it falls

within the circle of radius 1 inscribed in the square should be approximately


equal to the ratio of their areas. If we perform the experiment N times (iV a
large number) and if K of these throws fall inside the circle, then we would
expect jf
approximate ^, which is /f^p. We would also expect that this
to
approximation gets better as N increases. Let x and y be the coordinates of
the point (x, y) on the square. Then x and y are both real numbers between
— 1 and 1. Write a program that takes as argument the number of times N
the experiment is repeated; generates the values of x and y as uniformly
distributed random variables, each in the interval between —1 and 1; and
counts how many of them satisfy the condition that x^ + y^ < 1. The program
should then use this information to estimate tt. Run the experiment with
N= 100, 1000, 10000, and 100000.

Exercise 13.6
The definition of shorter station-maker does not handle stations with
in

fewer than or more than two pumps of the same variety. Rewrite shorter

13.3 The Gas Station Simulation 44^


Figure 13.19 Monte Carlo method for estimating tt

and any other procedures so that the simulation supports arbitrary numbers
of pumps of these two varieties.

Exercise 13.7
Modify the gas station simulation to apply to a case where there are three
lanes dedicated to self-service and only one lane dedicated to full service. Run
the same input data as in the simulation runs given above and compare the
results you get with the data in the tables.

Exercise 13.8
The gas station simulation does not generalize as simply as we might hope.
For example, what if the station also wanted information on their diesel fuel?
Diesel fuel has a different pump speed, but that would not be any cause to
make significant changes in the program. You would need to make a small
alteration to the argument simulation-setupftnm and a few other
list of
minor changes. However, by adding diesel fuel, you would have to add much
to station-maker. Make those changes, and test your program. Next, add
corn fuel in a similar fashion. Go back to the program and build an object or
apply abstraction principles so that if the station needs to support yet another
kind of fuel, the task will be simple.

Exercise 13.9: prompt -read


Consider the definition of prompt-read below.

(define prompt-read
(lambda items
(for-each display items)
(display " ")

(read)))

Can this definition be used in place of Program 13.15? How is it better than
Program 13.15?

444 Simulation: Objects in Action


Exercise 13.10
In Program 13.16, the gas-station-simulator prompts for the initial data,

but it does not echo that data back to be sure that the correct data was
entered. For example, when the response to the prompt "Enter the number
of hours the station is open:" is 12, the echo printed on the screen could
say "The station is open 12 hours." Rewrite the definition of gas-station-
simulator so that it provides an appropriate echo for the response to each
prompt.

Exercise 13.11
Add a report figure for the longest line that appeared at each pump.

Exercise 13.12
Modify report so that dollar figures are printed with two decimal places.

Exercise 13.13
Explain why the following definition of gas-station-simulator can not be
used in place of Program 13.16:

(define gas-station-simulator
(lambda ()

(apply simulation-setupJIriin (map prompt -read station-prompts))))

Hint: See the discussion of evaluation order on Page 38.

13.3 The Gas Station Simulation 44^


Part 4

Extending the Language

Let us think back to the introduction to Part 1. The restaurant where you
dine is a little cafe in Paris. Ordering may not be simple; you might not
be able to read the menu; it is in French. If that were the case, however,
you would probably be carrying with you a dictionary, which is analogous to
our syntax table, for looking up all the unfamiliar words. Here's one from
the menu: Poulet rati. First you look up the meaning of poulet, and it is

"chicken," and then you look up roti and it is "baked." So you know that it

is "baked chicken." The whole menu is full of such unfamiliar French words.
However, with your dictionary, you can look up each word and translate the
entire menu into your native language before placing your order. Derived
special forms are like words in a foreign language. When you do not know
what they mean, you look them up in a derived special forms dictionary.
For example, let expressions can be translated into applications of lambda
expressions. Applications and lambda expressions correspond to words in
your native tongue.
Suppose that you do not have a dictionary but your Parisian waiter and
you know a bit of German. You ask him what poulet roti is, and he says that
he does not know how to translate poulet roti into your native tongue, but he
can say German. He says ^^Backhuhn," and you translate it to "baked
it in

chicken." The French is translated into German, and the German is translated
into something you understand. This also happens with derived special forms.
For example, let* expressions are first translated into let expressions, and then
the let expressions are translated into familiar words: lambda expressions
and applications. Chapter 14 presents two different mechanisms for inserting
derived special forms into the syntsix table. This allows us to extend our
vocabulary of expression types. Delay expressions, introduced in this chapter,

are then used in Chapter 15 to develop unbounded (or infinite) lists.

These lists are not really infinite, but there is no way to show they are not.
Most of the finite list-processing procedures can be recast, with infinite lists

replacing finite lists. In fact, by removing the tests for the empty list, the task
is virtually complete. Working with infinite lists requires thinking about lists

that never end. You have already seen circular lists, which also never end, but
those lists always repeat their values. With infinite lists, different values can
appear in every position. Consider an infinite list of the positive integers (1

2 3 . . . ). If such a list exists, its car would be 1, its cdr would be (2 3 . . .


),

and consing onto it would form the nonnegative integers. All that remains
is to convince you that such a list does exist.

44^ Extending the Language


14 Declaring Special Forms

14.1 Overview

In Scheme, operators are applied to their operands by enclosing the operator


followed by its operands in parentheses. The call structure for applying an
operator to its operands is:

(.operator operand . . . )

When such an application is made, the operator and the operands are evalu-
ated in an unspecified order, ^ and then the procedure (which is the value of the
operator) is applied to the arguments (which are the values of the operands).
We have also encountered several special forms in which the subexpressions
following the keyword are treated differently from the operands of a proce-
dure. Examples of these are and, begin, cond, case, define, if, lambda,
let, let*, letrec, or and set!, each with a syntax of its own. Some of
these, like let, have been introduced to make it easier to read programs, for
any program using let could be rewritten using an application of a lambda
expression in place of each let expression. Such keywords are referred to as
derived keywords. One of the convenient features of Scheme is that it is an
extensible language that allows the user to add new special forms to make the
language more convenient to use and to provide a mechanism to do tasks that
procedures cannot perform. We shall study two mechanisms for making such
additions in this chapter.

^ Programs that rely on an order of evaluation are said to be ill formed. Since the order
of evaluation implementation dependent, such programs are not portable, and they can
is

not, in general, be transferred from one implementation to another.


2

The action of taking an expression and rewriting it in terms of something


we understand happens when we work with natural language. As we read
a passage, we often look in a syntax table, a dictionary, and substitute the
meaning of the word for the word itself. In Scheme, however, we restrict those
items for which substitutions can be made (we also say "which can be trans-
formed") to be lists that begin with a keyword (these are the special forms).
Before an expression can be evaluated, all special forms in the expression
must be transformed into expressions that are "understood." To carry the
metaphor a bit further, we cannot understand the complete thought conveyed
by the author of a passage until we have transformed all terms into words
we understand. In a sense, we cannot evaluate the author's passage without
the appropriate substitutions taking place. Similarly, we cannot evaluate a
Scheme expression until all the transformations have occurred. Each trans-
formation brings the expression closer to one in which all terms are familiar.
Thus, we do not evaluate an expression with a list that begins with a derived
keyword. When all such lists have been transformed, it is time to evaluate
the expression. Prior to evaluation there is a recursive program that removes
all such lists.

14.2 Declaring a Simple Special Form

In this book we have used several special forms without defining them as
procedures. In fact, it is the nature of these forms that they cannot (or
should not) be defined as procedures either because some of their operands
are not to be evaluated or because the order of evaluation of their operands is

not the same as in a procedure application. We


use the terminology that we
define procedures, but we declare special forms. The mechanism for declaring
special forms will be explained in the course of making a specific extension to
the syntax.
If we write

(define sm (+ 3 4))

^ We procedure here, since the way it is written is determined by what


shall not write that
the system assumes it For purposes of discussion, we assume the system knows
knows.
define, if, lambda, quote, and set!. Other systems might know about a different set of
special forms. For example, if might be described in terms of cond, thereby causing us
to assume that the system knows cond. This freedom of choice gives implementors the
flexibility they need for efficient implementation.

450 Declaring Special Forms


the expression (+ 3 4) is evaluated and its value is bound to the variable
sm. Suppose that we want to assign this expression to the variable sm but
postpone the evaluation of the expression (+3 4) until we actually need the
value of sm. One way of doing this is to encapsulate the expression (+3 4)
within the body of a lambda expression having no arguments. We could then
write

(define sm (laabda () (+34)))

The body of a lambda expression is not evaluated until that lambda expres-
sion is applied to its arguments, and since the thunk (lambda () (+3 4))
has no arguments, it is invoked by merely enclosing the lambda expression in
parentheses. Since the thunk in this case is bound to the variable sm, we can
invoke it by enclosing sm in parentheses, that is, by writing (sm). We are thus
able to postpone the evaluation of an expression until we need it by making it

into a thunk and binding a variable to that thunk. It would be nice to have a
procedure freeze that, when applied to an operand, has the effect of forming
a thunk that has that operand as its body. Suppose we write:

(define freeze
(lambda (expr)
(lambda () expr)))

Then we would write:

(define sm (freeze (+ 3 4)))

But when the define expression is evaluated, before being bound to sm, the
expression (freeze (+ 3 4)) is evaluated. Since freeze is a procedure,
its operand (+ 3 4) is evaluated. Thus we defeated the purpose for which
we wrote the procedure freeze, which was to postpone the evaluation of its
operand until sm is called. What happened is that (+ 3 4) is evaluated during
the definition of sm instead of when sm is called. Thus freeze cannot be a
procedure; it has to be the keyword of a special form if it is to accomplish
what we want.
To declare this special form with keyword freeze, we make use of a special

form with keyword macro. We ^ would like freeze to have the syntax (freeze
expr) and to transform into the thunk (lambda () expr) without evaluating

^ At the time this book is being written, the Scheme community has not yet agreed upon a
standard way of declaring specied forms. In this book, we use two methods that have been

14-2 Declaring a Simple Special Form 4^1


the expression expr. We call the expression (freeze expr) the macrocode,
and we want to transform the macrocode into the macroexpansion

(lambda () expr)

In general, a macro is a procedure that transforms macrocode into the corre-


sponding macroexpansion.
When an expression is entered into the system, the first subexpression is

checked to see if it is a keyword of some special form. If it is, then the


macrocode (in our case, (freeze expr)) is replaced by the corresponding
macroexpansion. Then at run time, the computer sees only the macroexpan-
sion (lambda () we had written the macroexpan-
expr) in the program as if

sion into the program instead of the macrocode. Thus the subexpression expr
of the special form (freeze expr) was not evaluated when the procedure (or
thunk) was created by evaluating (lambda () expr).
How is the macroexpansion accomplished? We have to write a procedure
that literally transforms the macrocode into the macroexpansion of that code.
Let us call that procedure f reeze-transf orraer; it takes the macrocode code
as its argument and returns the code for the macroexpansion. In our case, the

macroexpansion is a list containing the three items that make up a lambda


expression: the symbol lambda, the empty list of arguments, and the body.
Thus we can define f reeze-transf ormer to be:

(define freeze-transfomer
(lambda (code)
(make-lambda-ezpression '() (list (2nd code)))))

where make-lambda-expression is applied to the formal parameter(s) (in


this case, it is the empty list) and a list of expressions (in this case, it is a
list containing only one element). The second expression in the macrocode
is expr. In our specific example, that is the list (+ 3 4). We define make-
lambda-expression to be:

(define make-lambda-ezpression
(lambda (parameters body-expressions)
(cons 'lambda (cons peirameters body-expressions))))

included in some implementations. These methods use special forms with keywords macro
and ertend-syntax. If these are not implemented in the version you are using, read the
manual for your implementation to see how it declares special forms, and use that method
instead. In general, until a standard is agreed upon, code including user-made special forms

is not portable.

452 Declaring Special Forms


Now thatwe have defined the freeze-transf ormer, we can declare the
special form with keyword freeze using the special form with keyword macro
as follows:

(macro freeze freeze-transf ormer)

We can conceive of this process of declaring a special form as if macro places


the keyword freeze in a global table we call the syntax table, along with its
transformer, which is the procedure freeze-transf ormer. Thus each entry in
the syntax table consists of a keyword and its associated transformer. When
a program is entered and the symbol freeze is found in the first position
of an expression, it looks it up in the syntax table, and if it finds it there,
it passes the macrocode ((freeze expr) in this case) to the transformer.
The transformer then returns the macroexpansion (in our example, (lambda
() expr)). This macroexpansion is program in place of the
inserted into the
macrocode. It is customary to refer to the keyword freeze as a macro, though
the macro actually is the whole macrocode. Following custom, we shall say
"the macro freeze."
We can also unwrap the various helping procedures used in defining the

procedure freeze-transf ormer to get a self-contained representation for the


macro declaration. For example, we can replace

(make-lambda-expression '() (list (2nd code)))

by the body of its lambda expression with its parameters replaced by the
arguments to which they are bound to get:

(define freeze-transf ormer


(lambda (code)
(cons 'lambda (cons '() (list (2nd code))))))

Finally, replacing freeze-transf ormer by its lambda expression gives us

Program 14.1 freeze

(macro fre eze


(lambda (code)
(cons ' lambda (cons '0 (list (2nd code))))))

14-S Declaring a Simple Special Form 4^^


)

as a self-contained form of the declaration of the macro freeze. Either the


version using the helping procedures or this final self-contained version de-
clares the macro freeze. You may use the version you find more convenient.

14.3 Macros

In general, the special form with keyword macro has the syntax

(macro name transformer)

where name is the keyword of the new special form being declared and
transformer is a procedure of one argument that takes the macrocode and
returns the macroexpansion. In our example above, freeze is the keyword,
and

(lambda (code)
(cons 'lambda (cons '
() (list (2nd code)))))

is the transformer. Thus we summarize by recalling that when a program


containing an expression starting with a keyword for a special form is entered,
the system replaces the macrocode by the code returned when the macrocode
is passed to the keyword's transformer. It is this expansion that is seen when
the program is run.
The macro freeze can also be implemented to take several subexpressions;
this would let us write, for example,

(freeze (writeln "Hello") "How are you?")

and would macro expand into

(lambda () (writeln "Hello") "How are you?")

In general, we would like freeze to have the syntax

(freeze expri expr2 . .

where the ellipsis (three dots) means that there is a finite number of expres-
sions following the word freeze and that there is at least one such expression.*

* In general, the notation thing means zero or more occiirrences


. . . of thing, whereas
thingi thing2 - • means one or more occurrences of thing.

454 Declaring Special Forms


This is a pattern for our macrocode but it cannot be used as the macrocode
itself since it contains the ellipsis and the special form macro will not know
what to do with it. Using a similar notation, we can say that a pattern for

the macroexpansion is:

(lambda () expri expr^ ...)

A convenient notation to indicate that the first pattern is to be expanded into


the second pattern is:

(freeze expri expr2 )= (lambda () expri expr2 )


The symbol = can be read "macro expands to." We call a statement that has
the macro pattern on the left and the expansion pattern on the right a syntax
table entry.

In any actual case, the macrocode is a list that starts with the keyword
freeze and always has at least one expression following it. If we represent
this macrocode by the variable code again, then (cdr code) is just a list of
the expressions that make up the body of the lambda expression into which the
macrocode is expanded. The f reeze-transf ormer procedure defined above
can be modified so that it produces the right macroexpansion for this version
of freeze:

(define f reeze-transf ormer


(lambda (code)
(make-lambda-expression '
() (cdr code))))

It would be convenient if Scheme were to have a way of taking the two sides
of the syntax table entry and declare the special form for us. In essence, the
system would be writing the transform procedure for us and using it to declare
the macro. Such a special form, called extend-syntax,^ was developed (see

Kohlbecker, 1986). It has the following syntax:

^ Here is a way to get macro if you have ext end-syntax in your implementation of Scheme:

(extend-syntax (macro)
((macro name transformer)
(let {(t transformer))
(extend-syntax (name)
(x ((with ((h 'with)) w) ((v (t 'x))) v))))))

See Dybvig, 1987, for a discussion of extend-syntax's with clauses.

14.3 Macros 455


(extend- syntax (.name ...) imacro-pattern expansion-pattern) ...)

where macro-pattern and expansion-pattern are the left and right sides, re-

macro called name. Using ext end-


spectively, of the syntax table entry for the
syntax, the declaration of the macro freeze becomes:

Program 14.2 freeze

(extend-syntax (freeze)
((freeze exprl expr2 ...) (lambda exprl expr2 ...)))

Since no standard way of making special forms has been agreed upon, we shall
demonstrate both ways of doing it — that is, using macro and extend-syntax
in the rest of this chapter.

Along with the macro freeze, there is the procedure thaw, which invokes a
frozen entity (a thunk) and returns its value. The procedure thaw is defined
as follows:

Progr£iin 14.3 thaw

(define thav
(lambda (thunk)
(thunk)))

To show how it is used, we define:

(define th (freeze (display "A random number is: ") (ramdom 10)))

(thaw th) =^* A random number is: 7


(thaw th) =^ A remdom niomber is: 3

Each time the thunk is thawed, the expressions are reevaluated. Thus each
time we thawed the thunk th in the example, another random number is

computed and returned.


There are occasions when we want to postpone the evaluation of an expres-
sion but have it be evaluated only the first time it is called and thereafter
not have to reevaluate the expression each time it is called again but rather
return on each subsequent call the value already evaluated. This would be
advantageous if the same long calculation is involved each time the procedure

456 Declaring Special Forms


Program 14.4 make-promise, force

(define make-promise "procedure •)

(define force "procedure")

(let ((delayed-tag "delay' ) (value-tag "-->"))


(set ! make-promise (lambda (thunk) (cons delayed--tag thunk)))
(set ! force
(lambda (arg)
(if (and (pair? arg) (eq? (car arg) delayed-1:ag))
(begin
(set-car arg value--tag)
(set-cdr arg (thas (cdr arg)))))
(cdr arg))))

is called and the result obtained is the same, in the absence of side effects. We
propose to evaluate the postponed expression only the first time it is called
and on subsequent calls to return the already computed value. We declare
the special form delay to postpone the evaluation by creating a promise, and
a corresponding procedure force to evaluate (or "force") the promise. When
the promise is forced for the first time, the value of the postponed expres-
sion computed and returned. Each succeeding time the promise is forced,
is

the same value that was computed the first time is returned. Consider the
following:

(define pr (delay (display "A random number is: ") (random 10)))

(force pr) => A random number is: 6


(force pr) =^ 6
(force pr) =^ 6

and it continues returning 6 each time it is forced from now on.


The syntax table entry for delay is

(delay expTi expT2 ...) = (make-promise (freeze expri expr2 ...))

where make-promise is a procedure that takes a thunk as its argument and


returns a promise, which is a thunk tagged with "delay". (See Program 14.4.)
If force's argument is a promise, force converts the promise into a fulfillment.
A promise is converted into a fulfillment by tagging with " >" the value —
obtained by thawing the promise's thunk. In any event, the value stored in

14.3 Macros 457


the fulfillment is returned. Program 14.4 is written so as to protect the tags
from accidental reassignment.
We can now proceed to declare the macro delay. It has the macrocode

(delay expri expr2 )

which macroexpands into

(make-promise (freeze expri expT2 ..))

As we cannot define delay to be a procedure because its arguments


before,
expri expT2 would be evaluated too early. Using extend-sjrntax, we can
declare delay by simply writing:

Program 14.5 delay

(extend- syntax (delay)


((delay expri expr2 ...) (make-promise (freeze expri expr2 ...))))

Or, by using macro, we get

Program 14.6 delay

(define delay-transformer
(lambda (code)
(list 'make-promise (cons 'freeze (cdr code)))))

(macro delay delay-transformer)

As we have seen, in a procedure call, Scheme first evaluates the operands


(producing arguments) and the operator (producing a procedure) and then
applies the procedure to the arguments. We say that the arguments are
passed to the procedure "by value." In some languages, arguments are passed
to procedures as if they were thunks, and they are not thawed until they are
actually used in the procedure. Such arguments are said to be passed to the
procedure "by name."® We can write programs in Scheme so that procedures

In the presence of side effects, this is an oversimplification.

458 Declaring Special Forms


accept arguments that are thunks. These arguments are thawed when they
body of the procedure, so that passing of arguments by name
are used in the
can be accomplished in Scheme. Similarly, it is possible to pass arguments
to procedures as promises, which are not forced until they are needed in the
body of the procedures. In such cases, the arguments are said to be passed "by
need." In Chapter 15, we shall study streams, which use arguments passed
by need.
We have been using the special form with keyword let, which has the
syntax^

(let ((var val) ...) expri expr2 )


The syntax table entry for let is

(let ((var val) ...) expri €xpr2 )


((lambda (var ...) expri expr2 ) val ...)

The declaration of let is now a simple matter when we use extend-syntax


as in Program 14.7.

Program 14.7 let

(extend-syntax (let)
((let ((vaur val) ...) expri expr2 ...)
(danbda (var ...) expri expr2 ...) val ...)))

To declare let with macro, we have to build an application that consists


of a list containing a lambda expression followed by its operands. For the
lambda expression, we need its parameter list and its body expressions. If

code represents the macrocode, then the list of parameters is built up by first

taking the (2nd code) to get a list of pairs of var' s and val's. We extract the
list of var's by taking the 1st of each pair in the list using map as follows:

^ When using user- declaired macros that have the same keywords cis special forms in Scheme,

you might want to avoid collisions with the built-in forms. We suggest that you siuround
the keywords of those you declaire with equed signs; e.g., =let= in place of let.

14.3 Macros 459


(define ii«ike-li8t-of-para»eters
(laabda (code)
(ap Ist (2nd code))))

Similarly, we can build the list of operands from the macrocode by taking the
2nd of each pair. This leads to:

(define Beike-list-of-operands
(lambda (code)
(nap 2nd (2nd code))))

A list of the items in the body of the lambda expression we are building is

obtained by taking the cddr of the macrocode. Thus:

(define Bake-list-of-body-items
(lajBbda (code)
(cddr code)))

With these helping procedures, we can write the transform procedure and
declare it as the macro for let.

Program 14.8 let

(define let-transf oraer


(lambda (code)
(cons (make-laabda-expression
(nake-list-of-paraaeters code)
(make-list-of-body-items code))
(aike-list-of -operands code))))

(aero let let-treinsformer)

This is really only half of the declaration of the macro let since there is also
the so-callednamed let, which has a different syntax. We shall return to
the named let in the exercises, where we rely on the following discussion of
letrec. The above version of the macro declaration of let using the special
form with keyword macro clearly illustrates the advantage of using extend-
syntajc to declare a macro. Exercise 14.6 at the end of this section suggests
some interesting modifications to let so that it displays appropriate messages
when an expression with keyword let is entered with an incorrect syntax. For
example, if we write (let ( (a 3) ) ) incorrect syntax should be signaled since
,

460 Declaring Special Forms


. .

a let expression must contain at least one subexpression following the binding
pairs. If we use macro to declare our special forms, we must explicitly include

tests in the definition of the transformer to determine if the syntax is correct.

On the other hand, one of the great advantages of using ext end-syntax is

that it has built-in syntax checking, so we do not have to include our own tests

for correct syntax. You may find it some let expressions


instructive to enter
with incorrect syntax in your implementation of Scheme and see the messages
that are displayed.
We observed that in a let expression of the form

(let ((t/ar val) ...) expTi ex'pr^ ...)

the expression val . . . whose value will be bound to var . . . cannot contain
var . . . recursively, for looking at the pattern for the macroexpansion,

((lambda (var ...) expri expT2 ) val ...)

we see that val ... is not in the scope of var . .


.
, so any instance of var . .

in val . . . refers to an outer scope. The special form letrec does allow for a
recursive scope.
The macro letrec has the syntax table entry:

(letrec (.{var val) ...) expri expr2 )


(let ((var "any") ...) (begin (set! var val) ...) expri expr2 )
In this expansion, if any one of the val^s contains instances of any of the
var's, that val is in the lexical scope of those var's in the let expression of the

macroexpansion. This allows the use of recursion in var. Let us now write the
macro for letrec. Again, it is a simple matter to do so using ext end- syntax.

Program 14.9 letrec

(ext end-syntax (letrec)


((letrec ((var val) ...) expri expr2 ...)
(let ((var "any") ...)
(set ! var val) . .

expri expr2 ...)))

Consider the definition of the procedure odd?, which is defined using a letrec
expression:

14.3 Macros 461


(define odd?
(letrec
((even? (lambda (n) (if (zero? n) #t (odd? (subl n)))))
(odd? (lambda (n) (if (zero? n) #f (even? (subl n))))))
odd?))

It macroexpands into the following let expression:

(define odd?
(let ((even? "any")
(odd? "any"))
(begin
(set! even? (lambda (n) (if (zero? n) #t (odd? (subl n)))))
(set! odd? (lambda (n) (if (zero? n) #f (even? (subl n))))))
odd?))

Let us next look at how to declare letrec using macro. We first consider
how we construct the pairs of the form (var "ajiy"), which are in the let

expressions of the macroexpansion. After we get the var's from the 2nd of
the macrocode, we use map to give us the desired pairs of the form (var
"any"). Similarly, we build the set! expressions, and finally, we build a list

of expressions that complete thebody of the let expression. This leads to the
declaration of letrec using macro that is given in Program 14.10,

Program 14.10 letrec

(macro letrec
(lambda (code)
(cons 'let
(cons (map (lambda (z) (list (1st z) "any")) (2nd code))
( append
(map (lambda (z) (cons 'set! z)) (2nd code))
(cddr code))))))

Something you usually want to avoid is the creation of infinite loops. How-
ever, as an interesting demonstration of the use of letrec, we shall write
a special form cycle that takes an arbitrary number of subexpressions and
runs each subexpression in succession and then starts over again, repeating
this loop indefinitely. The syntax table entry for cycle is

(cycle expri expT2 ...) = (cycle-proc (freeze exprl expT2 ...))

462 Declaring Special Forms


Program 14.11 cycle-proc

(define cycle-proc
(lambda (th)
(letrec ((loop (lambda
(thaw th)
(loop))))
(loop))))

where cycle-proc is defined in Program 14.11. In Chapter 17, we shall

encounter several uses of cycle-proc.


The last special form that we discuss has keyword or. First why must or
be a macro instead of a procedure? When we write (or ei 62), the first
subexpression ei is evaluated, and if it is true, then its value is returned. If ei
is false, only then is 62 evaluated. If or were a procedure, both subexpressions
would be evaluated before they are passed to or. The fact that the second
subexpression is not evaluated unless the first is false allows us to include the
following expression in a program:

(or (zero? x) (> (/ 10 x) 2))

and be sure that division by zero does not occur because the second subex-
pression is not evaluated if x is zero. Thus we want or to be a macro that
can take any number of subexpressions, including no subexpressions. If or is

called with no subexpressions, it returns false. Having taken care of the case
of no subexpressions, we consider the following syntax table entry for or with
several subexpressions:

(or ei 62 ...) = (if ei ei (or 62 ...))

This works because if first evaluates ei and if it is true, it returns the value of
ei in the consequent. If ei is false, it skips to the alternative and returns the
"recursive" value obtained for the alternative. This looks like recursion, but
we must remember that these or expressions are not being evaluated. Rather
they are macrocode, which is being transformed into if expressions that are
the macroexpansions. We have treated the case of (or e), which should have
the same value as e, because using the syntax table entry, (or e) expands to
(if e e (or)) and (or) expands to #f.
We could use the above macroexpansion for or, but it does not work effi-

ciently since if ei is true, it must be evaluated a second time in the consequent.

14.3 Macros 463


If Ci includes some side effects, these would be done twice insteeid of once, and
that is generally incorrect. We can avoid this double evaluation by including
a let expression in the macroexpansion:

(or ex 62 ...) = (let ((val ei)) (if val val (or 63 ...)))

Once again, if we declare the macro according to this expansion pattern, it


willwork the way we want almost all of the time. But an unwanted behavior,
known as capturing, can occur, as the following example illustrates. Suppose
the macro or has been declared according to the above pattern. We then use
it in the following program:

(let ((val #t))


(or »f val))

We expect this to return #t. However, when the program is entered, the or
expression is expanded into

(let ((val #f))


(if val val val))

and the value returned is #f because the leist val has been captured within
the scope of the nearest binding, and unfortunately the variable val was also
used in the let expression in the declaration of the macro or. There are several
ways of avoiding this capturing. We shall make use of the fact that when a
frozen entity is thawed, it is evaluated in the environment that was in effect
when the entity was frozen. We first define a procedure, called or-proc, which
takes a list of thunks as its operand. Then to declare the macro or, we freeze
the operands and pass them to the procedure or-proc. Here is the definition
of or-proc:

Progrgun 14.12 or-proc

(define or-proc
(lambda (th-lis.t)
(cond
((null? th- list) «f)
(else (let ((v (thaw (cai th-lis t))))
(if V V (or- Droc (cdr th -list))))))))

454 Declaring Special Forms


In this version, the thunks are not evaluated until they are thawed, so only
one of the thunks is evaluated at a time until a true value is obtained. The
rest remain unevaluated.
With this definition of or-proc, the syntax table entry for the macro or
becomes:

(or e ...) = (or-proc (list (freeze e) ...))

How are the cases of zero expressions and one expression handled by this

entry? Now or-transf ormer can be defined and or can be declared:

Program 14.13 or

(define or-transf ormer


(lambda (code)
(list 'or-proc
(cons 'list
(map (lambda (e) (list 'freeze e))
(cdr code))))))

(macro or or-transf ormer)

We can also use extend-syntax to declare the macro or based on the above
syntax table entry. We have:

Program 14.14 or

(extend-syntax (or)
((or e ...) (or-proc (list (freeze e) ...))))

Several more special forms are developed in the exercises. The ability to
write your own Scheme is a powerful tool that can be used to
special forms in
make programs more readable. Most important, it allows you to build your
own textual abstractions. In the next chapter, we shall make use of the special
form delay to develop the idea of streams or "infinite lists."

14.3 Macros 465


)

Exercises

Exercise 14-1
What is the output of

(freeze-treinsf ormer '(freeze (cons 'a ' (b c))))

What is the output of

(let-transformer '(let ((a 5) (b 2)) (* a b)))

What general statement can you conclude from these examples concerning the
output when a transform procedure is applied to the quoted macrocode? Some
implementations of Scheme have a procedure called expand, which converts
the quoted macrocode into its macroexpansion.

Exercise 14-2
Declare the letrec macro using ext end- sjrnt ax without using let in its macro-
expansion.

Exercise 14-3
Consider the declaration of the macro or, below. Does this declaration suffer
the variable capturing that we were able to avoid using or-proc and a list of
thunks?

(extend-syntai (or)
((or) «f)
((or e) e)
((or el e2 ...) (let ((val el) (th (freeze (or e2 ...))))
(if val val (thaw th) ) ) )

Exercise 14 4' smd


Declare a macro with keyword and, which, like or, may take any number
of subexpressions. If called with no subexpressions, it is true. If all of its

subexpressions are true, it evaluates to the last one; otherwise it is false. Test
your macro on:

(and)
(and «t)
(and «f)
(and #t #t »t)
(and #t »t #f)

Note that the capturing problem need not arise in declaring and.

466 Declaring Special Forms


Exercise 14-5
The let expression

(let ((x 3))


(let ((x 10) (y x))
y))

evaluates to 3 because the x in the binding pair (y x) must look up its value
in an environment other than the local environment of the expression

(let ((x 10) (y x))


y)

The value 3 is found since that let expression is nested within the let expression
with binding pair (x 3). If we had wanted the x in (y x) to refer to the x in

(x 10), we would have had to put the (y x) in another nested let expression,
as follows:

(let ((x 3))


(let ((x 10))
(let ((y x))
y))) =* 10

In general, in the let expression

(let ((uari vali) (.var2 vo/2) (vars vala)) expri expr2 ...)

instances of vari in val^ and instances of var^ or var2 in vals cannot refer
to vari or var2 in this let expression but must find their values in a nonlocal
environment. However, if we were to write nested let expressions, such as

(let ((vari vali))


(let i(vaT2 va/2))
(let ((var^ va/3))
expri expr2 ...)))

then instances of vari in va/2 can refer to the vari in the first binding pair, and
instances of vari or var2 in vals can refer to the f ari or i'ar2 of the preceding
two binding pairs. We used the Scheme special form let* in Section 10.2.5.

It has a syntax similar to that of let but behaves as though the successive
binding pairs are in nested let expressions. In fact, if there is only one such
binding pair, then let* is the same as let, so that

14.3 Macros 467


(let* (.ivar val)) expri expr2 )= (let ((var val)) ezpri expT2 )
and if there is more than one such binding pair,

(let* ((vari vali) ivaT2 val2) ...) expT\ expr2 ...)

(let ((rari vali)) (let* ((i;ar2 val2) .-) expri expr2 ...))

Write let*-traiisf ormer or use extend- syntax to declare let*. Test it on


the following:

(let* ((a 1) (b (+ a 2)) (c (* a b))) (+ a (- c b)))

Exercise I4.6
The procedure let-transformer is correct only if the user obeys let's syntax.
The special form let expects a list of n 2 elements. The first must be the
-f-

symbol let; the second must be a list of pairs where each pair is a list of two
elements, in which the first element must be a symbol. The remaining n >
elements can be arbitrary expressions. Here are some incorrect examples:

(let (d 3) (y 4)))
(let ((3 3) (y 4)) (* x y))
(let (d 3) (y 4 5)) (* i y))
(let X 3 (* I y))
(let (("i" 3) (y 4)) (* "i" y))

Rewrite let-transformer so that reasonable error indications, such as those


shown below, are given to the user of let. Test these examples by invoking
let-tremsf ormer on the individual lists in question:

(let-transformer '(let (d 3) (y 4))))


Error: illegal let expression: (let ((x 3) (y 4)))
^
(let -transformer '(let ((3 3) (y 4)) (* x y))) =>
Error: illegal let expression: (let ((3 3) (y 4)) (* x y))

(let-transformer '(let ((x 3) (y 4 5)) (* i y))) ^


Error: illegal let expression: (let ((x 3) (y 4 5)) (* i y))

(let-transformer '(let i 3 (* i y))) ^^


Error: illegal let expression: (let x 3 (* x y))

(let-transformer '(let (("x" 3) (y 4)) (* "x" y))) =>


Error: illegal let expression: (let (("x" 3) (y 4)) (* "x" y))

468 Declaring Special Forma


) )

Exercise 14.7
The error information from the previous exercise does not pinpoint exactly
where the error occurred. Redesign the information displayed so that you can
better determine where the error occurred.

Exercise 14-8: named let


The macro let declared above did not include the case of the named let.
The named-let has the syntax table entry:

(let name (.(.var val) ...)


expri expT2 ) . . •

((letrec
(,inam,e (lambda (.var ...)
expri expr2 . . ))
name)
val . . . )

Define let-transformer or declare let using extend- syntax to include both


cases, the ordinary let and the named-let. Do Exercise 5.7 using named-
let.

Exercise 14-9: cycle


Define cycle-transformer or declare cycle using extend-s3^itax.

Exercise 14-10: while


The special form while is a control structure common to many program-
ming languages. In while, an expression is evaluated repeatedly as long as a
given condition is true. We can efi"ect the behavior of a while expression as
illustrated by the following program, which sums the numbers from 1 to 100:

(let ((n 100) (sum 0))


(letrec ((loop (lambda ()

(if (positive? n)
(begin
(set! sum (+ sum n))
(set ! n (subl n)
(loop))))))
(loop)
sum))

We would like to introduce the special form while, which allows us to write
the above program eis:

14.3 Macros 469


(let ((n 100) (sum 0))
(while (positive? n)
(set! sum (+ sum n))
(set! n (subl n)))
sum)

Thus while has the syntax table entry:

(while test expri expr2 )


(letrec
((loop (lambda ()
(if test (begin expri expr2 ... (loop))))))
(loop))

Define while-transformer or declare while using extend-synteuc. You must


take into account the variable capturing that is caused when the variable loop
occurs free in test or expr ... in the macroexpansion. The syntax table entry
for while must then be modified to be of the form

(while test expri expr2 )


(while-proc (freeze test) (freeze expri expT2 ...))

where while-proc is defined in Program 11.8. Test while on the above


program.

Exercise 14-11: repeat


The special form repeat takes two expressions. It executes the first expres-
sion. Then it executes the second expression. If that returns true, the expres-

sion terminates with an unspecified value. If not, it same


repeats in much the
way as while from the previous exercise. Define repeat -transformer or
declare repeat using ext end-syntax by including while in its macroexpan-
sion. Then redo the exercise without using while. Finally, write an expression

using repeat that models the test program of the previous exercise.

Exercise 14.12: for


Write a special form that models the behavior of for expressions. Such ex-
pressions have the following syntax:

(for var initial step test expri expr2 •)

470 Declaring Special Forma


.

The for expression is used for modeling iteration. The variable var is initial-

ized to initial. Then the test is evaluated to determine whether it should


terminate. If test is true, it does terminate. If test is false, then expr . .

is evaluated. Finally, var is reset to the evaluation of step, and the process
repeats.
Define for-traLnsformer or declare for using extend-syntax given the
syntax table entry below.

(for var initial step test expri expT2 )


(let iivar initial))
(let ((step-thunk (freeze step))
(test-thunk (freeze test))
(body-thiink (freeze expri exprQ ...)))
(while (not (thaw test-thunk))
(thaw body-thunk)
(set! var (thaw step-thiink)))))

This solution is subtle because each of step, test, and expri expr2 will be . . .

using var. For example, a typical use of for expressions is to add the elements
of a vector:

(define vector-siim
(lambda (v)
(let ((n (vector-length v))
(sum 0))
(for i (addl i) (= i n) (set! sum (+ sum (vector-ref v i))))
sum)))

Exercise 14.13: do
The special form do has the syntax table entry:

(do {{var initial step) ...)


{test exit\ exit2 . )
expri expr2 •)

((letrec
((loop (lambda (.var ...)
(cond
{test exiti exit2 )
(else (begin expri expr2 . •)
(loop step ...))))))
loop)
initial . . . )

The variable loop must not be among var . . . and it must not be free in test,

14.3 Macros 471


exiti exit^ . .
.
, expri expr2 , and step . . . Redesign f or's syntax table
entry using do. (See the previous exercise.)

Exercise 14.14' beginO


Consider the following syntax table entry for beginO:

(beginO e) = e

(beginO ei 62 63 . . . ) = (beginO-proc ei (freeze 62 63 ...))

beginO evaluates its subexpressions in order and returns the result of evalu-
ating the first one. Define the procedure beginO-proc, which always takes
exactly two arguments. Why is the syntax table entry

(beginO expri expr2 ...) ^ ((l£unbda args (car args)) expri expr2 )
incorrect? [Hint: Read the specification carefully. What can we say about
the order of evaluation of operands?) Test beginO-proc by defining beginO-
transf ormer or declaring beginO using extend-syntax.

Exercise 14-15: begin


Define begin-transf ormer or declare begin using extend-syntax without
using freeze or the implied begin associated with lambda expressions.

Exercise I4.I6: cond


Consider cond expressions that are restricted to including at least one expres-
sion following each test in every clause and where the last clause must be
an else clause. They can be transformed into nested if expressions using the
following two-patterned syntax table entry:

(cond (else ei 62 ...)) = (begin ei 62 ..)

(cond {test ei 62 . . . ) clauses . . . )

(if test (begin e\ 62 ...) (cond clauses ...))

Redefine member-trace and factorial below, using just the syntax table
entry for cond expressions.

4 72 Declaring Special Forms


(define member-trace
(lambda (item Is)
(cond
((null? Is) (writeln "no") #f)
((equal? (car Is) item) (writeln "yes") #t)
(else (writeln "maybe") (member-trace item (cdr Is))))))

(define factorial
(lambda (n)
(cond
((zero? n) 1)
(else (
n (factorial (subl n)))))))

Exercise 14-17: cond


In order to declare the simplified cond with extend-syntax, the symbol else
must be included in the first operand to extend-syntax. That is because
extend-syntax has to be told what symbols it is supposed to be treating
literally. In most cases, it is just the special form name, but for cond and

case, it includes the symbol else. Fill in the rest of the declaration of cond
below. (See the previous exercise.)

(extend-syntax (cond else)


((cond (else el e2 ...)) ? )

((cond (test el e2 ...) clauses ...) ? ))

Exercise 14-18: variable-case


Consider a variant of the case expression called variable-case. This expres-
sion is similar to case, except that instead of allowing its first operand to
be any expression, it is limited to being a variable. Thus, using case we can
write:

(case (remainder 35 10)


((2468) (writeln "even") (remainder 35 10))
((13 5 7 9) (writeln "odd") (remainder 35 10))
(else (writeln "zero") (remainder 35 10)))

but with variable-case we must write:

(let ((x (remainder 35 10)))


(variable-case x
((2 4 6 8) (writeln "even") x)
((13 5 7 9) (writeln "odd") x)
(else (writeln "zero") x)))

14.3 Macros 473


Complete the declaration of variable-case presented below, and then de-
fine vaxiable-case-transf ormer. Explain why keys has been transformed

into (quote keys). Hint: Remember that keys will be a list.

(extend-syntaz (variable-case else)


((vairiable-case vax (else el e2 ...)) ? )

((variable-case var (keys el e2 ...) clauses ...)


(if (memv var (quote keys))
(begin el e2 .) . .

(variable-case var clauses ...))))

Exercise 14.19
If we did not have variable-case from the previous exercise, then the case
example above would require an additional evaluation of (remainder 35 10).
Instead, we can choose a variable, say target, that will always hold the value
of the first operand of the most deeply nested case expression. Given this

constraint, declare this variant of case using extend-syntax. Hint: If you


use variable-case, you need only one rule for its synteix table entry, but
remember to include else in the list of symbols to be taken literally. Test
your program with the following case expression:

(case (remainder 35 10)


((2 4 6 8) (writeln "even") target)
((13 5 7 9) (writeln "odd") target)
(else (writeln "zero") target))

Exercise 14-20: object-maker


In Chapter 12 we presented a set of object-oriented programs that had a
particular pattern of use. For example, each object maker includes

(lambda msg (case (1st msg) ...))

Design a special form object-maker that abstracts this pattern of use. Are
there other patterns of use with object makers that can be abstracted?

4 74 Declaring Special Forma


15 Using Streams

15.1 Overview

In this chapter, we discuss streams, a data structure that enables us to process


infinite lists of items.We apply streams to handle input and output from files;
in particular, we construct a rudimentary formatter. To do this, we include a
brief introduction to the Scheme character data type.

15.2 Delayed Lists

In Chapter 14, we discussed the special form with keyword delay, which is

used to postpone the evaluation of an expression until it is needed. Thus


when we write (delay expri expr2 ...), a promise is returned, and the
body expri expr2 ... is not evaluated. When this promise is forced using
the procedure force, the body is evaluated, and the value is remembered.
Thereafter, each time the promise is forced, it returns the remembered value
instead of reevaluating its body. Thus, creating a promise has the effect of

"memoizing" the body, as well as delaying its evaluation. We shall now see
how we use this "lazy evaluation" to handle infinite lists.

Suppose that our work requires that we process a list of random numbers,
but we are not sure how long the list has to be. We can choose a very large
number and make a list that long, each member of the list being a random
number, say, between 2 and 12, inclusively. Thus, the list can be generated
by
(define random-2-to-12-list
(lambda (n)
(if (zero? n)
'()

(cons (+ 2 (random 11)) (random-2-to-12-list (subl n))))))

Then when (random-2-to-12-list 100) is called, a list of 100 random num-


bers is created. Suppose we now add the numbers from the beginning of the
list until the first time the number 7 is reached, at which time the sum is

printed along with the number of integers summed. The following program
does this:

(define sum-until-f irst-7


(letrec
((local-sum
(lambda (rl sum count)
(if (null? rl)
(writeln
"A seven nas not found; sum = "
sum " and count = " count)
(let ((next (car rl)))
(if (= next 7)
(writeln "sum = " sum " nhen count = " coiint)
(local-sum
(cdr rl)
(+ next Slim)
(addl count))))))))
(lambda (rand-list)
(local-sum rand-list 0))))

Here are some sample runs of this program:

[1] (sum-until-f irst-7 (random-2-to-12-list 100))


sum =31 when count = 4
[2] (sum-until-f irst-7 (random-2-to-12-list 4))
A seven was not foujid; siun = 28 and count = 4

When we (sum-until-f irst-7 (random-2-to-12-list 100)), a list


called
of 100 random numbers was generated, and they were processed from the
beginning of the list, adding the successive numbers until the first 7 was
encountered, at which time the sum and count were printed. But a list of 100
random numbers was generated, and only 4 were used. Is there any way to

j^16 Using Streams


create a list that has the property that the next random number will not be
generated until we are ready to process it?

We have seen how to postpone the evaluation of an expression by delaying


it. This will let us redefine the procedure that builds the random list so that
each time cons is called, its second operand is delayed. We shall call a list

built using such conses a delayed list. We illustrate this method of producing
a delayed list by constructing del-list containing the two elements (fib 8)
and (fib 9):

(define del-list
(cons (fib 8)
(delay (cons (fib 9)
(delay '())))))

In order to look at the first element of del-list, we take the car. Our goal
now is to define operators on the data type delayed lists.The first of these is
delayed-list-car, which is the same as car, since we do not delay the first
argument to cons. Thus

(define delayed-list-car
(lambda (x)
(car x)))

Observe that this definition can be written more compactly:

Program 15.1 delayed-list-car

(define delayed-list-car car)

If we (delayed-list-car del-list), 21 is returned. To get to the


call

second element in del-list, we must first take the cdr of del-list, yielding
a promise, and then force that promise. The result is another delayed list:

(cons (fib 9) (delay '()))

If we next apply delayed-list-car to this delayed list, 34 is returned. We


often use the sequence of operations consisting of taking the cdr and then
forcing the resulting promise. Thus, we define delayed-list-cdr to be that
sequence of two operations:

15.2 Delayed Lists 477


(define delayed-list-cdr
(lambda (x)
(force (cdr x))))

or, more compactly,

Program 15.2 delayed-list-cdr

Then we can get the second element of del-list by calling:

(delayed-list-cax (delayed-list-cdr del-list) ) ==» 34

Because of the memoizing effect of delay, (fib 9) is evaluated the first time
the above call is made, and the value 34 is stored. The next time the call
is made, 34 is returned without reevaluating the (fib 9) in (cons (fib 9)
(delay '())).
If we apply delayed-list-cdr to del-list and then apply delayed-list-
cdr to that result, we get the list (), which we call the-null-delayed-list.
'

We test for the-null-delayed-list with the predicate delayed-list-null?

defined as

(define delayed-list-null?
(lambda (delayed-list)
(null? delayed-list)))

We collect the definitions of the delayed list operators in Program 15.3.

Program 15.3 Basic definitions for delayed lists

(define the-null-delayed-list '())

(define delayed-list-null? null?)

(define delayed-list-car ceir)

(define delayed-list-cdr (compose force cdr))

478 Using Streams


)

In order to add a new object a to a delayed list b, we cons a onto (delay


b) to get (cons a (delay b)). In the spirit of what we did in Program 15.3,
we shall introduce delayed-list-cons to produce the above code, remem-
bering that we want to delay the evaluation of the second operand b. If we
were to define delayed-list-cons to be a procedure, then if we were to
call (delayed-list-cons a b), the fact that procedure applications evalu-

ate their operands before passing their values to the procedure defeats the
purpose of the delay. Thus, we must declare delayed-list-cons as a special
form using the following syntax table entry:

(delayed-list-cons expr del-Hat) = (cons expr (delay del-list))

We can declare it with the techniques of Chapter 14.

With delayed-list-cons, we can rewrite the definition of del-list as

(define del-list
(delayed-list-cons
(fib 8)
(delayed-list-cons
(fib 9)
the-null-delayed-list) )

so that building delayed lists looks analogous to building lists. We can also
rewrite the definition of r£aidom-2-to-12-list using our new constructor
delayed-list-cons to give us random-delayed-list:

Program 15.4 random-delayed-list

(define random-delayed-list
(lambda (n)
(if (zero? n)
the-null-delayed-list
(delayed-list-cons
(+ 2 (random 11))
(random-delayed-list (subl n))))))

We now rewrite the procedure sum-Tint il-first-7 using delayed lists as


follows:

15.2 Delayed Lists 4'79


(define sum-until-f irst-7
(letrec
((local-sum
(lanbda (delayed-list sun count)
(if (delayed-list-null? delayed-list)
(writeln
"A seven was not found; sum = "
sum " and count = " count)
(let ((next (delayed-list-car delayed-list)))
(if (= next 7)
(writeln "sum = " s\im " when count = " count)
(local-sum
(delayed-list-cdr delayed-list)
(+ next sum)
(addl count))))))))
(lambda (rand-delayed-list)
(local-sum rand-delayed-list 0))))

The output from this procedure has the same form as that of our previous
version, but now a random number is computed only when it is used.
In order to see the elements of a delayed list, it is convenient to have a
procedure delayed-list->list, which converts a delayed list delayed-list
into a list of its elements:

(define delayed-list->list
(lEunbda (delayed-list)
(if (delayed-list-null? delayed-list)
'()

(cons (delayed-list-car delayed-list)


(delayed-list->list (delayed-list-cdr delayed-list))))))

We can now use this to look at the elements in the delayed list (random-
delayed-list 20):

[1] (delayed-list->list (random-delayed-list 20))


(7 5 11 3 7 5 8 10 5 8 8 2 2 12 9 7 12 4 5 6)
[2] (delayed-list->list (random-delayed-list 20))
(2 43473995 10 44 12 777 11 55 3)
[3] (define rdelayed-list20 (random-delayed-list 20))
[4] (delayed-list->list rdelayed-list20)
(7 5 11 3 7 5 8 10 5 8 8 2 2 12 9 7 12 4 5 6)
[5] (delayed-list->list rdelayed-list20)
(7 5 11 3 7 5 8 10 5 8 8 2 2 12 9 7 12 4 5 6)

480 Using Streams


Exercises

Exercise 15.1
Define the delayed list consisting of the first n even integers starting with 0.

Define the delayed list consisting of the first n odd integers starting with 1.

Exercise 15.2: list->delayed-list


Define a procedure list->delayed-list that takes a list as its argument and
returns the corresponding delayed list. This procedure is useful for testing the
delayed list data type at the prompt.

Exercise 15.3: delayed-list-sum, delayed-list-product


Define a procedure delayed-list-sum that adds the first k elements in a
delayed list whose elements are numbers. If the delayed list has fewer elements
than k, add them all. Do the same for delayed-list-product, and then use
procedural abstraction to define a procedure delayed-list-accumulatefrom
which these can be obtained by suitably choosing its arguments. If one of the
elements of the delayed list evaluates to 0, the value should be returned for
the product without evaluating any additional elements of the delayed list.

Exercise 15.4
Delayed lists can be treated as an abstract data type with a few basic opera-
tors. Convince yourself that if the definitions of the five entities are given, all

of the remaining definitions in this section would still be defined:

• the-null-delayed-list,
• delayed-list-null?,
• delayed-list-car,
• delayed-list-cdr,
• delayed-list-cons.

One alternative way of defining these entities is based on the decision to delay
the car part as well as the cdr part of a cons cell. Thus, the syntax table entry
for delayed-list-cons becomes:

(delayed-list-cons val del-list) = (cons (delay val) (delay del-Hat))

How must the other four basic definitions of Program 15.3 be changed? Does
the behavior of any of the procedures defined in this section change using
these definitions of the five basic entities? Discuss the behavior of del-list

15.2 Delayed Lists 481


if these five definitions are used. In particular, discuss when (fib 8) and
(fib 9) are evaluated. What other syntax table entries can you suggest
for delayed-list-cons? How does each of them affect the other four basic
entities?

15.3 Streams

In the delayed list rdelayed-list defined by

(define rdelayed-list (random-delayed-list 100))

(delayed-list-car rdelayed-list) is a random number. It really does not


matter how long the delayed list is at this point, for no further calculation is

done. When delayed-list-cdr is invoked, another delayed list is returned.


Thus it is not necessary to indicate at any time how much of the delayed list

still remains, and hence the variable n and the terminating condition may be
omitted from the definition of random-delayed-list.
When the terminating condition is omitted in the definition of a delayed

list, we get what appears to be a nonterminating list, or what we can call

a stream. We have delayed-list-car, delayed-list-cdr, and delayed-


list-cons that use delayed lists. We have now introduced streams as a
new data type and, in order to be consistent about the data types, we limit
and give them new names when they are used with
their use to delayed lists
streams. The new names are stream-car, stream-cdr, and stream-cons.
The definitions of stream-car and stream-cdr are in Program 15.5. The
syntax table entry for stream-cons is:^

(streas-cons expr stream) = (cons expr (delay stream,))

^ Using techniques from Chapter 14, we can declare stream-cons with

(ext end-syntax (stream-cons)


((stream-cons expr stream) (cons expr (delay stream))))

or with

(macro stream-cons
(lambda (code)
(if (not (= (length code) 3))
(error "stream-cons: Wrong number of expressions" code)
(list 'cons (2nd code) (list 'delay (3rd code))))))

482 Using Streams


Program 15.5 stream-car, streeun-cdr

(define streaun-car ceur)

(define strean-cdr (compose force cdr))

Now we return to considering the elimination of the terminating condition


from the definition of random-delay ed-list. We get:

Program 15.6 random-stream-generator

(define random-stream-generator
(lambda ()

(stream-cons (+ 2 (random 11)) (random-streeun-generator))))

and

Program 15.7 random- stream

(define random-stream (random-stream-generator))

This looks as though the code for random-stream-generator contains a non-


terminating recursion and its invocation in random-stream (see Program 15.7)
causes an infinite loop, but when stream-cons is expanded within random-
stream-generator, it produces a stream whose cdr is not evaluated but in-

stead is waiting to be forced. When (stream-car random-stream) is in-


voked, a random integer is returned. When (stresun-cdr random-stream) is
invoked, a stream is returned, waiting for the next stream-car call, carrying
out the next recursive step. In general, a stream is defined recursively to be
a cons cell whose car pointer refers to a value and whose cdr pointer refers
to a delayed stream. Thus we may think of a stream as a nonterminating
(or infinite) delayed list. The discussion in Exercise 15.4 also applies to the
corresponding stream operations.
Another example of a stream is the-null-stream, all of whose elements are
the same; that common value is the-end-of-stream-tag, which we define to
be the string "end of stream":

15.3 Streams 483


Program 15.8 the-null-stream

(define the-null-stream
(stream-cons the-end-of -stream-tag the-null-stream))

Program 15.9 list->stream

(define list->stream
(lambda (Is)
(if (null? Is)
the-null-stream
(stream-cons (car Is) (list->stream (cdr Is))))))

Program 15.10 end-of-stream?

(define end-of-stream?
(lambda (z)
(eq? X the-end-of-stream-tag)))

(define the-end-of-stream-tag "end of stream")

We use the-null-stream (see Program 15.8) to define list->streajn, which


converts any list into a stream that contains the same elements and terminates
with the-null-stream. See Program 15.9.
If list->stream is given a circular list, then the result is an infinite stream.

For example,

(list->stream (let ((i (list 1 2 3))) (append! x x)))

A stream is called a finite stream if it has the property that from some element
on it becomes the-null-stream. Any noncircular list that is converted into
a stream is an example of a finite stream. We use the term infinite stream to
refer to those when such a distinction is called for.
streams that are not finite

The predicate end-of-stream? defined in Program 15.10 tests whether a


given stream element is the-end-of-stream-tag. In Program 15.11, the
predicate stream-null? uses end-of-stream? to determine whether its ar-

gument is the-null-stream.

484 Using Streams


Program 15.11 streajn-null?

(define streeun-null? (compose end-of-stream? stream-ceu:))

To look n elements of a stream strm, we use a procedure that


at the first
builds a list out of those n elements. If strm is a finite stream, we show
the list of its elements only up to where the-null-stream starts by passing
stream->list any negative number as its second argument. We define the
procedures stream->list and f inite-streain->list as

Program 15.12 streain->list, f inite-streani->list

(define streani->list
(lambda (strm n)
(if (or (zero? n) (stream-null? strm))
'()

(cons (stream-car strm)


(stream->list (stream-cdr strm) (subl n))))))

(define f inite-stream->list

(lambda (f inite-strm)
(stream->list f inite-strm -1)))

We can use streain->list to look at numbers generated by random-stream-


generator:

[1] (stream->list (random-stream-generator) 25)


(7 5 5 4 6 4 5 11 2 11 11 7 5 11 11 8 9 3 5 10 4 12 7 7 10)
[2] (streajn->list (random-stream-generator) 25)
(8 5 10 12 10 8 2 8 3 5 4 9 2 5 4 12 6 3 7 5 12 3 12 2 9)
[3] (stream->list remdom-stream 20)
(7 7956823567 10 12 33 11 544 5)
[4] (stre2un->list random-stream 25)
(7 7 9 5 6 8 2 3 5 6 7 10 12 3 3 11 5 4 4 5 10 11 12 8 4)

We see that random-stream (Program 15.7) contains a fixed stream of ran-


dom numbers, while calling (random-stream-generator) generates a differ-

ent stream of numbers each time it is called.

Other streams can be defined using stream-cons. For example, the stream
of positive integers can be defined as:

15.3 Streams 4^5


)

Program 15.13 positive-integers

(define positive-integers
(letrec
( (stream-builder
(lambda (x)
(stream-cons x (streaim-builder (addl x))))))
(stream-builder 1 ) )

The stream of even positive integers can be defined as follows:

Program 15.14 even-positive-integers

(define even-positive-integers
(letrec
( (stre«un-builder
(lambda (x)
(stream-cons x (stream-builder (+ x 2))))))
(stream-builder 2)))

Similarly, the stream of powers of 2 can be defined as:

Program 15.15 poHers-of-2

(define powers-of-2
(letrec
( (stream-builder
(lambda (x)
(streeim-cons x (stream-builder (* x 2))))))
(stream-builder 1)))

The definitions of these three streams share common features that lead us to
think about abstraction. We build-stream that abstracts
define a procedure
the structure of the definitions of these streams. The first place in which the
three differ is in the initial value of the argument x. We call this initial value
seed. The other place where they differ is in the procedure in the last line,

which appears as the operand to the local procedure. This procedure is the

486 Using Streams


)

Program 15.16 build-stream

(define build-strean
(leonbda (seed proc)
(letrec
( (strejuB-builder
(lanbda (x)
(stream-cons x (strezun-builder (proc x))))))
(stream-builder seed) ) )

rule for going from the current value of x to the next value of x. We call this

transition procedure proc. Then the procedure build-stream is defined in


Program 15.16. The three streams defined above can now be defined in terms
of build-stream, as follows:

(define positive- integers


(build-stream 1 addl))

(define even-positive-integers
(build-stream 2 (lambda (x) (+ x 2))))

(define powers-of-2
(build-stream 1 (lambda (x) (* x 2))))

and the stream of random numbers defined above can be defined using build-
stream if the seed is (+ 2 (random 11)) and the transition procedure is

(lambda (x) (+ 2 (random 11))). We have:

(define random-streeun-generator
(lambda
(build-stream (+ 2 (random 11)) (lambda (x) (+ 2 (random 11))))))

With a slight modification of the above technique, we can define the stream
of factorials. To do so, we define a local procedure stream-builder with two
parameters. We have:

15.3 Streams 487


)

Program 15.17 factorials

(define factorials
(letrec
( (stream-builder
(lambda (x n)
(stream-cons x (stream-builder (* x n) (addl n))))))
(stream-builder 1 1)))

Certain of the procedures that were defined in the previous section for de-
layed lists can be redefined for streams. For example, we can redefine the
procedure delayed-list-sum of Exercise 15.3 to get the procedure stream-
sum, which sums the first k terms of a stream of numbers. If a stream of
numbers is a finite stream, then by putting in the appropriate test for the-
null-stream, we can write sura-finite-stream, which sums num- all of the
bers in the stream preceding the-null-stream. If the stream is infinite, we
cannot ask for the sum of all of the elements of the stream. Similarly, we
cannot append one infinite stream onto another, since the first stream has no
end. Always be sure operations will terminate before applying them to infinite
streams.

From a given stream strm, we can build a new stream in which a given
procedure proc is applied to each element of strm. The procedure stream-
map, which builds this new stream, is defined by

Program 15.18 stream-map

(define stream- map


(Isunbda (proc strm)
(if (stream -null? strm)
the-null-stream
(stream -cons
(proc (stream-car strm)
(stre am-map proc ^stream--cdr strm))))))

This enables us to define the infinite stream of odd positive integers as shown
in Program 15.19.

Now let strml and strm2 be two infinite streams, and let an be the nth
element of strml and let bn be the nth element of strra2. If proc is a procedure
that takes two arguments such that (proc a^ bn) is defined, a stream can be

488 Using Streams


) ) )

Program 15.19 odd-positive-integers

(define odd-positive- integers


(stream-map subl even-positive-integers))

built that has the element (proc a^ b^) as its nth element. The procedure
that applies proc to the corresponding elements of the two infinite streams
to form the new stream stream-apply-to-both and is defined in
is called
Program 15.20. This enables us to define stream-plus and stream-times
as the streams obtained by taking the element-wise sum and element-wise
product of two streams of numbers. (See Program 15.21.)

Program 15.20 stream-apply-to-both

(define stream-apply-to-both
(lambda (proc)
(letrec
( (str-app
(lambda (si s2)
(stream-cons
(proc (streeun-car si) (stream-car s2))
(str-app (stream-cdr si) (stream-cdr s2))))))
str-app) )

Program 15.21 stream-plus, stream-times

(define stream-plus (stream-apply-to-both +)

(define stream-times (stream-apply-to-both *))

In Program 15.22, we stream-filter-out that removes


define a procedure
from a stream all of those elements for which a given predicate test? is true.
This gives us another way of defining the stream of odd integers from the
stream of integers by writing:

(define odd-positive-integers
((stream-filter-out even?)
positive-integers)

15.3 Streams 489


Program 15.22 stream-f ilter-out

(define streeua-f ilter-out


(lambda (test? )
(letrec
( (helper
(lambda (strm)
(let ((a (streaa-car strm)))
(if (test? a)
(helper (strean-cdr strm))
(stream-cons a (helper (stream--cdr strm))))))))
helper)))

We can give another interesting recursive definition of the stream of positive


integers using stream-map:

Program 15.23 positive-integers

(define positive-integers
(stream-cons 1 (stresun-map addl positive-integers)))

For if we add 1 to each of the elements in the stream of positive integers, we


get a stream of integers from 2. Then stream-consing 1 onto this stream of
integers starting from 2 gives us the stream of positive integers. We can also
look at the definition from another point of view that says we first stream-
cons 1 onto the stream, so the stream-car of positive-integers is 1. The

stream-cdr of positive-integers is the stream obtained by adding 1 to


each element of positive-integers, so its stream-csir is 2. Continuing in
this way, we see that this procedure recursively defines the stream of positive
integers.

Another definition of the stream of factorials can be motivated by the ob-


servation that if the items in the list of factorials shown below are multiplied
element-wise by the items in the list of positive integers, then we reproduce
the list of factorials with the first element missing.

1 1 2 6 24 120 720 ...

1 2 3 4 5 6 7 ...

multiply
1 2 6 24 120 720 5040

490 Using Streams


)

The stream of factorials can then be defined recursively by:

Program 15.24 factorials

(define factorials
(stream-cons 1 (stream-times factorials positive-integers)))

In a similar way, we can motivate the definition of the stream of Fibonacci


numbers by observing that if the list of Fibonacci numbers shown below is
added element-wise to the same list (without its leading 0) shifted one element
to the left, the resulting list is again the list of Fibonacci numbers, this time
without the first two numbers and 1.

112 3 5 8 13 ...

112 3 5 8 13 21 ...

add
1 2 3 5 8 13 21 34 ...

The definition of the stream of Fibonacci numbers is then:

Program 15.25 f ibonacci-numbers

(define f ibonacci-numbers
(stre<un-cons
(streaun-cons 1

(stream-plus
f ibonacci-numbers

(strecun-cdr f ibonacci-numbers))))

A prime number is a number, other than 1, that has only 1 and itself as

factors. Thus 2, 3, 5, 7, 11, and 13 are the first six primes. Eratosthenes, who
lived in the third century B.C., devised a clever way of finding all of the primes
up to some given number N. First, write a list of all of the integers from 2
up to N. Then, with 2 eis the base, remove all multiples of the bcise that are
greater than the base. Now take the first remaining number after the base (in

this case, 3) and call it the base. Once again, remove all multiples of the base
greater than the base. We continue this process, choosing as the new base the
first remaining number that follows the preceding base, and then removing all

multiples of the new base that are greater than the new base, until there are

15.3 Streams 491


Program 15.26 divides-by, sieve, prime-numbers

(define divides-by
(leunbda (n)
(lambda (k)
(zero? (remainder k ci)))))

(define sieve (compose stre>am-f ilter-out divides -by))

(define prime-numbers
(letrec
((primes
(lambda (s)
(stream-cons
(stream-car s)
(primes ((sieve (stream-car s)) (stream--cdr s)))))))
(primes (stream-cdr pos itive- integers)))) -

no more numbers to take as the base. The remaining numbers are the primes
less than or equal to N. This method is called the Sieve of Eratosthenes. It is

used below to find all of the primes up to 20. Each successive list is the result
of removing multiples of the next base.

(2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20)
(23579
(2357
11
11
13
13
15 17
17
19)
19)

The last list contains all of the primes up to 20.


Each step of the sieve process is an application of a filter to the stream of
remaining integers that removes those numbers that are multiples of the beise

and are greater than the base. Such a filter, which we call sieve, and the
stream prime-numbers are defined in Program 15.26.

In Program 15.27 we show another way of defining the stream of all prime
numbers that combines the ideas of Exercise 7.20 and the data recursion used
in Program 15.23. Instead of testing with all odd integers < t/n, we now can

restrict our testing to the odd prime numbers < -y/n. Assuming we have the

stream odd-primes, we first define a divisibility test has-prime-divisor?


which returns #t if its operand, an odd number, has a prime divisor. Then
the stream prime-numbers is defined by stream-consing 2 onto the stream
of odd primes. The stream odd-primes is defined by stream-consing 3 onto
the stream of all primes from 5 on, which is generated by the procedure odd-

492 Using Streams


)

Program 15.27 has-prime-divisor?, prime-numbers

(define has-prime-divisor?
(lajnbda (n)
(let ( (max-value (sqrt n) )

(letrec
((try (lambda (primes)
(and (<= (stream-car primes) max-value)
(or (zero? (remainder n (streeun-car primes)))
(try
(stream-cdr primes)))))))
(try odd-primes)))))

(define prime-numbers (stream-cons 2 odd-primes))

(define odd-primes (stream-cons 3 (odd-primes-builder 5)))

(define odd-primes-builder
(lambda (n)
(if (has-prime-divisor? n)
(odd-primes-builder (+ n 2))
(stream-cons n (odd-primes-builder (+ n 2))))))

primes-builder. The invocation (odd-primes-builder n) with n odd and


>= 5 generates the stream of those prime numbers > n.

Exercises

Exercise 15.5: integers-from, multiples-of squares-of -integers ,

Define the procedure integers-from such that (integers-from m) is the


stream of all integers beginning with the integer m and increasing. For exam-
ple, if 771 = 6, the stream will contain the integers 6, 7, 8, 9, . . .Then define the
procedure multiples-of such that (multiples-of A;) is a stream of integers
that starts with 0, whose elements are increasing and are multiples of the
positive integer k. Finally, define the stream of squares-of-integers whose
first few elements are 1, 4, 9, 16, 25, 36, . . .Test your streams by printing a
list of the first 20 elements of each stream using stream->list.

15.3 Streams 493


Exercise 15.6: all- integers
Write the definition of all-integers (including both the positive and neg-
ative integers and 0). Your stream does not have to contain the integers in
increasing order. For example, you can start with 0, then take 1, then -1,

then 2, and then -2, etc.

Exercise 15.7: stream-f ilter-in


Define a procedure stream-f ilter-in that takes as arguments a predicate
and a stream and returns a stream consisting of those elements of the original
stream for which the predicate is true. Test your program by filtering the
stream of positive integers using the predicate, which tests whether a number
is an odd multiple of 3. Use strezan->list to print the first 20 elements of

the resulting stream: (3 9 15 21 ... ).

Exercise 15.8: stream-ref


Write a procedure stream-ref that is the analogue of list-ref . Then use
Programs 15.26 and 15.27 prime-numbers along with your procedure
for the
stream-ref to find the hundredth, three-hundredth, and seven-hundredth
prime. Which of the two programs ran faster?

Exercise 15.9: stream-member?


Define a procedure stream-member? that takes three arguments: an item a,

a stream strm, and a nonnegative integer n, which is true if a is one of the


first n elements of the stream strm. If strm is a finite stream with length less

than n, it tests whether a is an element of strm.

Exercise 15.10: prime?


Write the definition of a procedure prime? that tests whether a given positive
integer is prime using has-prime-divisor? of Program 15.27. Test prime?
on numbers such as 37, 35, 51, 57, and 100000007.

Exercise 15.11: positive-rationals


The positive rational numbers, which are ratios of two positive integers a/6,
can be enumerated by them in order of increasing sums a + 6, with
listing

those numbers having the same sum listed in order of increasing numerator
a. Those fractions that are not in lowest terms are omitted from the enu-
meration. Thus the enumeration begins with 1/1, 1/2, 2/1, 1/3, 3/1,...
Define a stream positive-rationals that contains all of the positive ratio-
nal numbers with numerator and denominator having no common divisors

494 Using Streams


(1 1) (1 2) (1 3) (1 4) (15) ...
(2 1) (2 2) (2 3) (2 4) (2 5) ...
(3 1) (3 2) (3 3) (3 4) (3 5) ...
(4 1) (4 2) (4 3) (4 4) (4 5) ...
(1 5) (5 2) (5 3) (5 4) (5 5) ...

Table 15.28 Table with constant sum diagonals

greater than 1. Represent the rational number a/h as a pair (list a b) and
use the Scheme procedure gcd to test whether a/h is in lowest terms. Test
your program by listing the first 20 elements of the stream.

Exercise 15.12: stream-cdr


The procedure stream-cdr should have been implemented without the " >" —
tag. Modify the definitions of stream-cdr and force so that when a promise
is fulfilled, just its value is stored. (See Programs 14.4 and 15.5.) Compare this
version of the stream operators with those used earlier to evaluate the expres-
sion (begin (stream-ref positive-integers 20) positive-integers).

Exercise 15.13: diagonal


In Table 15.28, the ith diagonal (going up to the right) consists of all integer
pairs in which the two integers have sum i + 1; that is:

(i 1) (i - 1 2) (i - 2 3) . . . (2 2 - 1) (1 i)

Define a procedure diagonal that takes an integer, i, and returns a finite

stream containing as its first i elements the zth diagonal, followed by the-
null-stream. Test it with 4 and 5.

(finite-streain->list (diagonal 4)) =^ ((4 1) (3 2) (2 3) (1 4))


(finite-streain->list (diagonal 5)) => ((5 1) (4 2) (3 3) (2 4) (1 5))

Exercise 15.14: stream-append


Consider the incorrect code for stream-append given below:

(define stream-append
(lambda (finite-stream stream)
(cond
((stream-null? finite-stream) stream)
(else (stream-cons

15.3 Streams 495


(stream-car finite-stream)
(stream-append (stream-cdr finite -stream) stream))))))

Next consider int-pairs-generator, which uses stream- append:

(define int-pairs-generator
(lambda (i)
(stream- append (diagonal i) (int-pairs-generator (addl i)))))

Find out what happens when (int-pairs-generator 1) is evaluated. Ex-


plain.

Exercise 15.15
The problem with stream-append mentioned in the previous exercise can
be corrected by treating stream-append as a syntactic extension with the
following syntax table entry:

(stream-append finite-stream stream)

(stream-append/delay finite-stream (delay stream))

Declare stream-append and define stream-append/delay. Then complete


the following experiment:

[1] (define int-pairs (int-pairs-generator 1))


[2] (define f irst-300-int-pairs (stream->list int-pairs 300))
[3] first-300-int-pairs
7

15.4 Using Character Data

In Section 15.5, we look at an application of streams to input and output.


That will make use of a data type known cis characters . These are the letters,

numbers, and other symbols on the computer keyboard, as well as certain


control characters such as newline and space. In this section, we see how this

data type is handled in Scheme.


Since information is stored in the computer in the form of binary numbers,
each letter in the alphabet is assigned an integer number. An example of
such a system that is used in many computers ASCII character set,
is the
which assigns numbers to 128 symbols that can be entered on the computer

496 Using Streams


keyboard. The ASCII codes are given in Table A 1.1 Appendix A. The char-
in

acters on the computer keyboard are represented in Scheme by the character


data type, and each character is entered with #\ preceding it. For example,
the character representation of the letter "A" is #\A. There is a Scheme pro-
cedure cheir->integer that takes a character as itsargument and returns an
integer representation of that character. In this book, we assume that the
integer representation of a character is the ASCII code for that character.

For example:

(chcur->integer #\A) ^^ 65
(char-> integer «\B) =» 66
(cheur->integer #\a) =» 97
(char->integer «\b) ^ 98
(cheir->integer #\0) ^^ 48
(char-> integer «\1) ^ 49

Scheme also has the procedure integer->ch2Lr, which is the inverse of char-
>integer; that is, if n is any number between and 127, inclusive, then
(integer->chsu: n) returns the character corresponding to n, and we again
use the ASCII code to determine that character. For example,

(integer->char 65) =* «\A

Special Scheme characters are used to denote some of the control characters
on the computer keyboard. For example, a blank space, which corresponds to
pressing the space bar on the computer keyboard, is denoted by the character
#\space, and a newline (or line feed) is denoted by the character tXnewline.
Some implementations of Scheme also contain the character #\retum, which
produces the control character corresponding to pressing the return or enter
key on the computer keyboard.
There are also a number of predicates that are used to test the order of two
characters by comparing the order of their ASCII codes: ch2a'=?, cliar<?,
ch.ar>?, ch.eoc<-?, and char>=?. We have:

(char<? «\C «\F) => «t


(char<? «\B #\A) =*• «f
(char<? «\A «\a) => «t
(ch2u:=? «\A (integer->char 65)) ^ #t

Sometimes it is desirable to ignore the case of a letter and consider upper-


and lowercases of a given letter as the same. Then we would treat #\A and

15.4 Using Character Data 497


#\a as if they were the same. In order to do this, there are case-insensitive

predicates corresponding to the ones listed above: ch2a— ci=?, chcur-cK?,


chau:-ci<=?, char-ci>?. and chear-ci>=?. For example, (cheo— ci=? #\A
#\a) has the value true. Two other procedures relevant to the case of charac-
ters are char-upcase and char-downcase. Both take a character as argument
and return another character. The first leaves all characters unchanged ex-
cept that it returns an uppercase character when its argument is a lowercase
alphabetic character; the second returns a lowercase character when its ar-

gument is an uppercase alphabetic character. Along with these are the two
predicates char-upper-case? and char-lower-case? which test the case of

a letter:

(char-upcase #\a) ==*> #\A


(char-downcase #\Z) ^^ #\z
(char-upper-case? #\A) =^ #t

There are some string procedures that also make use of characters. For
example, string->list is a procedure that takes a string and returns a list

of characters that make up the string. Thus,

(string->list "Have fun.")


=> (#\H «\a #\v t\e «\space #\f f\u #\n «\.)

To go in the opposite direction, we have the procedure list->string, which


collects together the items in a list of characters and produces a string.

Exercises

Exercise 15.16: string->list


We can think about a string as a vector composed of characters, but each
vector operation has become a string operation. For example, for vector ihere
is string, for maJce-vector there is make-string, for vector-ref there is

string-ref , for vector-set ! there vector-length


is string-set !
, and for

there is string-length. Using only string-rel and string-length from


this set of string-processing operations, write string->list, which takes a
string (of characters) and returns a list (of characters).

Exercise 15.17: list->string


Using the discussion of the previous exercise, write list->string. Here is a
start:

498 Using Streams


(define list->string
(lambda (list-of-characters)
(let (den (length list-of-characters)))
(let ((result-string (make-string len)))
...))))

Test your solution with the following examples:

[1] (define string-tester


(lambda (strng)
(let ((chars (string->list strng)))
(let ((s (list->string chars)))
(write (list s chars))
(nesline)))))
[2] (for-each string-tester ' ("abc" " " "uv xyz" ""))

Exercise 15.18: string


Define string, which, like list and vector, takes an arbitrary number of
arguments. In the case of string, all must be characters.

Exercise 15.19: string-append


Define string-append, which takes two strings and returns a string (see Ex-
ercise 9.6). string-append is the analog of append using strings instead of
lists. Define string-append using only list->string, string->list, and
append.

Exercise 15.20: Icjer


Define a procedure lower that takes a string and returns a new string where
all upperccise characters become lowercase. Hint: Use map and string->list.

Exercise 15.21: lower!


Define a procedure lower! that takes a string and side effects it so that all
uppercase characters in the string are replaced by lowercase characters.

Exercise 15.22: flipflop


Define a procedure flipflop that takes a string and returns a new string
where all uppercase characters become lowercase and all lowercase characters
become uppercase.

15.4 Using Character Data 499


Exercise 15.23: hash-function
In Section 12.6, a naive-hash-f unction was used as a local procedure to
assign an integer to a string. It assigned the ASCII code of the first charac-
ter in the string, modulo some fixed number (26 was used in naive-hash-

fiuiction). This method has the disadvantage that some letters are used
more frequently than others to start words, so the buckets would not be filled
uniformly. A better hash function is one that uses the sum of theASCII
codes of all the characters in the string modulo some fixed number m. This
will tend to distribute the words more evenly through the m buckets. Define
the procedure hash-function, which has as its parameter an integer m and
returns another procedure, which, when passed a string, returns the sum of
the ASCII codes of the first n characters in the string modulo m (that is, the
remainder when the sum is divided by m). First, define hash-function so that
it is case sensitive, and then redefine it with the name hash-f unction-ci so
that it is case insensitive and treats all letters as if they were lowercase. Test
your procedures on:

((hash-function 26) "Hello") =^ 6


((hash-function 26) "hello") =^ 12
((hash-f unction-ci 26) "Hello") =J> 12
((hash-f unction-ci 26) "hello") =^ 12

15.5 Files

In this section, we and writing to files. We shall


discuss reading from also
develop an application of streams when we develop a formatter that reads text
from one file, reformats it, and writes it to another file. This will necessitate
the reading and writing of characters.
You have probably been using files in your work to store the text of programs
that you write in the editor and to store the output of your Scheme programs
by using a transcript facility or by saving the contents of a window. It is also
possible to have Scheme programs read directly from a file or write directly
to a file. In all cases, the material entered using a read expression comes from
an input port, and the material printed using a display or write expression
is sent to an output port. In general, a port is associated with an input or
output device. In our programs so far, the input port for the read expressions
has been associated with the computer's keyboard, known as the standard
input. Similarly, the output port for our display and write expressions has
been associated with the computer's video display, known as the standard
output. These are the default values if no other port is specified for the input

500 Using Streams


or output.
It is also possible to make the input port be associated with a file from
which we want to read items, or to make the output port be associated with
a file we want to write items. To associate an input port with a file,
to which
we use the Scheme procedure open- input-file, which takes as an argument
a string that contains the name of an existing file or the path to the file, and
returns a port associated with that file. For example, the expression

(open- input -file "inputl.dat")

returns an input port associated with the file named "inputl.dat". The
port returned is capable of delivering characters from the file "inputl.dat".
The procedure read takes a port as an optional argument, and if that argu-
ment is present, it reads from that port. For example, suppose that the file

"inputl.dat" contains the following:

This is "a test string."


((1 2) (3 4))

Then we assign the port that we defined above to the variable port-in and
see how read successively reads each item:

[1] (define port-in (open-input-file "inputl.dat"))


[2] (read port-in)
This
[3] (read port-in)
is
[4] (read port -in)
"a test string."
[5] (read port-in)
((1 2) (3 4))
[6] (read port-in)
some implementation- dependent end-of-file message
[7] (close- input -port port-in)

When the reading of the file is finished, we invoke close-input -port, which
has the effect of closing the input port so that no further operations can be
performed on it. Since many computer operating systems limit the number of
ports that can be opened at the same time, it is good practice to close ports

when they are no longer needed.


When the end of the file is reached, a special end-of-file object is encoun-
tered and is generally not treated as data. Thus Scheme provides a predicate

15.5 Files 501


eof -object? that tests whether the item read is the end-of-file object. For
example, if the file "input2.dat" contains

100
150
200
250

then the following program

(let ((p (open-input-file "input2,dat")))


(letrec
((add- it ems
(lambda (sum)
(let ((item (read p)))
(cond
((eof -object? item)
(close-input-port p)
sum)
(else (add-items (+ item sum))))))))
(add-items 0)))

returns the sum of the numbers in the file "input2.dat", namely 700.
In addition to the procedure read, which reads the next item. Scheme
provides the procedure read-cheur, which reads the next character. Scheme
writes that character using the #\-notation for characters. For example, the
character A is written as #\A. If read-cheo: is called with no argument, it

reads the next character from standard-input. If it has one argument, that
argument must be a port, and it reads the next character from that port.
Thus if a file "input3.dat" contains

Testing 12 3

then the following program

(let ((p (open-input-file "input3.dat")))


(letrec
((reader (lambda (ch)
(if (eof-object? ch)
'()

(cons ch (reader (read-chair p)))))) )

(let ((eins (reader (read-char p) )))


(close-input-port p)
ans)))

502 Using Streams


^ )

returns the list

(#\T #\e #\s #\t #\i #\n #\g #\space #\1 #\space #\2 #\space #\3)

To write directly to a file, we must first associate an output port with that
file. This is done with the procedure open- output -file, which takes as its

argument a string that identifies the file to which the output should be sent.

Thus, the expression

open- output -f il e " output dat '

( . '

returns a port associated with the file "output.dat". The port returned
is capable of writing characters to the file "output.dat". If the file does
not already exist, it creates the file "output.dat". If the file does exist,

the behavior depends upon the particular implementation of Scheme you are
using.
We have used write and display as procedures of one argument. They
printed their argument on standard- output, which has been the computer's
video display. Both of these procedures accept a port as an optional second
argument. When a port is present as its second argument, the procedure
sends a printed representation of its first argument to that port. Similarly,
newline can take a port as an optional argument. Here is an example:

(let ((port-out (open-output-file "output.dat")))


(display "This is an output test." port-out)
(newline port-out)
(close-output-port port-out))

sends the sentence

This is an output test.

to the file "output .dat". As was the case with input ports, it is good practice
to close the output port when one is finished. This is done with the proce-
dure close-output-port, which takes a port as its argument. Had we used
write instead of display, the sentence would have been printed in the file

surrounded by double quotes.

' For example, PC-Scheme and MacScheme delete the file and create a new one with the
same name, so that the previous contents of the file are destroyed. Some implementations
of Scheme may signal an error if one tries to open an output file that already exists.

15.5 Files 503


Program 15.29 file-copier

(define file-copier
(lambda (infile outfile)
(let ((p-in (open- input -file infile))
(p-out (open-output-file outfile)))
(letrec ((copier (leunbda (ch)
(if (not (eof-object? ch))
(begin
(write-char ch p-out)
(copier (read-chau: p-in)))))))
(copier (read-char p-in))
(close-input-port p-in)
(close-output-port p-out)))))

It is also possible to write individual characters to a file using the Scheme


procedure write- char, which takes a character as its first argument and takes
an output port as its optional second argument. If the second argument is not
present, it sends the character to standard- output; otherwise it sends it to the
port identified by the second argument. Program 15.29 copies the contents
of the file identified by the string infile character by character into the file

identified by the string outfile.


We close this section with an example of a program that reads the text
stored in one file, reformats it to have some given line length, and prints it

to another file. We are demonstrating this formatter to indicate how to treat


input and output as streams, so we are making no eflfort to have it handle all

possible grammatical constructions. It is a simplified formatter that illustrates


the ideas we want to convey. We think of the input from the input file as a
stream of characters that we process. We can define this stream by writing
the procedure file->streajn given in Program 15.30.

When f ile->stream is invoked, the first character is read from the input
file and made the first element of the stream. No other characters are read
until they are needed. We shall describe when reading happens in this process

after we define stream->f ile. As long as the input port is open, characters
are read from that port and the stream is built. When the object denoting
the end of the file is encountered, the input port is closed and the the-null-
stream is installed.
Our strategy is first to remove all of the newlines and returns in the stream
of characters, for these are where the original line breaks were. We insert a
space wherever we remove a newline or return, for otherwise there would be

504 Using Streams


)

Program 15.30 f ile->streain

(define f ile->streain
(lambda (filensune)
(let ((port-in (open- input -file f ileneune)))

(letrec
( (build- input-stream
(lambda ()

(let ((ch (read-char port -in)))


(if (eof-object? ch)
(begin
(close- input -port port-in)
the-null-stream)
(stream-cons ch (build-input-stresun) ))))))
(build-input-stream) ) ) )

no space separating the last word on one line from the first word on the next
line. Insome cases, we may have put more than one space between words, so
we next eliminate all excess spaces; that is, whenever there are more spaces
than one between words, the extra ones are removed. Next we insert double
spaces at the end of each sentence. Finally, we count the characters and
insert a newline character so as to give us a line length not exceeding the
desired amount given by line-length and then write the resulting stream to
the desired output file. These operations are performed so that the output
of one is the input of the next one. The procedure formatter applies these
operations one after the other. The three arguments to formatter are a string
giving the name of the input file, a string giving the name of the output file,

and the desired line-length of the output. We first look at the definition of
formatter in Program 15.31 and then proceed to the definitions of each of
the operations that formatter composes.
The procedure that removes the newlines from the input stream is given in
Program 15.32.
If there is more than one space between words, the excess is removed by

the procedure remove-extra-spaces given in Program 15.33. This procedure


uses the helping procedure trim-spaces, which removes all spaces from the
beginning of the stream passed to it until the first character diff"erent from a
space is encountered. (See Program 15.34.)
The procedure insert-double-spaces in Program 15.35 is used to guar-
antee that each sentence-ending punctuation is followed by double spaces.
After we have inserted the two spaces we use trim-spaces, defined in Pro-

15.5 Files 505


Program 15.31 formatter

(define formatter
(lambda (input-file output-file line-length)
(stream->f ile output-file
( insert -newlines line-length
(insert -double-spaces
(remove-extra-spaces
(remove-newlines
(f ile->stream input-file))))))))

Program 15.32 remove-newlines

(define remove-newlines
(leunbda (str)
(stream-map
(lambda (ch)
(case ch
((#\return #\newline) #\space)
(else ch)))
str)))

Program 15.33 remove-extra-spaces

(define remove-eztra-spaces
(lambda (str)
(cond
((stream-null? str) str)
((char=? (stresun-car str) #\space)
(stream-cons #\space
(remove-extra-spaces
(trim-spaces (stream-cdr str)))))
(else (stream-cons
(stream-car str)
(remove-extra-spaces (stream-cdr str)))))))

506 Using Streams


-

Program 15.34 trim-spaces

(define trim-spaces
(lambda (str)
(cond
((stream-null? str) str)
((char=? (stream-car str) #\space)
(trim-spaces (stream-cdr str)))
(else str))))

Program 15.35 insert-double-spaces

(define insert-double -spaces


(lambda (str)
(cond
((streeun-null? str) str)
((end-of -sentence? (stream-car str))
(stream-cons ( stream-car str)
(stream-cons #\space
(stream-cons #\space
( insert double-spaces
(trim- spaces (strejun- cdr str)))))))
(else (stream-cons (stream-car str)
(insert -double-spaces (stream--cdr str)))))))

Program 15.36 end- of -sentence?

(define end-of-sentence?
(leunbda (ch)
(or (char=? ch «\.) (char=? ch «\ ! ) (char=? ch #\?))))

gram 15.34, to remove any that might be left. Although at most one space
needs to be inserted because we know that for this problem, there is exactly
one space following a sentence terminator, it is better to make the procedure
do what its and be more independent of its input. The
specification dictates
helping procedure end-of-sentence? in Program 15.36 merely tests whether
its argument is a period, an exclamation mark, or a question mark.

15.5 Files 507


)

Program 15.37 insert-newlines

(define insert -newlines


(lambda (line-length str)
(letrec
((insert (lambda (str count)
(if (stre€un-n\ill? str)
str
(let ((n (coiint-chars-to-nert-space str)))
(if (and (< count line-length)
(<= (+ n count) line-1 ength)
(stream-cons (stream-car str)
(insert (stream-cdr str) (addl count)))
(stream-cons SXnewline
(insert (trim-spaces str) 0))))))))
(insert i
'trim-spaces str) 0))))

Program 15.38 count -chars -to-next -space

(define count-chars-to-next-space
(lambda (strm)
(letrec
( (count-ahead
(lambda (str count)
(cond
((stream-null? str) count)
((char=? (stream-car str) #\space) coiint)
(else (count-ahead (stream-cdr str) (addl count)))))))
(count-ahead stm 0))))

The last step before writing the reformatted stream to the output file is

to reintroduce line breaks. The procedure insert-newlines given in Pro-


gram 15.37 does this. Whenever a newline is inserted, we must remove any
remaining spaces. This is accomplished by using trim-spaces.
Whenever a new word is encountered, the above procedure has to know
how many characters it contains in order to know whether it fits on the same
line or whether it should be the first word on the next line. This counting
of the number of characters in the next word is done by the help procedure
count-chars-to-next-space, defined in Program 15.38.

508 Using Streams


Program 15.39 streain->f ile

(define streani->f ile


(l2uiibda (filename stream)
(let ((port-out (open-output-file filename)))
(letrec ( (write-stream
(lambda (str)
(if (not (stream-null? str))
(begin
(write-char (stream-car str) port-out)
(write-stream (stream-cdr str)))))))
(write-stream stream)
(close-output-port port-out)))))

The reformatted stream is printed to the output file by the procedure


streain->f ile, defined in Program 15.39.
When processing small files, it does not matter whether we use streams or
lists to handle the data. However, if the file is enormous, the advantages of
streams are as follows. Although it appears as though each processing proce-
dure such as remove-extra-spaces or insert-newlines is completed over
the entire data before the next procedure is invoked, in reality, the demand for

the stream-car in stream->f ile starts the process. That demand is prop-
agated through insert-newlines, insert-double-spaces, remove-extra-
spaces, remove-newlines all theway to f ile->stream. Then the procedure
f ile->stream responds to the demand by actually reading the next charac-
ter.That character is sent back to remove-newlines, which decides if it has
enough information to send the next character to remove-extra-spaces, etc.
Thus the demand is propagated down the procedures, and values are propa-
gated up through the procedures. We see that only the minimal information
is used at any one time.
This extended example illustrates the use of input and output to files and
the use of streams in reading from and writing to a file and processing the
information stored in the stream. There are many ways in which this formatter
can be modified to take into account textual features that it now ignores. For
example, the quotation mark that ends a sentence is usually after the period,
and this program will insert spaces after the period. It also does not preserve
blank lines that separate paragraphs or paragraph indentation. It makes an
interesting exercise to add some of these features to the formatter.

15.5 Files 509


Exercises

Exercise 15.24
A file contains a column of integers, one per line. Write a procedure that reads
the integers in this file and produces another file that contains two columns:
the first column containing the integers in the original file and the second
containing the running sum of the integers in the first column. The number
of integers in the original file is not specified. Place an appropriate header at
the top of each column.

Exercise 15.25
A file contains a column of integers, one per line. Write a procedure that reads
the integers in this file and produces two additional files that contain two
columns: the first column of both files contains the integers in the original file

and the second contains the running sum and running product, respectively,
of the integers in the first column. The number of integers in the original file

is not specified. Place an appropriate header at the top of each column.

Exercise 15.26
Write a program that will count the number of words in a file containing text.
You may make reasonable eissumptions about the nature of the text.

The next six problems are related. Work them in order and you will discover

a more elegant way of writing formatter.

Exercise 15.27
Test formatter developed in this section.

Exercise 15.28
Test the procedure formatter defined below. In order to do this, you will

need to curry insert-newlines and stream->f ile.

(define fomatter
(lanbda (input-file output-file line-length)
((8treaim->file output-file)
((insert-newlines line-length)
(insert-double-spaces
(remove-extra-spaces
(renove-newlines
(file->8tream input-file))))))))

510 Using Streams


)

Exercise 15.29
Test the procedure formatter defined below. In order to do this, you will
need to pass output-file and line-length first and then pass input-file
to that result.

(define formatter
(leunbda (output-file line-length)
(lambda (input -file)
( (stream->f ile output-file)

(( insert -newlines line-length)


(insert -double-spaces
(remove-extra-spaces
(remove-newlines
(file->stream input-file)))))))))

Exercise 15.30: apply-procedures, compose


Consider the definition of apply-procedures below.

(define apply-procedures
(lambda (procedures)
(if (null? procedvires)
(lambda (x) x)
(compose
(car procedures)
(apply-procedures (cdr procedures))))))

Test it on ((apply-procedures (list addl addl addl addl)) 3). Next,


define compose to take an unrestricted number of single-argument procedures
so that (compose pi ...pk) applied to argximent is the same as ((apply-
procedures (list pi ...pk)) argument). Test compose with ((compose
addl addl addl addl) 3).

Exercise 15.31
Test the procedure formatter defined below, which uses compose from the
previous exercise.

(define formatter
(lambda (output-file line-length)
(compose
(stream->f ile output-file)
( insert -newlines line-length)

insert-double-spaces
remove-extra-spaces
remove-newlines
f ile->stream) )

15.5 Files 511


Exercise 15.32
In the definition of formatter from the previous exercise, we see that we
merely peiss any desired procedures as arguments to compose, and the invoca-
tions are taken care of automatically. Now we will consider different variations
on the arguments to compose. Test the following:
a. (compose
(streaiii->f ile output-file)
f ile->streain)

b. (compose
(stream->f ile output-file)
remove-newlines
f ile->stre2uii)

C. (compose
(stream->f ile output-file)
remove-extra-spaces
remove-newlines
f ile->stream)

d. (compose
(stream->f ile output-file)
(insert-newlines line-length)
remove-ertra-spaces
remove-newlines
f ile->stream)

e. (compose
(stream->f ile output-file)
(insert-newlines line-length)
remove-newlines
f ile->stream)

512 Using Streams


Part 5

Control

When we think about the dining-out procedure discussed in the introduction


to Part 1, we can begin to understand the power of abstracting control. Imag-
ine that there is a genie photographing us while we dine. Here is a photo of
us just about to order. Do you see the waiter standing by our table? Now,
here is one of us polishing off dessert. The genie saves these photographs.
When a meal has been particularly good and we long to go back to that little

cafe in Paris whose name we have long since forgotten, there is one way we
can relive the experience. We may ask the genie to rub a magic liquid on a
photograph. When that happens, we escape to the same cafe where we were
long ago. We will have the same waiter and perhaps order the same food.
Whether the waiter aged or not, or whether we are heavier, will depend on
whether changes have occurred. If not, then we are the same. If so, then some
aspects may be the same, like the cafe, but other aspects may have changed.
Perhaps the genie rubbed the wrong photograph, and instead of rubbing the
photograph to get us to the cafe, he rubbed the photograph of us paying the
waiter. What a shame, thrust back to that delicious cafe and not reliving the
meal. What happens after the meal is over? You have two choices. You can
stay in Paris and enjoy the night life, as you did long ago, or you can ask the
genie to rub another photograph. Each time one is rubbed, you are escaping
to another point in your past but with possible changes.

A computer is like a genie. While computing, it takes a snapshot of where


you are in the computation. However, rather than keep every photograph
around, it keeps only the ones that you tell it are worth saving. The pho-
tographs correspond to what are called escape procedures, and invoking an
escape procedure corresponds to rubbing the photograph. The point of Part
5 is to show you how to reason with the power of escape procedures.

514 Control

16 Introduction to Continuations

16.1 Overview

Did you ever lie in bed early in the morning and think about what you were
going to do that day? Your thinking probably led to something like this: "I've

got to shower, then brush my teeth, eat breakfast, find my way to campus, and
get to my first class. After I get to my first class, I'll think about what I have
left to do for the rest of the day." You packaged the rest of the day into a single
concept, relative to some point in the morning. You did not consciously figure
out what you would do with the rest of the day; you formed an abstraction
of the rest of the day. This notion can carry over to computations as well.

In Scheme the rest of the computation relative to some point in an evaluation


can also be packaged in the same way that we packaged the rest of the day in
our real-world experiences. The rest of a computation is a continuation. This
chapter is an introduction to the use of continuations in Scheme. It shows
what they are, how they work, and when to use them.
When we learn to deal with continuations, we shall be able to do all sorts
of interesting things. For example, we shall be able to exit with a result from
within a deep recursion. In addition, we shall be able to design break packages
and coroutines, new concepts introduced in this and the next chapter.
In order to understand continuations, two new concepts contexts and es-

cape procedures — must be acquired. The first concept formalizes the creation
of a procedure with respect to a subexpression of an expression. The second
characterizes a procedure that upon invocation does not return to the point
of its invocation. A continuation is a context that has been made into an
escape procedure. Such continuations are created by invocations of call-
with-current-continuation.
We have already encountered an escape procedure, error. When error gets
invoked, its context, a procedure that represents the rest of the computation,
is abandoned. Consider the very simple expression:

(cons (if (zero? divisor)


(error "/:" dividend "divided by zero")
(/ dividend divisor))
'(a b c))

The result of invoking this expression is either an invocation of error or a list

of length four, whose first element is a number. If error were a conventional


procedure, then when we would do the cons and get a list of
it returned,
length four, whose first element would not likely be a number. But we know
that that is not what happens, so error is not a conventional procedure. We
describe how to construct such escape procedures in Section 16.3, but for now
we observe that if error gets invoked, no consing occurs. In the next section
we develop contexts, procedures that describe what does not happen when
such escape procedures get invoked.

16.2 Contexts

A context is a procedure of one variable, Q. We use the symbol D, pronounced


"hole," to distinguish contexts from other procedures. If e is a subexpression
of E, then we use the terminology that "the procedure c is a context of e in
£." In the absence of side effects, the procedure c applied to the value of e is
the value of E.
Consider the following expression that evaluates to 47:

(+ 3 (* 4 (+ 5 6)))

The expression is evaluated using the following scheme. First, add 5 and 6
and get 11. Next multiply 11 by 4, yielding 44, and then increase that result
by 3. Now, what is the context of (+ 5 6) in that expression? We must find
a procedure that, if passed the value 11, will produce 47. There are lots of
such procedures, but we will find one by using a simple two-step technique.
In the first step we replace e, that is, (+ 5 6), by D. In the second step, we
form a procedure from the value of the result of the first step wrapped within
(lambda (D) . . . ). The context of (+ 5 6) in

(+ 3 (* 4 (+ 5 6)))

516 Introduction to Continuations


is the procedure, which is the value of:

(lambda (D)
(+ 3 (* 4 D)))

Then applying this context to 11 results in 47.

Let's look at another example. What is the context of (* 3 4) in

( (+ (* 3 4) 5) 2)

To form this context, we simply replace ( 3 4) by D and then wrap what


remains by (lambda (D) . .
. ) leading to the procedure, which is the value
of:

(lambda (D)
(* (+ D 5) 2))

Applying this context to 12 results in (* (+ 12 5) 2), which evaluates to


34. But we can apply it to other values. Applying it to 3 yields 16. What
does applying it to 24 yield?
Let us next extend the mechanism for creating contexts. The second step
remains the same, but the first step does more. Before, all we did in the

first step was replace a subexpression by D. Now we extend the first step
by evaluating the expression with the hole. When evaluation can no longer
proceed because of the hole, we have finished the Thus contexts are first step.

procedures created at the point in the computation where we can no longer


compute because of the existence of D The previous examples were correct .

because no evaluation was possible. To demonstrate this way to form contexts,


consider the slightly more complicated expression:

(if (zero? 5)
(+ 3 (* 4 (+ 5 6)))
( (+ (* 3 4) 5) 2))

In finding the context of (* 3 4), the result of the first step is what is left

after evaluating

(if (zero? 5)
(+ 3 ( 4 (+ 5 6)))
(* (+ D 5) 2))

16.2 Contexts 517


(zero? 5) is false, sowe choose the alternative of the if expression, which
leads to ( (+ D 5) 2). No more computation can take place. Thus, the
procedure formed as a result of the second step is the value of

(lambda (D)
(* (+ D 5) 2))

Consider the context of (* 3 4) in:

(let ((n D)
(if (zero? n)
(writeln (+ 3 (* 4 (+ 5 6))))
(writeln (* (+ (* 3 4) 5) 2)))
n)

The result of the first step is:

(begin
(writeln ( (+ D 5) 2))
n)

The begin is needed because it is a sequence of expressions. We cannot do


the addition because of the hole. We cannot do the multiplication because
we cannot do the we cannot do the displaying because we cannot
addition,
do the multiplication, and we cannot return the value of n because we cannot
determine the value of the expression that precedes it. In figuring out the
value of expressions, we work from the inside and try to work outward. The
procedure formed as the result of the second step is responsible for remem-
bering the value of the free variable n. Thus we observe that contexts are
procedures and must respect free variables. We do not need to worry about
the let and we do not need to worry about the if expression. Eval-
expression,
uation proceeds until the presence of D makes it impossible to continue and
then we do the second step that forms the context, which is the value of:

(lambda (D)
(begin
(writeln (* (+ D 5) 2))
n))

Applying it to 6 leads to (begin (writeln (* (+ 6 5) 2)) n), and with n


bound to 1 the value displayed is 22 with the result 1. Applying it to 8, 26 is
displayed.

518 Introduction to Continuations


)

The let expression is just a procedure invocation. We can reformulate the


last example with a global procedure:

(define tester
(lambda (n)
(if (zero? n)
(writeln (+ 3 (* 4 (+ 5 6) ))

(writeln (* (+ ( 3 4) 5) 2)))
n))

Then we can determine the context of (* 3 4) in the expression (tester 1).


Although (* 3 4) does not appear physically within (tester 1), we know
that the computation will eventually get to that point, so the same context
will be formed. If we were looking for the context within the expression (*

10 (tester D), then the context would be formed from the value of:

(lambda (D)
( 10 (begin
(writeln (* (+ D 5) 2))
n)))

Let us apply these rules to a begin expression:

(begin
(writeln 0)
(let ((n D)
(if (zero? n)
(writeln (+ 3 (* 4 (+ 5 6))))
(writeln ( (+ (* 3 4) 5) 2)))
n))

We are still forming the context of (* 3 4). At the first step, (* 3 4) is

replaced by D just prior to evaluation:

(begin
(writeln 0)
(let ((n D)
(if (zero? n)
(writeln (+ 3 (* 4 (+ 5 6))))
(writeln (* (+ D 5) 2)))
n))

16.2 Contexts 519


)

First, a is displayed. Then the context is determined as the procedure,


which is the value of:

(lambda (D)
(begin
(writeln (* (+ D 5) 2))
n))

Invoking it with 9 causes the displaying of 28 and then returns 1.

A context might involve the use of set ! . The example below is similar to
the last one, except that within the scope of the let expression is an assignment
to the local variable n. The context of (* 3 4) in

(begin
(writeln 0)
(let ((n D)
(if (zero? n)
(writeln (+ 3 (* 4 (+ 5 6) ) )

(writeln (* (+ (* 3 4) 5) 2)))
(set ! n (+ n 2))
n))

is the value of

(lambda (D)
(begin
(writeln (* (+ D 5) 2))
(set ! n (+ n 2))
n))

The free variable n, initially 1, is taken from the let expression. Each time
the context is invoked, the variable n is incremented to the next positive odd
integer, and what gets subsequently returned is also increased. If <c> is this

context, then the first invocation of <c> assigns 3 to n, and the second invo-
cation assigns 5 to n. From the way in which n changes upon each invocation
of <c>, it follows that contexts are procedures that may even maintain state.
In the next example, we look at the terminating condition of a recursive

procedure invocation. Consider the definition of the procedure map-addl,


which adds one to each element of a list, but instead of returning the empty
list, it returns (23) as the result of the terminating condition:

520 Introduction to Continuations


(define map-addl
(lambda (Is)
(if (null? Is)
(cons (+3 (* 4 5)) '())
(let ((val (addl (car Is))))
(cons val (map-addl (cdr Is)))))))

For example, (map-addl '


(1 3 5)) is (2 4 6 23). What is the context
of (* 4 5) in (cons (map-addl '(1 3 5)))? This is the same as "run
this until the existence of D stops the computation, and what is left is the
context." We compute the expression looking for D:

(cons (map-addl '(1 3 5))) =»


(cons (cons 2 (map-addl (cdr '(1 3 5))))) ^^
(cons (cons 2 (map-addl '(3 5)))) =>
(cons (cons 2 (cons 4 (map-addl '(5))))) ^^
(cons (cons 2 (cons 4 (cons 6 (map-addl '()))))) ^^
(cons (cons 2 (cons 4 (cons 6 (cons (+ 3 Q) '())))))

Because of the hole, no additional computation can be performed, so the


context is the procedure formed from

(lambda (D)
(cons (cons 2 (cons 4 (cons 6 (cons (+ 3 D) '()))))))

If we invoke this context on 5, we create the list (02468), and if we


invoke it on 13, we get (0246 16). What makes this a bit unusual is the
fact that the hole does not show up in the expression right away, and in this
case, it shows up just as the termination condition is considered.
we cannot initially find a place to insert D. However,
In the next example,
we know that D we can compute until it occurs and eventually
will occur, so

stops the computation. Consider the simple procedure sum+n, which adds n
to the sum of the numbers from 1 to n:

(define siim+n
(lambda (n)
(if (zero? n)

(+ (addl n) (sum+n (subl n))))))

What is the context of (addl n), just when n is 3, in (* 10 (sum+n 5))?


As in the previous example, we are looking for a context associated with

16.S Contexts 521


a recursive procedure invocation. However, this differs from the previous
example by the additional detail used in its description. Stepping through
the computation leads eventually to an occurrence of D:^

(* 10 (sum+n 5)) =»
(* 10 (if (zero? 5) (+ (addl 5) (siiin+n (subl 5))))) =^
(* 10 (+ 6 (sum+n 4))) ^*
(* 10 (+ 6 (if (zero? 4) (+ (addl 4) (sum+n (subl 4)))))) =>
(* 10 (+ 6 (+ 5 (sum+n 3)))) =>
(* 10 (+ 6 (+ 5 (if (zero 3) (+ D (sum+n (subl 3))))))) =*
(* 10 (+ 6 (+ 5 (+ D (sum+n 2)))))

Thus, the context is the procedure formed from:

(lambda (D)
(* 10 (+ 6 (+ 5 (+ D (sum+n 2))))))

The final example uses the predicate of an if expression. Consider the


context of (* 3 4) in (if (zero? (* 3 4)) 8 9). First, determining the
expression prior to evaluation results in (if (zero? D) 8 9). There is no
evaluation possible, so the context is the value of

(lambda (D)
(if (zero? D) 8 9)).

When this context is applied, its value will be 8 or 9, depending on what value
gets bound to D.
In order to understand continuations, you will need to have lots of experi-
ence forming contexts. The exercises below should give you enough practice.

Exercises

Exercise 16.1
What is the context of (cons 3 '())in(cons 1 (cons 2 (cons 3 '())))?
What results when we apply this context to '(a b c), '(x y), and '
(3)?

^ The trace that follows assumes a left to right order of evaluation of the operands to +.
The procedure map-addl imposed a left to right order of evaluation of the operands to cons
by using a let expression.

522 Introduction to Continuations


Exercise 16.2
For the following exercises assume these bindings: a is 1, b is 2, c is 3, d is 4,

n is 5, X is 6, y is 7, and z is 8. Each answer will be in two parts. In the first

part, describe the context of each expression; in the second part, determine
the resultant values found by sequentially applying the context to each of 5,

6, and 7.

a. (+ a b) in ( c (+ a b)).

b. x in (+ X y).

c. y in (- X y).

d. X in (let ((a 4)) (+ a x)).

e. (* c (+ a b)) in (+ d (* c (+ a b))).

f. (zero? n) in (if (zero? n) a b).

g. X in (if X y z).

h. a in (let ((x 3)) (set! x (+ a x)) x).

Exercise 16.3
For each expression below, determine the context of (cons 3 ' (4)) and the
result of applying that context to (1 2 3).

a. (letrec ((f (lambda (n)


(if (zero? n)
(car (cons 3 (4))) '

(* n (f (subl n)))))))
(f 3))

b. (letrec ((f (lambda (n)


(if (zero? n)
(car (cons 3 '(4)))
(* n (f (subl n)))))))
(+ 1000 (f 3)))

16.3 Escape Procedures

We now introduce a new procedure type, called escape procedures. An escape


procedure upon invocation yields a value but never passes that value to others.
When an escape procedure is invoked, its result is the result of the entire
computation. Anything awaiting the result is ignored. Let us assume the
existence of a procedure, escape-*, which is an escape multiply:

16.3 Escape Procedures 523


(+ (escape-* 5 2) 3)

This expression evaluates to 10. The waiting + is abandoned. It is as if (* 5


2) were the entire expression.
At this point we do not have a mechanism for creating escape procedures

such as escape-*. Let us further assume there is a procedure escaper that


takes any procedure as an argument and returns a similarly defined escape
procedure. Then with escaper we can define escape-*

(define escape-* (escaper *))

and

(+ ((escaper *) 5 2) 3)

evaluates to 10.
Consider the invocation:

(+ ((escaper
(lambda (x)
(- (* I 3) 7)))
5)
4)

Here the addition cannot happen, so this is the same as

((lambda (x)
(- (* X 3) 7))
5)

so the answer is 8. Consider the following expression with an escape subtrac-


tion procedure:

(+ ((escaper
(lambda (x)
((escaper -) (* x 3) 7)))
5)
4)

This is also 8, because once (escaper -) is invoked, the result is determined,


and + is abandoned. But consider what happens with the following escape
multiplication procedure:

524 Introduction to Continuations


(+ ((escaper
(lambda (x)
((escaper -) ((escaper *) x 3)
7)))
5)
4)

The invocation of (escaper *) results in 15. The (escaper -) is never


invoked, so the subtraction never occurs. The following four expressions have
the same value. Why?
1. ((lambda (x)
(* X 3))
5)

2. (+ ((escaper
(lambda (x)
(- ((escaper *) x 3)
7)))
5)
4)

3. (+ ((lambda (x)
((escaper -) ((escaper *) x 3)
7))
5)
17)

4. (+ ((lambda (x)
(- ((esraper ) x 3)
7))
5)
2000)

Does this fully characterize the behavior of escape procedures? Not quite.
Consider the following:

(/ (+ ((escaper
(lambda (x)
(- ( X 3) 7)))
5)
4)
2)

The awaiting addition is abandoned. Is the division, which awaits the addi-
tion, also abandoned? Yes. Since the division awaits the addition and since
the addition hcis been abandoned by the escape invocation, the division has

16.3 Escape Procedures 525


4

also been abandoned. This behavior can be characterized by an equation:


if e is an escape procedure and / is any procedure, then (compose / e) =
e. That is, (/ (e expr)) is the same as (e expr) for all expressions expr.
The context of (e expr) in (/ (e expr)) is(lambda (D) (/ D)), which
is the same as f. Since the result of (/ (e expr) ) is the result of (e expr),
we say that an escape invocation abandons its context. In our last example,
the context of the escape invocation included the awaiting addition and the
awaiting division. We discuss special escape procedures in the next section
where we characterize call-with-current-continuation.

Exercises

Exercise 16.
Evaluate each of the following:

a. ((escaper addl) ((escaper subl) 0))

b. (let ((es-cons (escaper cons)))


(es-cons 1 (es-cons 2 (es-cons 3 '()))))

Exercise 16.5
Using the definition of es-cons from the previous exercise, determine the con-
text of (es-cons 3 '()) in (es-cons 1 (es-cons 2 (es-cons 3 '()))).

Exercise 16.6: reset


Consider the definition of reset:

(define reset
(leuDbda ()
( (escaper
(lambda ()
(writeln "reset invoked"))))))

Determine the value of (cons 1 (reset)).

Exercise 16.7
Let e be an escape procedure, and let / and g be any procedures. To what
is (compose g (compose / e)) equivalent? Can this be generalized to an
arbitrary number of procedure compositions?

Exercise 16.8
Let / be any procedure. When can / be replaced by (escaper /) and still

produce the same value as /?

526 Introduction to Continuations


16.4 Continuations from Contexts and Escape Procedures

We are about to discuss call-with-current-continuation (or call/cc). If


call/cc is not available on your Scheme, define it as follows:

Program 16.1 call/cc

(define call/cc call-Hith-current-continuation)

call/cc is a procedure of one argument; we call the argument a receiver.


The receiver is a procedure of one argument. Its argument is called a con-
tinuation. The continuation is also a procedure of one argument. Regardless
of how we form the continuation, (call/cc receiver) is the same as (re-
ceiver continuation). What is left is to understand how continuation is
formed. To form continuation, we first form the context, c, of (call/cc re-
ceiver) in some expression E. We then invoke (escaper c), which forms
continuation. We have now completely characterized call/cc. All we have
left to do is see how our understanding of how to form continuations leads us

to determine correctly the evaluation of expressions using call/cc.


Consider the following expression:

(+ 3 ( 4 (call/cc r)))

The context of (call/cc r) is the procedure, which is the value of

(lambda (D) (+ 3 ( 4 D)))

so our original expression means the same cis:

(+ 3 (* 4 (r (escaper (lambda (D) (+ 3 ( 4 D)))))))

That is, after the system forms the context of (call/cc r) the system passes ,

it as an escape procedure to r. Since this is now just a simple invocation, all


the rules for procedure invocation apply. A little practice is helpful. Let us
consider r to be the value of (lambda (continuation) 6). What is the value
of the expression derived from the call/cc expression above?

(+ 3 (* 4 ((lambda (continuation) 6)
(escaper (lambda (D) (+ 3 ( 4 D)))))))

16. 4 Continuations from Contexts and Escape Procedures 527


The value of

((lambda (continuation) 6)
(escaper (lambda (D) (+ 3 ( 4 D)))))

is 6; it does not use continuation, so the result is 27 (i.e., 3 + 4*6). What


about this one?

(+ 3 (* 4 ((lEunbda (continuation) (continuation 6))


(escaper (lambda (D) (+ 3 (* 4 D) ))))))

The explicit invocation of continuation on 6 leads to

((escaper (lambda (D) (+3 (* 4 D)))) 6)

and then the result is 27. Is this one any different?

(+ 3 (* 4 ((lambda (continuation) (+ 2 (continuation 6)))


(escaper (lambda (D) (+ 3 (* 4 D)))))))

The explicit invocation of continuation on 6 leads to

((escaper (lambda (D) (+3 (* 4 D)))) 6)

and then the result is 27. Remember, an escape invocation abandons its
context, so (lambda (D) (+3 (*4 (+2 n))))is abandoned, contin-
uation has the value (escaper (lambda (D) ... D ...)). Because the
context of a call/cc invocation is turned into an escape procedure, we use
the notation <ep> for procedures that get passed to r.

Scheme supports procedures as values, and since <ep> is a procedure, it is


possible to invoke thesame continuation more than once. In the next section
there are three experiments with call/cc, and in the last experiment the
same continuation is invoked twice. The countdown example of Chapter 17
shows what happens when the same continuation is invoked many times.

528 Introduction to Continuations


Exercises

Exercise 16.9
For each expression below, there are four parts. In Part a, determine the
expression's value. In Part b, define r locally using let, and form the original
application of (call/cc r), which leads to this expression. In Part c, define
r globally, and in Part d, using the global r, form the original application of
(call/cc r), which leads to this expression. The solution to problem [1] is

given below:

[1] (- 3 (* 5 ((lambda (continuation) (continuation 5))


(escaper (lambda (D) (- 3 (* 5 D) ))))))

a. -22

b. (let ( (r (lambda (continuation)


(continuation 5))))
(- 3 (* 5 (call/cc r))))

c. (define r
(lambda (continuation)
(continuation 5)))

d. (- 3 (* 6 (call/cc r)))

[2] (-3 (* 5 ((lambda (continuation) 5)


(escaper (lambda (D) (- 3 (* 5 D)))))))

[3] (-3 (* 5 ((lambda (continuation) (+ 1000 (continuation 5)))


(escaper (lambda (D) (- 3 (* 5 D)))))))

Exercise 16.10
If r is

(lambda (continuation) (continuation botij/))

in (. . . (call/cc r) . . . ), why can r be rewritten as

(leunbda (continuation) body)

16. 4 Continuations from Contexts and Escape Procedures 5S9


Exercise 16.11
Ifr is

(escaper (lambda (continuation) (continuation body)))

in (. . . (call/cc r) . . . ), when can r be rewritten as

(lambda (continuation) body)

16.5 Experimenting with call/cc

We next consider three simple experiments. Each experiment includes one use
of a receiver (remember that a receiver is just a single-parameter procedure)
without using call/cc and one that uses call/cc. The point of these experi-
ments is to show the simple behavioral characteristics of call/cc expressions.
Although the differences may seem minor in the first two experiments, their
differences are important. In the last experiment, however, the differences
demonstrate the unusual behavior of continuations. The receivers we use to
demonstrate these properties are presented in Program 16.2.

Program 16.2 receiver-1, receiver-2, receiver-3

(define receiver-1
(lambda (proc)
(proc (list 1))))

(define receiver-2
(lambda (proc)
(proc (list (proc (list 2))))))

(define receiver-3
(lambda (proc)
(proc (list (proc (list 3 proc))))))

Each receiver consumes a procedure (possibly a continuation) that is in-

voked at least once. In receiver-3, not only is the procedure invoked at


least once, but it is also used as an argument. We consider the behavior of
each of these receivers using two global variables, result and resultcc, given

530 Introduction to Continuations


Program 16.3 result, resultcc

(define result "any value")

(define resultcc "any value")

Program 16.4 writeln/return, answer-meJcer, call

(define writeln/retum
(lambda (x)
(writeln x)
x))

(define einswer-maker
(Icuubda (x)
(cons 'answer-is (writeln/return x))))

(define call
(Izunbda (receiver)
(receiver writeln/return)))

in Program 16.3, and three simple procedures, writeln/return, answer-


meJicer, and call, given in Program 16.4. The procedure writeln/return

displays and returns its argument. The procedure answer-maker is like


writeln/return, but instead of returning its argument, it returns the consing
of aoiswer-is to its argument. Thus, (receiver-1 answer-meiker) displays
(1) and returns (eoiswer-is 1). The procedure call invokes its argument
on writeln/return.
For reasons that are not yet clear but will be by the end of this section, we
use set! to hold the results of each experiment. Recall that receiver-1 is

the value of

(leunbda (proc)
(proc (list 1)))

16.5 Experimenting with call/cc 531


Experiment 1:

A.

[1] (set! result (ansser-meiker (call receiver-1)))


(1)
(1)
[2] result
(ansHer-is 1)

B.

[3] (set! resultcc (answer-maker (call/cc receiver-l) ))


(1)
[4] resultcc
(answer-is 1)

These results are identical except that in Part A writeln/return is invoked


in call so there is an additional (1). The continuation formed in Part B is

the value of:

(escaper
(lambda (D)
(set! resultcc (answer-meiker D))))

Then this continuation is invoked on (list 1), and since it is an escape pro-
cedure, that is all that happens. The procedure einswer-meiker is invoked on
(list 1), causing (1) to appear, and its result, (eoiswer-is 1), is assigned
to resultcc. At [4] we verify that resultcc is indeed (einsHer-is 1).
For Experiment 2, recall that receiver-2 is the value of:

(lambda (proc)
(proc (list (proc (list 2)))))

Experiment 2:

A.

[1] (set! result (answer-mziker (call receiver-2)))


(2)
((2))
((2))
[2] result
(answer-is (2))

532 Introduction to Continuations


. .

B.

[3] (set! resultcc (answer-maker (call/cc receiver-2)))


(2)
[4] resultcc
(answer-is 2)

In Part A the main difference is the extra set of parentheses around the value,
which is the result passed to answer-maker. Both invocations of proc do
a writeln/return. The first time is with (2) as its argument. When this
returns, its argument is passed to list, resulting in ( (2) ). Now we are ready
for the second invocation of writeln/return. It displays argument ((2))
its

and returnsit to answer-maker, which displays its argument by invoking


writeln/return and returns the result (answer-is (2)). This is the value
assigned to result. In Part B, why is there just one displaying of (2), and
where did the extra set of parentheses go? Recall that the continuation built
from the context of (call/cc receiver-2) is an escape procedure. Thus,
once invoked, it abandons its context, the value of

(lambda (D)
(set! resultcc (answer-maker (proc (list D)))))

The list invocation and the proc invocation waiting for the result of list are
abandoned. The list invocation not occurring accounts for the missing set
of parentheses, and the proc invocation not occurring accounts for why only
one (2) is displayed. In Part B when proc, the continuation, is invoked, its
argument is passed to the waiting answer-msJcer. The value (2) is displayed,
and the result (answer-is 2) is sent to the waiting set!. The set! causes
the value (answer-is 2) to be associated with resultcc. The result of the
experiment is verified at [4]

We have come to our last experiment. This one is slightly trickier than the
earlier ones. Because of we discuss all of Part
this, A before we look at Part
B. We recall that receiver-3 is the value of

(lambda (proc)
(proc (list (proc (list 3 proc)))))

Experiment 3:^

' To denote the procedure that is the value of the veu'iable procedure-name, we use the
notation <procedure-naTiie>

16.5 Experimenting with call/cc 533


[1] (set! result (answer-maker (call receiver-3)))
(3 <wriieln/return>)
((3 <writeln/retuTn>))
((3 <writeln/return>))
[2] result
(answer-is (3 <writeln/return>))
[3] ((2nd (2nd result)) (list 1000))
(1000)
(1000)
[4] result
(answer-is (3 <wriieln/return>))

The result of (call receiver-3) to be passed to answer-maker is

(.<writeln/return>
(list i<writ€ln/return>
(list 3 <writeln/return>))))

First, the list (3 <writeln/return>) is passed to <writeln/return>. It duti-


fully displays its argument. Then a set of parentheses wrapped around it,
is

and that result, ((3 <writeln/return>)) , is displayed and passed to einswer-


maker. The procedure answer-maker displays that list and passes (aoiswer-
is (3 <writeln/return>)) to the waiting set!. The set! does the appro-
priate assignment. At [2] the experiment is verified. At [3] the procedure
<wrvteln/return> is extracted using (2nd (2nd result)). That procedure
is As expected <writeln/return> displays its ar-
then invoked on (1000).
gument (1000) and returns (1000). At [4] nothing has changed result.
Although this is a contrived experiment, only simple procedures are used to
do simple things. We are now ready to consider Part B.

B.

[5] (set! resultcc (answer-maker (call/cc receiver-3)))


(3 <ep»
[6] resultcc
(answer-is 3 <ep>)
[7] ((3rd resultcc) (list 1000))
(1000)
[8] resultcc
(answer-is 1000)

The result of (call/cc receiver-3) to be passed to answer-meJcer is

534 Introduction to Continuations


(<ep>
(list (<ep>
(list 3 <ep>))))

where <ep> is the continuation, which is the value of

(escaper
(lambda (D)
(set! resultcc (answer-maker D))))

but since <ep> is invoked, the outer list, <ep>, and answer-maker invo-
cations are abandoned, as well as the set! expression. Therefore, the result
of (call/cc receiver-3) is the result of invoking (<ep> (list 3 <ep>)).
The escape procedure <ep> is invoked giving the value (3 <ep>) as the value
that is passed to eoiswer-maker, which displays the list (3 <ep>). Next
answer-is is consed to the front of (3 <ep>), which yields (answer-is 3
<ep>). Then is done, which changes the value of resultcc.
the set! At
[6] , we what was
verify that expected has indeed occurred. We are about to
execute the code at [7]. The expression (3rd resultcc) yields the escape
procedure <ep> that was saved earlier. It is passed the list (1000). What is
(.<ep> (list 1000))? Recall that <ep> is an escape procedure that passes
its argument to answer-maker and then assigns to resultcc the result of

the am.swer-maker invocation. The procedure aLnswer-maker displays its ar-


gument and then returns (answer-is 1000). The list (answer-is 1000) is
for the waiting set! and so the set! happens again. This time resultcc
(answer-is 1000), and the role of the escape procedure has
gets the value
ended. Was resultcc really changed? How do we find out? At [8] we check ,

the value of resultcc. This time it has been changed to (answer-is 1000)!
Although the set ! was done back at [5] , the escape procedure <ep> included
doing everything again once it was invoked.

Exercises

Exercise 16.12
Rewrite aoiswer-maier using call.

Exercise 16.13
Run the experiment with exer-receiver.

(define exer-receiver
(lambda (proc)
(list (proc (list 'exer proc)))))

16.5 Experimenting with call/cc 535


4

Exercise 16.1
For each expression below, describe the binding that continuation gets, and
give the value(s) of the expression. Each expression must be tested more than
once. We include the solution for Part a.

a. (let ((r (lambda (continuation)


(continuation 6))))
( (+ (call/cc r) 3) 8))

The value of (escaper (lambda (D) (* (+ D 3) 8))), 72.

b. (let ((r (leUBbda (continuation)


(+ 1000 (continuation 6)))))
( (+ (call/cc r) 3) 8))

c. (let ((r (launbda (continuation)


(+ 1000 6))))
( (+ (call/cc r) 3) 8))

d. (let ((r (leunbda (continuation)


(if (zero? (random 2))
(+ 1000 6)
(continuation 6)))))
( (+ (call/cc r) 3) 8))

e. (let ((r (lambda (continuation)


(if (zero? (random 2))
(+ 1000 6)
(continuation 6)))))
(+ (* (+ (call/cc r) 3) 8)
(* (+ (call/cc r) 3) 8)))

f. (let ((r (leunbda (continuation)


(continuation
(if (zero? (continuation (random 2)))
(+ 1000 6)
6)))))
(+ (* (+ (call/cc r) 3) 8)
(* (+ (call/cc r) 3) 8)))

Exercise 16.15
Determine the outcome of Experiment 3 with [1] and [5] replaced by the
expressions below.

[1] (begin
(set! result (cuiswer-meJcer (call receiver-3)))
'done)

536 Introduction to Continuations


[5] (begin
(set! resultcc (answer-maker (call/cc receiver-3) ))
'done)

Exercise 16.16
We define a procedure map-subl that takes a list of numbers and returns a
list with each element of the list decremented by one. In addition to doing
the work of map-subl, it also sets the global variable deep to a continuation.

(define deep "any continuation")

(define map-subl
(leunbda (Is)
(if (null? Is)
(let ((receiver (lambda (k)
(set! deep k)
'())))
(call/cc receiver))
(cons (subl (ccir Is)) (map-subl (cdr Is))))))

Consider the following experiment:

[1] (cons 1000 (map-subl '()))


(1000)
[2] (cons 2000 (deep '(a b c)))

[3] (cons 1000 (map-subl '(0)))


(1000 -1)
[4] (cons 2000 (deep '(a b c)))
7
[5] (cons 1000 (map-subl '(1 0)))
(1000 -1)
[6] (cons 2000 (deep '(a b c)))
7

[7] (cons 1000 (map-subl '(543210)))


(1000 4 3 2 10-1)
[8] (cons 2000 (deep '(a b c)))
7

After each invocation of map-subl, deep is reset. The first continuation


formed at [1] is:

16.5 Experimenting with call/cc 537


(escaper
(lambda (D)
(cons 1000 D)))

At [3] , the next continuation formed is:

(escaper
(lambda (Q)
(cons 1000 (cons -1 D))))

The third continuation formed at [6] is:

(escaper
(lambda (Q)
(cons 1000 (cons (cons -1 D)))))

At [7] a fourth continuation


, is formed and bound to deep. Write an expres-
sion that characterizes that continuation, and then fill in the four blanks of

the experiment.

16.6 Defining escaper

We now have all the tools we need to define escaper:

Program 16.5 escaper

(define *escape/thunk* "any continuation")

(define escaper
(leUibda (proc)
(lambda (x)
(escape/thunk* (lambda () (proc x))))))

Although escape/thunk* is defined as a global variable, it does not yet have


the right value. To remedy this, one more experiment must be performed. For
this experiment, a receiver is used to assign a value to escape/thunk*.

538 Introduction to Continuations


Program 16.6 receiver-4

(define receiver-4
(Icunbda (continuation)
(set! *escape/thunk* continuation)
(*escape/thimk* (launbda () (writeln "escaper is defined")))))

We then have:

[1] ((call/cc receiver-4))


escaper is defined
[2] (*escape/thunk* (lambda (addl 6)))
7
[3] (+ 5 (escape/thunk* (lambda () (addl 6))))
7

At [1], the continuation (escaper (launbda (D) (n)))is formed by the


system. It becomes the value of continuation and, in turn, the value of es-
cape/thunk*, indirectly changing the definition of escaper in Program 16.5.

This escape procedure takes as its argument a procedure of zero arguments


and immediately invokes it. Next escape/thunk* is passed the procedure

(lambda () (writeln "escaper is defined"))

This escapes while binding D to

(launbda (writeln "escaper is defined"))

Finally,

(dcimbda () (writeln "escaper is defined")))

displays escaper is defined. At [2] , invoking escape/thunk* on


(lambda (addl 6))

yields 7; at [3] , invoking it on


(lambda () (addl 6))

once again yields 7. Because *escape/thunk* is an escape procedure, the


context

(lambda (D) (+ 5 D))

16.6 Defining escaper 539


is abandoned. Earlier we hypothesized escaper's existence in order to ex-
plain the continuations formed from invocations of call/cc. Now we have
defined escaper using call/cc, which is in Scheme. The procedure call/cc
is not built with escaper, as we suggested earlier, but it behaves as though
it were. On some systems, it may be necessary to determine the value of
escape/thuiLk* at the prompt by invoking ((call/cc receiver-4)).
Using *escape/thuiik* we can redefine escaper so that it accepts proce-
dures of any number of arguments:

Program 16.7 escaper

(define escaper
(lambda (proc)
(lambda args
(escape/thunk*
(lambda ()

(apply proc args))))))

This definition of escaper can be used to test all the results and exercises of
this chapter.

Exercises

Exercise 16.17
Assume the existence of escaper and then define *escape/thunk* with es-
caper. You may not use call/cc.

Exercise 16.18
Determine the value of (/ 5 (*escape/thunk* (lambda () 0))).

Exercise 16.19: reset


Use call/cc to define a zero-argument procedure reset that upon invocation
abandons its context and causes the string "reset invoked" to be displayed.
In Chapter 7, when we defined error, we assumed the existence of reset.
For example,

[1] (cons 1 (reset))


reset invoked

540 Introduction to Continuations


Exercise 16.20
Explain why (*escape/thuiik* *escape/thunk*) causes an error.

Exercise 16.21
Determine the value of the following expressions:

[1] (let ( (r (escaper


(lambda (proc)
(cons 'c (proc (cons 'd '())))))))
(cons 'a (cons 'b (call/cc r))))

[2] (let ( (r (escaper


(lambda (proc)
(cons 'c (cons 'd '()))))))
(cons 'a (cons 'b (call/cc r))))

Exercise 16.22
Consider the procedure new-escaper below.

(define new-escaper "smy procediire")


(let ((receiver (lambda (continuation)
(set ! new-escaper
(lambda (proc)
(lambda args
(continuation
(lambda ()

(apply proc args))))))


(lambda () (writeln "new-escaper is defined")))))
((call/cc receiver))) displays new-escaper is defined

Are new-escaper and escaper the same? Why is new-escaper better than
escaper?

16.7 Escaping from Infinite Loops

Suppose we would like to some code into control and action. To be


separate
a bit more specific, consider a piece of program that we want to run forever:

(let ((r (random n)))


(if (= r tsLTget)
(begin (writeln count) (set! count 0))
(set! count (+ count 1))))

16.7 Escaping from Infinite Loops 5^1


Program 16.8 how-many-till

(define hos-many-till
(lambda (n teirget)
(let ((count 0))
(cycle-proc
(lambda ()

(let ((r (remdom n)))


(if (= r target)
(begin (writeln count) (set! count 0))
(set! count (+ count 1)))))))))

Then using cycle-proc (see Program 14.11), which runs a zero-argument


procedure forever, we can write Program 16.8. The procedure how-many-till
continuously reports how many values are unequal to the target. If the number
displayed is always the same, then we ought to question the randomness of the
random number generator. Each time it displays a count, the counter is reset.
The only way to stop this program is by some kind of keyboard interrupt
mechanism. However, we can build into how-many-till an exit facility using
call/cc. Instead of looping indefinitely, we exit whenever the sum of the
counts is greater than some threshold. We need an additional local variable
that maintains the sum. We invoke the procedure how-many-till with the
threshold as an additional argument. This version of how-many-till is given
in Program 16.9. If exit-above-threshold is ever invoked, then we come
out of the invocation of (how-many-till n target thresh); otherwise we
stay within cycle-proc. What is interesting about this example is that it is

possible to exit an infinite loop without changing the definition of cycle-proc.

An example of the use of how-many-till is given in Program 16.10, where


we can invoke (random-data 10 20).
The first continuation formed (by the call/cc in how-many-till) is the
value of

542 Introduction to Continuations


Program 16.9 how-meuiy-till

(define hos-many-till
(lambda (n target thresh)
(let ((receiver
(lambda (exit-above-threshold)
(let ((count 0) (svim 0))

(cycle-proc
(lambda ()

(if (= (rajidom n) teurget)


(begin
(writeln "target " tsirget
" required " count " trials")
(set! sum (+ siim count))
(set ! count 0)
(if (> sum thresh)
(exit-above-threshold sum)))
(set! count (+ count 1)))))))))
(call/cc receiver))))

Program 16.10 random- data

(define random-data
(lambda (n thresh)
(letrec ((loop (leunbda (target;
(cond
((negative? target) '())
(else (cons (how-m£uiy-till n teurget thresh)
(loop (subl target))))))))
1

(loop (subl n)))))

(escaper
(lambda (Q)
(cons D (loop (subl target)))))

where loop is as it is defined in random-data and target is 9.

16.7 Escaping from Infinite Loops 543


+

Exercise

Exercise 16.23
Explain why the test for termination within rsuidoni-datais (negative? tar-
get).

16.8 Escaping from Flat Recursions

The call/cc operator gives the ability to escape from recursive computations
while basically throwing out all the work that has stacked up. A simple exam-
ple clarifies in what sense the mechanism avoids doing pending computations.
We look at the problem of taking the product of a list of numbers and adding

the number n to the product if the result is nonzero:

(product+ 5 '(3 6 2 7)) => (+ 5 252) => 257


(product* 7 '(2 3 8)) =^

Here is the solution in a functional style:

Prograini 16.11 product

(define product+
(lanbda (n nums)
(letrec
((product (laabda (nu»s)
(cond
((null? nuMs) 1)
(else ( [* icdir nuns) (product (cdr nu»8))))))))
(let ((prod (product nuns)))
(if (zero? prod) (+ n prod))))))

This solution can be improved by adding a test to determine if one of the


values in the list is zero. This stops the recursion upon encountering the first

zero. This version is in Program 16.12. Consider the following subtle fact:

Finding a zero in the list does not stop the computation of product. In fact,

what happens is that if the first zero is in the kth position, then there are
k — 1 multiplications using zero. This is because the context of the product

544 Introduction to Continuations


+

Program 16.12 product

(define product*
(lambda (n nvuns)
(letrec
((product (lambda (nums)
(cond
((null? nums) 1)
((zero? (car nvuns)) 0)
(else (* (car nums) (product (cdr nums))))))))
(let ((prod (product nums)))
(if (zero? prod) (+ n prod))))))

invocations includes k — 1 multiplications. When the zero is found, each of


the k — I waiting multiplications must still be done.
Is it possible to exit the invocation of product so that the result causes no
waiting multiplications to occur? A solution is in Program 16.13. Consider
the invocation (+ 100 (product* 10 '
(2 3 4 6 7))). Since the list of
numbers contains a 0, the continuation, which is the value of

(escaper
(lambda (D)
(+ 100 D)))

is invoked, and the result is 100. This follows because the continuation is

being invoked on 0. If, however, no zero is found, then (product nums)


terminates normally, and (+ n prod) is returned as the value of (receiver
<ep>). Since prod cannot be zero, the result returned is (+ n prod). The
let expression can be shortened to (+ n (product nums)). This version is in
Program 16.14.

We see that finding a zero in the list produces a value to pass to the contin-
uation formed from the invocation of (call/cc receiver) and finishes the
computation of product*. Moreover, we observe the rather surprising fact
that if there is a zero in the list, then no multiplications occur regardless of
where in the list that zero occurs.

16.8 Escaping from Flat Recursions 545


Progrson 16.13 product*

(define product*
(lanbda (n nuns)
(let ((receiver
(lambda (exit-on-zero)
(letrec
((product (lambda (nums)
(cond
((null? nums) 1)
((zero? (ceir nums)) (exit-on-zero 0))
(else (* (car nums)
(product (cdr nums))))))))
(let ((prod (product nums)))
(if (zero? prod) (+ n prod)))))))
(call/cc receiver))))

Program 16.14 product*

(define product*
(lambda (n nums)
(let ((receiver
(lambda (exit-on- zero)
(letrec
((product (lambda (nums)
(cond
( (null? niims) 1)
((zero? (car lums)) (exit-on-zero 0))
]

(else (4 (car nums)


(product (cdr nums))))))))
(* n (product nums))))))
(call/cc receiver))))

16.9 Escaping from Deep Recursions

Let us take a look at a slightly more complicated example. The problem is to


redefine product* for a larger class of lists. Specifically, we allow deep lists

of numbers. Thus we can invoke

(product* 5 '((1 2) (1 1 (3 1 D) (((((1 1 0) 1) 4) 1) 1)))

546 Introduction to Continuations


Program 16.15 product+

(define product*
(lanbda (n nims)
(let ((receiver
(la«bda (exit-on-zero)
(letrec
((product
(leunbda (nuns)
(cond
((null? nuBs) 1)
((number? (car nums))
(cond
((zero? (car nuns)) (exit-on-zero 0))
(else (* (cen: nuns)
(product (cdr nu»s))))))
(else (* (product (car nums))
(product (cdr nums))))))))
(+ n (product nums))))))
(call/cc receiver))))

Program 16.16 -emd-count-maJcer

(define *-and-count-m2Jcer
(lambda
(let ((local-counter 0))
(lambda (nl n2)
(set! local-counter (+ local-counter 1))
(writeln "Number of multiplications « " local-counter)
(* nl n2)))))

which results in 0. However, if the had been a 3, then the result would have
been 77. The new definition of product + is given in Program 16.15.

Some, but not all, multiplications are avoidable. By counting the number of
multiplications, we can discover how many can be avoided. This can be done
by invoking a special multiplication procedure *-and-count-maJter, given in

Program 16.16, and then passing the result of its invocation as an argument
to product+. The procedure product+ in a functional style would have once
again introduced all those multiplications by zero. (See Program 16.17.) Thus
we can invoke:

16.9 Escaping from Deep Recursions 547


Program 16.17 prod.uct+

(define product+
(leunbda (n nums *-proc)
(letrec
( (product
(lambda (nums)
(cond
((null? nums) 1)

((number? (car nums))


(cond
((zero? (car nums)) 0)
(else (*-proc (ceu: nums) (product (cdr nums))))))
(else
(let ((val (product (car nums))))
(cond
((zero? val) 0)
(else (*-proc val (product (cdr nums)))))))))))
(let ((prod (product nums)))
(if (zero? prod) (+ n prod))))))

(let ((counter (*-and-coiint-maker))


(num-list '((1 2) (1 1 (3 1 1)) (((((1 1 0) 1) 4) 1) 1))))
(product+ 5 niim-list coiinter))

When product+ of Program 16.17 is used on the given tree, there are 12
multiplications, and when product+ of Program 16.15 is used there are fewer
than 12 multiplications. There is, of course, a way to avoid all multiplications,
but it involves walking through the entire list looking for O's before starting the
multiplication process. This makes the algorithm two-pass (it would require
two pcisses through the list).

Exercises

Exercise 16.24
Run product + of Program 16.14 with the *-proc argument over a list of
numbers to verify the claim that no multiplications occur if the list contains a
0. Run product + of Program 16.12 with the *-proc argument over the same
list to compare with the first part of this exercise.

548 Introduction to Continuations


Exercise 16.25
Run product+ of Program 16.15 with the *-proc argument over the nested
list of numbers given above. Run product + of Program 16.17 with the *-proc

argument over the same list to compare with the first part of this exercise.

Exercise 16.26
Rewrite product + of Program 16.15 where n is always 0.

Exercise 16.27
Rewrite product+ of Programs 16.14 and 16.15 using a local variable to main-
tain the accumulating product. Can this be done without using call/cc?

16.9 Escaping from Deep Recursions 549


17 Using Continuations

17.1 Overview

In this chapter we discover some unusual properties of continuations. We


demonstrate how to build a break facility. This allows computations to halt
and then restart an indefinite number of times. Each time the computation
halts, the user will be able to interact with the system. In addition, we show
how to build a coroutine system. In such systems, multiple procedures can
interact with each other without actually returning control from within each
process. Before we begin this development, we review the fundannental rules
concerning call/cc.

17.2 Review of call/cc

1. call/cc's argument is called a receiver.

2. A receiver's argument is called a continuation. It is an escape procedure


<ep> of one argument formed from the context of the call/cc invocation.
S.jA continuation's argument is passed to the context from which <ep> was
formed by invoking <ep> on that value.
4. If the escape procedure <ep> is formed from the call/cc invocation and is

then ignored, the following hold, where the use of ellipses surrounding an
expression indicates that the expression may be embedded:
. )

(let ((receiver (lambda (continuation) body)))


. . . (call/cc receiver) . . .

(let ((receiver (lambda (continuation) body)))


... (receiver 'anything) ...)

. . . body . .

and

(let ((receiver (escaper (lambda (continuation) body))))


. . . (call/cc receiver) . . . )

(let ((receiver (escaper (lambda (continuation) body))))


... (receiver 'anything) ...)

= (receiver 'anything)
= body

where the next to the last equality holds since receiver is an escape proce-
dure, and the last equality holds since continuation is ignored.

5. In all circumstances the following hold:

(let ((receiver (lambda (continuation) (continuation botfj/))))


... (call/cc receiver) ...)

(let ((receiver (lambda (continuation) body)))


. . . (call/cc receiver) . . .

and

(let ((receiver (escaper (lambda (continuation) (continuation 6o(ij^)))))


. . . (call/cc receiver) . . . )

(let ((receiver (leimbda (continuation) body)))


. . . (call/cc receiver) . . . )

552 Using Continuations


Program 17.1 countdown

(define countdown
(launbda (n)
(writeln "This only appears once")
(let ((pair (message "Exit" (attempt (message "Enter" n)))))
(let ((v (1st pair))
(returner (2nd pair)))
(writeln " The non-negative-number: " v)
(if (positive? v)
(returner (list (subl v) returner))
(writeln "Blastoff"))))))

17.3 Making Loops with One Continuation

In the previous chapter we introduced continuations. We noted that continua-


tions were escape procedures and could be the value returned by any procedure
or could be stored in data structures; however, our examples (except for the
third experiment and escaper) ignored that feature. Each example shared the
property that once a receiver was exited, the continuation was useless. Each
receiver's continuation was always invoked; it was never passed as an argument
or considered as the value of any procedure invocation. This property led us
to refer to the continuations with such names as exit-above-threshold and
exit-on-zero, because each was invoked only once for each invocation of its

associated receiver. Now we abandon this property so that a continuation

survives beyond giving a value to its associated receiver's invocation.


we used a continuation to exit deep recursions with the various defi-
Earlier
nitions of product +. However, we have not yet developed an interesting use of
a continuation, other than escape/thunk*, that can be returned as a value
and stored in a data structure. To illustrate such a continuation, we define a
procedure countdown that counts a positive integer down until it reaches zero.
This is a very simple loop. We use two different definitions of the auxiliary
procedure attempt. The first does not create any continuations and does not
perform a loop. The second does create a single continuation and with this
continuation is able to perform a loop. The definition of countdown uses a
trivial displaying procedure message for tracking the flow of the computation.
The definitions are given in Programs 17.1, 17.2, and 17.3.

The value of proc is just the identity procedure we denote as <proc>. Here
is what appears when (countdown 3) is invoked:

17.3 Making Loops with One Continuation 553


Progr£un 17.2 message

(define message
(lambda (direction value)
(writeln " " direction "ing attempt with value: " value)
value))

Program 17.3 attempt

(define attempt
(lambda (n)
(let ((receiver (lambda (proc) (list n proc ))))
(receiver (lambda (x) x)))))

This only appears once


Entering attempt vith value: 3
Exiting attempt vith value: (3 <proc>)
The non-negative-number: 3
(2 <proc»

"This only appeairs once" appeeirs once. The next event is an attempt
to find the value of the expression (message "Enter" 3). This produces
the message, "Entering attempt vith value: 3" and message returns its

second argument, 3. So now we attempt to find the value of the invocation


(attempt 3). This invocation yields the list (3 <proc>) because once the
list (list n proc) is constructed, attempt is exited. Next we attempt to
find the value of the expression (message "Exit" (3 <proc>)). Once again
the message is displayed, but this time it is an exiting message, "Exiting
attempt with value: (3 <proc>).^^ The invocation's value is (3 <proc>).
Now we bind pair to this list, and bind
take the pair apart, bind v to 3,

returner to <proc>. We display a message that acknowledges where we are


and that we do indeed have the correct value. The message is, "The non-
negative number: 3." We then check to see if the number is positive. In
this case it is. (returner (list (subl v) returner)). We form
We invoke
the list (2 <proc>) and hand this list to <proc>, which returns (2 <proc>).
With the definition of attempt in Program 17.3, we did not create a loop nor
did the result end with Blast oil.
We now redefine attempt (see Program 17.4) to create a continuation <ep>
that we return in place of <proc>. In the discussion that follows, we explain

554 Using Continuations


Program 17.4 attempt

(define attempt
(lambda (n)
(let ((receiver (lambda (proc) (list n proc))))
(call/cc receiver))))

how that continuation is powerful enough to build a looping construct.


The result of (countdown 3) using attempt of Program 17.4 follows:

This only appears once


Entering attempt with value: 3
Exiting attempt with value: (3 <ep>)
The non-negative-nvimber : 3

Exiting attempt with value: (2 <ep>)


The non-negative-number: 2
Exiting attempt with value: (1 <ep>)
The non-negative-number: 1
Exiting attempt with value: (0 <ep>)
The non-negative-number:
Blastoff

"This only appears once" appears once. The next event is an attempt
to find the value of the expression (message "Enter" 3). This produces
the message, "Entering attempt with value: 3" and message returns its

second argument, 3. So now we attempt to find the value of the invocation


(attempt 3). This invocation yields the list .(3 <ep>) because once the list

(list n proc) is constructed, attempt is exited. Next we attempt to find


the value of the expression (message "Exit" (3 <ep>)). Once again the
message is displayed, but this time it is an exiting message, "Exiting attempt
with value: (3 <ep>)." The invocation's value is (3 <ep>). Now we bind
pair to this list, take the pair apart, bind v to 3, and bind returner to
<ep>. We display a message that acknowledges where we are and that we do
indeed have the correct value. The message is, "The non-negative number:
3." We then check to see if the number is positive. In this case it is. We
invoke (returner (list (subl v) returner)). We form the list (2 <ep>)
and hand this list to <ep>.
To this point, everything has been the same as in the analysis of attempt
of Program 17.3. In fact, all we did to write the above paragraph was change
instances of <proc> to <ep>. Now we are doing something new. Instead of
invoking <proc>, we are invoking <ep>. The continuation <ep> is the value of

17.3 Making Loops with One Continuation 555


(escaper
.aabds
(let ((pair (aessage "Exit" Z)))
(let ((t (1st pair))
(returner (2iid pair)))
(sriteln " The non-negative-number :
" v)
(if (positive? v)
(returner (list (subl t) returner))
(writeln "Blastoff"))))))

This continuation is formed as the result of the firstand only invocation of


attempt. That is, the value passed as an argument to <ep> becomes the
second argument to message in the let expression that binds pair. The
next event is the displaying of the message. "Exiting attempt with value:
(2 <ej>>)." To go a bit further, the value of this message invocation is (2
<ep>). We bind pair to this list, take the pair apart binding v to 2 and
binding ret^imer to the same <ep>. Once again we display a message that
acknowledges where we are and that we do indeed have the correct value.
The message is, "The non-negative number: 2." We then check to see if
the number is still positive. In this case it is. We invoke

(returner (list (subl v) returner))

Clearly we are in a loop, with v replaced by (subl v). The loop terminates
when V is no longer positive. An important point is that call/cc is invoked
only once. Therefore, we know for certain that <ep> is always the same
continuation. The procedure attempt of Program 17.4 is invoked only once
and its body is never reentered. This follows because the sentence "Entering
attempt with value: n" appears only when n is 3.

Exercise

Exercise 17.1: cycle-proc


Rewrite cycle-proc using continuations instead of recursion as presented in
Program 14.11.

556 Using Conttnuattoru


17.4 Experimenting with Multiple Continuations

In this section we consider an experiment where we use more than one con-
tinuation. Everything until now has worked with just one continuation. Now
we shall use several continuations. To keep track of the full meaning of each
continuation, we shall plug in values for variables that will not change. This
frees us from having to remember their values for use later.

In this experiment we need a receiver and a testing procedure. The receiver


returns <ep>, which it receives as an argument. There are several continua-
tions formed in this one example, so it is easy to get confused.

Program 17.5 receiver

(define receiver
(lambda (continuation)
(continuation continuation)))

Program 17.6 tester

(define test(ar
(lambda (continuation)
(writeln "beginning")
(call/cc continuation)
(writeln "middle")
(call/cc continuation)
(writeln "end")))

Experiment:

[1] (tester (call/cc receiver))


beginning
beginning
middle
beginning
end
[2]

The first event is to form <ep>, which, if it ever gets an argument, passes

17.4 Experimenting with Multiple Continuations 557


that argument to tester. <ep> is the value of:

(escaper
(lambda (D)
(tester D)))

We can think of <ep> as (escaper tester). We invoke (tester <ep>).


Now continuation is bound to <ep>. We write beginning. We next invoke
(call/cc <ep>). This causes us to create <epa>. Before we figure out any-
thing about what <ep> does with <epa>, we must understand what <epa>
does if it ever gets invoked. <epa> is the value of:

(escaper
(laabda (D)
D
(writeln "iddle")
(call/cc <cp>)
(writeln "end")))

The continuation <epa> ignores its argument, D, displays middle, then in-

vokes (call/cc <ep>), and when that returns, it displays end. Now recall

that <ep> takes its argument and invokes (escaper tester) on its argument,
so continuation is bound to <epa>. We write beginning. We next invoke
(call/cc <epa>). This causes us to create <epb>. Before we figure out any-
thing about what <epa> does with <epb>, we must understand what <epb>
does if it ever gets invoked. <epb> is the value of:

(escaper
(laabda (D)
D
(writeln "Middle")
(call/cc <epa>)
(writeln "end")))

The continuation <epb> ignores its argument, displays middle, then invokes
(call/cc <epa>), and when that returns, it displays end. Now recall that
<epa> takes its argument (ignores it) and displays middle, which we do now,
and then invokes (call/cc <ep>). Once agaun we form the new continuation
<epc>, which is the value of:

(escaper
(lambda (D)
D
(writeln "end")))

558 Using Continuations


This continuation ignores argument and displays end, so now we invoke
its

((escaper tester) <epc>). First, we display beginning. Next we invoke


(call/cc <epc>). This causes the creation of the new continuation <epd>,
which is the value of:

(escaper
(lambda (D)
D
(writeln "middle")
(call/cc <epc>)
(writeln "end")))

This continuation displays middle, invokes (call/cc <epc>), and when that
returns, it displays end. What is {<epc> <epd>)? The continuation <epc> is

an escape procedure that ignores its argument and displays end. So we ignore
<epd>, after having gone to all the trouble of constructing it, and display end.

Exercises

Exercise 17.2
During the experiment, how many more continuations were formed than were
invoked?

Exercise 17.3
Determine what this expression represents:

(let ((receiver (lambda (continuation)


(call/cc continuation))))
(call/cc receiver))

What is (call/cc call/cc)?

17.5 Escaping from and Returning to Deep Recursions

In product + of Section 16.9, we demonstrated how to escape from deep re-


cursions. Sometimes we want to escape from deep recursions but jump right
back in when we so desire. In this section, we present a use of continuations
that allows such behavior. We leave the deep recursion, but we give ourselves
the ability to get right back where we were at the time we left. We assume

17.5 Escaping from and Returning to Deep Recursions 559


Program 17.7 f latten-number-list

(define f latten-number-list
(lambda (s)
(cond
((null? s) '())
((number? s) (list (break s)))
(else
(let ((flatcar
(f latten-number--list (car s))))
( append flatcar
(f latten-niiaber-list (c dr s))))))))
1

Program 17.8 breai

(define break
(lambda (x)
i))

Program 17.9 break

(define break
(lambda (x)
(let ( (breeik-receiver
(lambda (continuation)
(continuation i))))
(call/cc break-receiver))))

that the data for the example are the same as those of prodTict+: a deep list

of numbers. (See Section 16.9.)


Consider the definition of f latten-number-list in Program 17.7, where
the first version of break is the identity procedure given in Program 17.8.

Hence:

(flatten-number-list '((1 2 3) ((4 5)) (6))) (12 3 4 5 6)

Another way to write break, which uses continuations but has the same
meaning, is given in Program 17.9. This follows because we return as a value
the argument to break. Since that value is x, we get the equivalent of (lambda

560 Using Continuattoru


Program 17.10 break

(define get-back "any procedure")

(define breeJc
(lanbda (x)
(let ( (bresJt-receiver
(leuDbda (continuation)
(set! get-back (lambda () (continuation x)))
(amy-action x))))
(call/cc brezJt-receiver))))

Program 17.11 any-action

(define any-action
(lambda (x)
(writeln x)
(get-back)))

Program 17.12 any-action

(define any-action
(lambda (x)
((escaper (lambda () x)))
(get-back)))

(x) x). But now we have access to continuation, and, moreover, we can
characterize its behavior. Whenever break is invoked, we can think about the

call as temporarily halting the computation; by invoking continuation on


the same argument, we can continue the computation where it left off. We do
not notice anything about the pause taking place because the continuation
invocation happens immediately. But that is not required. For example, in
Program 17.10, we display the value of the argument to breeJc, using any-
action, which is defined in Program 17.11. But since any-action is any
action whatsoever, we may rewrite it as shown in Program 17.12 instead of

explicitly writing the value of x.

Does the invocation of (get-back) in any-action of Program 17.12 ever


happen? Because we are invoking an escape procedure prior to invoking

17.5 Escaping from and Returning to Deep Recursions 561


Program 17.13 break

(define get-back "any escape procedure")

(define break
(lambda (z)
(let ((break-receiver
(lambda (continuation)
(set! get-back continuation)
(any-action x))))
(call/cc break-receiver))))

(get-back), the answer is no. Is there a way to get back into the original
computation? The answer is yes. Since get-back is bound globally, we can
invoke it at the prompt. Below is an experiment using these tools.

[1] (flatten-number-list '((1 2) 3))


1

[2] (get-back)
2

[3] (get-back)
3
[4] (get-back)
(1 2 3)

The procedure break has a limitation. There is no control over what value is

sent back. Unfortunately, that is determined by the definition of get-back.


We can soften the definition by allowing get -back to accept an argument.
Then get-back becomes

(lambda (v) (continuation v))

which is same as continuation and gives us Program 17.13. We can


the
still use any-action defined in Program 17.12 since the escaper invocation

guarantees that (get-back) will never be invoked. Whenever get-back is


invoked, it must be passed an argument.
Then the experiment could produce different results:

[1] (flatten-number-list '((1 2) 3))


1

[2] (get-back 4)
2

562 Using Continuations


Program 17.14 any-action

"
(define breeJc-argunent any value")

(define smy-action
(lambda (x)
(set ! bresJt-argxment x)
((escaper (lambda () x)))))

[3] (get-back 5)
3
[4] (get -back 6)
(4 5 6)

Why is the result (4 5 6) in this experiment, whereas it was (1 2 3) in the

previous experiment? By returning the values 4, 5, and 6 to the get-back


continuation, we are returning a different value each time. The computation
was repeatedly suspended waiting for a value, which we supplied interactively
at the prompt.
We might want to make public the value of the argument to breeik. We
can do this in any-action, as shown in Program 17.14.
Finally, we note
Program 17.15 that smy-action is not strictly neces-
in

sary and can be included in the definition of break-receiver. The procedure


break is an interesting program. It is very useful for interactive debugging.
For example, by changing the argument to break, we can construct a mech-
anism for accessing and modifying part of the local state at the point of the
invocation of break. This can be accomplished by passing to breeik proce-
dures such £IS

(lambda x) or (lambda (v) (set! x v))

In this case, if x is locally bound at the time of invocation of break, the list

composed of these two procedures gives a lot of power to affect the internal
state of a computation. Program 17.16 shows how flatten-number-list
changes to support break. Whenever breaJc occurs, breaOt-ao-giunent gets
bound to a two-element list and we define the extract and store procedures
as shown in Programs 17.17 and 17.18.

This is just the tip of an iceberg. We are concerned only about one vari-
able. This idea for debugging can be generalized to lists of arbitrarily many
variables, but its utility diminishes as the number of variables increases. If

17.5 Escaping from and Returning to Deep Recursions 563


Program 17.15 break

(define get-back "any escape procedure")

(define break-argument "any value")

(define break
(lambda (z)
(let ((break-receiver
(lambda (continuation)
(set! get-back continuation)
(set ! break-argument x)
((escaper (lambda () x))))))
(call/cc break-receiver))))

Program 17.16 flatten-number-list

(define flatten -number-list


(lambda (s)
(cond
((null? s] '())
( (number? s) (list
(break
(list (lambda () s)
(lambda (v) (set! s v))))))
(else
(let ((flatcar
(flatten-number-list (car s))))
(append f latceir

(flatten-number-list (cdr s)) ))))))

Program 17.17 extract

(define extract
(lambda ()

((1st break- argument))))

there are too many variables, it may be time to redesign the procedures. With
breatk we have seen how there are many continuations coming from one pro-

564 Using Continuations


Program 17.18 store

(define store
(laabda (value)
((2nd break-eurguaent) value)))

cedure invocation of flatten-number-list. Each of these continuations is

eventually invoked after escaping to the prompt. The escape to the prompt is
not very exciting. In the next section we allow far more interesting behavior to
balance each continuation. Because the behavior of such uses of continuations
is balanced, these continuations are called coroutines.

Exercises

Exercise 17.4: llatten-nuober-list


Consider the new definition of flatten-number-list below. What changes
are needed to make the sequence of invocations to get-back in the first ex-
periment produce the same result? How about for the second experiment?

(define f latten-nuaber-list
(laBbda (s)
(letrec
((flatten
(laabda (s)
(cond
((null? s) '())
((nuBber? a) (break (list s)))
(else (let ((flatcar (flatten (car s))))
(append flatcar (flatten (cdr s) ))))))))
(flatten s))))

Exercise 17.5
Consider how we can repeat the results of the first experiment using flatten-
number-list of Program 17.16. A condition imposed on this exercise is that
no number may be input from [2] to the end of the experiment. Hint: Do
not use store.

Exercise 17.6: product*


Consider product + below and define break-on-zero, which displays a and
escapes to the prompt. Each time it displays a 0, resume the computation

17.5 Escaping from and Returning to Deep Recursions 565


as if the had been a 1. This can be done by typing (get -back 1) at the
prompt. more than three zeros are found, then the result is "error: too
If

many zeros." This is actually a form of exception handling where finding


the corresponds to an exception and finding the fourth corresponds to an
error. Experiment with different models of user interaction.

(define product+
(lambda (n Is)
(letrec ((product
(lambda (Is)
(cond
((null? Is) 1)

((number? (car Is))


(* (if (zero? (csir Is)) (break-on-zero) (car Is))
(product (cdr Is))))
(else (* (product (car Is))
(product (cdr Is))))))))
(+ n (product Is)))))

Experiment with

(product+ 5 '((1 2) (3 4) (0 6) (7 0)))

(product+ 5 '((1 2) (0 3) (2 ((0 5) 0) 0)))

Exercise 17.7: break-var


A syntax table entry for break-var can be written so that:

(break-var var)

(break (list (leunbda var) (lambda (v) (set! var v))))

Test f latten-number-list of Program 17.16 using break-var.


Bonus: This works for all variable names except one. Why is the variable
name, for which it does not work, a bad choice?

Exercise 17.8
Consider the following experiment:

[1] (f latten-number-list '((1 2) 3))


1

[2] (get-back 4)
2
[3] (f latten-number-list '((5 6 7) 8))
5

566 Using Continuations


In this experiment, (flatten-number-list '((1 2) 3)) never gets a value.
Why? Generalize breaJc to maintain a list (as a stack) of get-back contin-
uations so that no information is lost. Then continue the experiment to get
these results.

[4] (get -back 7)


6
[5] (get-back 8)
7
[6] (get -back 9)
8
[7] (get-back 10)
(7 8 9 10)
[8] (get -back 5)
3
[9] (get-back 6)
(4 5 6)

Exercise 17.9
Consider the results of the experiment from the previous exercise. How would
the results differ if the list of continuations were treated like a queue instead
of a stack?

17.6 Coroutines: Continuations in Action

There are lots of ways to package control information. We next look at a


famous problem along with a well-known control mechanism. The problem
is Grune's problem, and the control mechanism is called coroutines. Before
we look at Grune's problem, we consider a simplified version of the use of
coroutines. It is sometimes legitimate to imagine that several procedures are
running at the same time, sending information among themselves. In this
model, only one procedure is running at any given time. When information
is sent from an active procedure to a dormant procedure, the active proce-
dure becomes dormant, and the dormant procedure, the one receiving the
information, becomes the active one.
One of the best examples for thinking about coroutines comes from game
playing. Imagine a typical board game with three players. Each player is

modeled by a coroutine, so there are three coroutines. Let us name these


coroutines A, B, and C. Let us further assume that A plays first, hands the dice
to B, B then plays and hands the dice to C, and then C plays and hands the
dice back to A, and so on. In translating this game into a computer program,

17.6 Coroutines: Continuations in Action 567


the code for A indicates a transfer of control by resuming B, and it indicates a
transfer of the dice by passing them as an operand with the resume operation.
This is accomplished by including in the code for A an instance of (resmne B
dice). Similarly, the code for B includes (resume C dice), and the code for
C includes (resume A dice). The act of resuming means that the coroutine
stops processing, and the entity that is the first argument to resume continues
processing where it left off.

The board game's control flow is very regular. A plays, then B plays, then
C plays, then A plays, and so on. As a result, not enough of the generality of
coroutines can be seen through a board game simulation. If each player deter-
mined randomly which opposing player was to play next, this would require
much of the generality of coroutines. Rather than using random numbers
we simply picked an unnatural ordering that is illustrated in Program 17.19.
Remember that nothing is displayed in a writeln expression until all of its

operands have a value. Now if we invoke (A '*) we get the following output:

[1] (A '*)

This is A
This is B
This is C
Came from C

Back in A
Came from A
Back in C
Came from C
Back in B
Came from B

Let us see what it takes to make these programs work. We need the pro-
cedure coroutine-maker, which takes a procedure as an argument. This
argument is a procedure that obtains a meaning for resume and v when it is

invoked. The variable v is of little concern. We focus on the variable resume.


From these examples, we see that resume necessarily must look like a pro-
cedure of two arguments. When resume is invoked, it does not immediately
return a value. In fact, it gives up control to whomever it is resuming and
eventually gets an answer when someone else resumes it. (Since coroutines
are first class, not only can they be passed as the required first argument to
resume, but they can also be included in the second argument to resume.)

Program 17.20 contains coroutine-meiker.


The first thing that coroutine-maker does is create a local variable that
will only hold continuations. Next, a procedure update- continuation! is

568 Using Continuations


Program 17.19 Coroutines for a simple board game

(define A
(let ((A-proc (lambda (resume v)
(writeln "This is A")
(writeln "Came from " (resume B "A"))
(writeln "Back in A")
(writeln "Came from " (resume C "A")))))
(coroutine-mciker A-proc)))

(define B
(let ((B-proc (lambda (resume v)
(writeln (blanks 14) "This is B")
(writeln (blanks 14)
"Came from " (resume C "B"))
(writeln (blanks 14) "Back in B")
(writeln (bleinks 14)
"Came from " (resume A "B")))))
(coroutine-meiker B-proc)))

(define C
(let ((C-proc (leimbda (resume v)
(writeln (blanks 28) "This is C")
(writeln (blanks 28)
"Came from " (resume A "C"))
(writeln (blanks 28) "Back in C")
(writeln (blanks 28)
"Came from " (resume B "C")))))
(coroutine-maJcer C-proc)))

formed so that saved-continuation can be done within


local side effects to
other procedures. This is reminiscent of some of the techniques we presented in
Chapter 12 when we showed how objects were built. The procedure resumer,
having the properties of resmne we discussed above, is next defined using
resume-meiker, whose code is given in Program 17.21. A boolean flag, first-
time, is initially true. Then a procedure is returned. The first time this
procedure is invoked, (proc resumer value) is evaluated. This is where
the binding of resume and v in the programs above takes place. Subsequent
invocations of this procedure invoke a continuation that was stored as a result
of an earlier invocation of a resiime to some other coroutine. Basically, the

structure of resumer is

n.6 Coroutines: Continuations in Action 569


) .

Progreun 17.20 coroutine-meJcer

(define coroutine-maker
(leuabda (proc)
(let ((saved-continuation "any continuation"))
(let ((update-continuation!
(lambda (v)
(set! saved-continuation v))))
(let ((resumer (resume -msJcer update-continuation!))
(first-time ft))
(leimbda (value)
(if first-time
(begin
(set! first-time if)
(proc resumer value))
(saved-continuation value) )))))))

Program 17.21 resune-malcer

(define resume-mciker
(lambda ( update -proc !

(lambda (next-coroutine value)


(let ((receiver (lambda (continuation)
(update-proc! continuation)
(next-coroutine value))))
(call/cc receiver)))))

(lambda (next-coroutine value)


(let ((receiver (lambda (continuation)
(.<update—continuation\> continuation)
(next -coroutine value))))
(cadl/cc receiver)))

Thus far the code has not shown us where the continuations are being cre-
ated. In coroutine-maker, this is done in the procedure formed by invoking
(resume-maker update-continuation ! )

When resumer is invoked with a coroutine, say B, and a value, say "V",
a continuation bound to continuation. That continuation is stored in
is

the saved-continuation associated with the code of the invoker of resumer.


For example, if the code (resume B "V") is invoked from within A, then the

570 Using Continuations


updating takes place in the saved-continuation associated with coroutine
A. When the updating is finished, the value "V" is sent to coroutine B. B
then causes the invocation of the saved-continuation, which was stored as
a result of an earlier invocation of its resumer.

Exercises

Exercise 17.10
To clarify the behavior of coroutine-msQcer and resume-meJcer, we used many
variables. Very few are required. Furthermore, resume-maicer itself is not nec-
essary. Using this knowledge, rewrite coroutine-meiker with as few variables
as possible.

Exercise 17.11
Look at the results of the previous exercise. If there is a first-time flag,

rewrite coroutine-meOter so that it no longer requires such a variable.

Exercise 17.12
Study the definitions of ping and pong below:

(define ping
(let ((ping-proc (lambda (resume v)
(display "ping-")
(resume pong 'ignored-ping))))
(coroutine-maker ping-proc)))

(define pong
(let ((pong-proc (leunbda (resume v)
(display "pong")
(newline)
(resume ping 'ignored-pong))))
(coroutine-mciker pong-proc)))

What happens when we evaluate (begin (ping '*) (pong '*))?

17.7 Grune's Problem

Now we are ready to look at Grune's problem (Grune 1977). The problem is

described as follows:

1 7. 7 Grune 's Problem 571


We have a process A that copies symbols from input to output in such
a way that where the input has aa, the output will have b instead.
And we have a similar process B that converts bb into c. Now we
want to connect these processes in series by feeding the output of A
into B. Input with aab yields c, as does baa.

If we line the processes up as:

Input ^ k ^ B ^ Output

we can think of the flow of requests emanating at Output. Requests for values
flow from right to left and values, themselves, flow from left to right. This is

reminiscent of streams. The coroutine Output requests of B to find a symbol


for Output to The coroutine B requests of A to find a symbol for B to
display.
consider in its analysis of a "possible c." The coroutine A requests of Input to
find a symbol for A to consider in its analysis of a "possible 6." Having made
these requests, control now lies within Input. It does a read by first prompting
the user. It responds by resuming A with that symbol. It does this with the
following code, (resume right (prompt-read "in> ")), where A is bound
to right. A is now in control. If the symbol is an a, A cannot pass it along to
B because the next symbol reeid might be an a. The only possible alternative
for A is to give control back to Input. Once again Input prompts for the next

symbol. This symbol is also sent to A. Now A has enough information to send
something to B. Here are the conditions under which information flows to the
right. In these rules, x and y are the symbols in question, q is not the same
cis X, where x is a (respectively, b) and y is 6 (respectively, c):

1. XX =^ send y to the right.

2. X q ==> send x to the right, saving q for the next request.

3. q =^ send q to the right.

The code segment for this characterization follows:

(let ((symbol-1 (resume left 'ok)))


(if (eq? symbol-1 x)
(let ((syTiibol-2 (resume left 'more)))
(if (eq? symbol-2 i)
(resume right y)
(begin
(resume right symbol-1)
(resume right symbol-2))))
(resume right symbol-1)))

572 Using Continuations


Program 17.22 reader

(define reader
(lajBbda (right)
(let ((co-proc (lambda (resume v)
(cycle-proc
(lambda
(resume right (prompt-read "in> ")))))))
(coroutine-meiker co-proc))))

In order to replace aa by 6, x is a, y is 6, left is Input, and right is B; in

order to replace 66 by c, x is 6, y is c, left is A, and right is Output. Here


is a description of the code segment. Get a symbol from left. If that symbol
differs from x, send it along to right. If not, get another symbol from left.
If that symbol is the same as the first, send y to right. If it differs, send both
symbols, one at a time, to right.
The action of Output makes a request from its left neighbor
is simple. It

(i.e., B). If it finds a symbol matching end, it invokes an escape procedure,


and the computation halts. If not, it writes the symbol. The code segment
for this Output action is:

(let ((symbol (resume left 'ok)))


(if (eq? symbol 'end)
(escape-on-end symbol)
(writeln "out> " symbol)))

The action of Input sends to its right neighbor whatever it read after first

displaying a prompt:

(resume right (prompt-read "in> "))

Given that these are the basic actions, it is a relatively simple task to make
sure all free variables have the correct values and that each code segment is

run as a nonterminating loop with cycle-proc. The three procedures for


forming the coroutines are given in Programs 17.22, 17.23, and 17.24.
We still have the task of building the wires into the communication channels.
We are now going to use letrec to create the mutually recursive coroutines
Input, A, B, and Output. One might expect the following letrec expression to

work:

J 7. 7 Grune 's Problem 573


1

Program 17.23 writer

(define writer
(lEunbda (left escape-on-end)
(let ((co-proc (lambda (resume v)
(cycle-proc
(lambda ()

(let ((symbol (resume left 'ok)))


(if (eq? symbol 'end)
(escape-on-end 'end)
(writeln "out> " symbol))))))))
(coroutine-maker co-proc))))

Program 17.24 x->y

(define x ->y
(lambda (x y left right)
(let ((co-proc (lambda (resume v )
(cycle-proc
(lambda ()

(let ((symb ol-l (resume left •ok)))


(if (eq? symbol- x)
(let ((symbol--2 (resume left 'more)))
(if (eq? symbol-2 x)
(resume right y)
(begin
(resume right symbol- 1)
(resume right symbol-2))))
(resume right symbol-!)) ))))))
(coroutine-maker co-proc))))

(letrec
((Input (reader A))
(A (x->y 'a 'b Input B))
(B (x->y 'b 'c A Output))
(Output (writer B escape-grune)))
(Output 'ok))

Each of Input, A, B, and Output is built by invoking the procedures reader,


x->y, x->y, and writer, respectively. The procedures reader, x->y, and

574 Using Continuations


)

Program 17.25 grune

(define grune
(laabda
(let ((grune-receiver
(laabda (escape-grune)
(letrec
((Input (reader (lambda (v) (A v))))
(A (x->y 'a 'b (lambda (v) (Input v)) (launbda (v) (B v))))
(B (x->y 'b 'c (lambda (v) (A v)) (lambda (v) (Output v))))
(Output (writer (lambda L (v) (B v)) escape--grune)))
(Output 'ok)))))
(call/cc gnme-receiver) ) )

writer have been carefully designed to avoid invoking any of their corou-
tine arguments: Input, A, B, and Output. Here is the problem. All of the
procedures are being created at the same time as they are being passed as
arguments. For example, to create Input, we need A, and to create A, we need
Input. To solve this problem, we must freeze the coroutines that are argu-
ments in the right-hand sides of definitions. This has the effect of postponing
the evaluation of the variables that refer to the coroutines. Unfortunately, if

we freeze these variables, we get the wrong arity; that is, coroutines take one
argument, but frozen objects (i.e., thunks) take no arguments. The code that
follows, however, works:

(letrec
((Input (reader (lambda (v) (A v))))
(A (x->y 'a 'b (lambda (v) (Input v)) (lambda (v) (B v))))
(B (x->y 'b 'c (lambda (v) (A v)) (lambda (v) (Output v))))
(Output (writer (lauibda (v) (B v)) escape-grune)))
(Output 'ok))

Program 17.25 shows the final definition of grune with all the necessary
uses of (lambda (v) ( v)). In the exercises, we develop a more natural
way to think about this unusual behavior.

i 7. 7 Grune 'a Problem 575


Exercises

Exercise 17.13: wrap


Consider the special form wrap, which has the following syntax table entry:

(wrap proc) = (lambda args (apply proc args))

This works in all cases but one: when args is a free variable in the proc
expression. Rewrite wrap using thunks to avoid this potential free variable
capture.

Exercise 17.14
Using the results of the previous exercise, write the syntax table entry for
wrap when proc is known to refer to a procedure of just one argument. This
is the case for the coroutines used in grime. Is free variable capture still a
problem?

Exercise 17.15
Using the results of the previous exercise, redefine gnine using wrap.

Exercise 17.16: safe-letrec


Another way to implement gnine is with a special form safe-letrec. This
special form is like letrec except that each right-hand side variable is wrapped
if it also appears as a left-hand side variable. Using the results of the previous
exercise, create the syntax table entry for safe-letrec so that the following
definition of grune works. [Hint: Use let to bind proc to (wrap proc) to
avoid processing each right-hand side.)

(define gnine
(lambda ()

(let ((grune-receiver (lambda (escape-grune)


(safe-letrec
((Input (reader A))
(A (x->y 'a 'b Input B))
(B (x->y 'b 'c A Output))
(Output (writer B escape-grune)))
(Output 'ok)))))
(call/cc grune-receiver))))

576 Using Continuations


Exercise 17.17: process-msLker
Sometimes processes are perceived as automatically being in an infinite loop.
Use the following variation of coroutine-meOcer, called process-meiker, and
rewrite the solution to the Grune problem using processes.

(define process-meJcer
(lambda (f)
(let ((saved-continuation "any continuation"))
(let ((update-continuation!
(lambda (v)
(set! saved-continuation v))))
(let ((resumer (resume-maJcer update-continuation!))
(first-time #t))
(leuabda (value)
(if first-time
(begin
(set! first-time #f)
(cycle-proc
(lambda ()
(f resumer value))))
(saved-continuation value))))))))

Exercise 17.18
Using the results of the previous exercise, explain how process-maker differs

from coroutine-maker by constructing an appropriate example.

Exercise 17.19
Redesign Towers of Hanoi using coroutine-maker so that each disk is a corou-
tine. Can process-maker be used?

Exercise 17.20
Redesign the solution of the Eight Queens problem using coroutine-maker
so that each queen is a coroutine. Can process-madcer be used?

Exercise 17.21
Implement Grune's problem using streams instead of coroutines.

Exercise 17.22
Extend grune to any number of x->y pairs. Hint: This can be accomplished
by rewriting the procedure grime leaving everything else unchanged.

Exercise 17.23
Rework the previous exercise using streams.

17.8 Final Thoughts 577


17.8 Final Thoughts

We have not shown you all the interesting things you can think about with
continuations, but we have tried to show you some of the ways that continua-
tions can be used. Most of the time, you should be content to solve problems
with conventional procedural techniques. Occasionally you will be tempted
to use state changing operations like those we used when we worked with
object-oriented programming. And even less frequently you will run across a
need for continuations. This is your basic bag of tricks.

The existence of the computer ha^ been incidental to the understanding


of the concepts conveyed in this book. The computer's role has been much
like that of a chemist's laboratory, used primarily for experimentation. What
would happen if you added two parts hydrogen to one part oxygen? If you are
curious about what happens when you compose two procedures, use the com-
puter as your laboratory. What happens when you compose the procedure
(lambda (x) (+ x 1)) with the procedure (lambda (x) (- x 1))? This
book has been about ideas and how we can combine separate categories of
ideas to create procedures that do our computing. Although some emphasis
has been placed on how fast the computer determines the value of a com-
putation, we have tried to approach the ideas in this book more in terms of
capturing the essence of a computation. Subtle issues of efficiency can come
much later. We have challenged you at every turn. Each piece of the compu-
tational puzzle fits together and is described in terms of simple ideas. Under
our guidance, you have entered the universe of computer science. It was our
goal to cause you to look forward to future explorations into this fascinating
field.

PROBLEMS

Problems worthy
of attack
prove their worth
by hitting back.

Piet Hein, Grooks

578 Using Continuations


Al The ASCII Character Set

Al.l The ASCII Table

Hex 1 2 3 4 5 6 7

NUL DLE SP @ P (
P

1 SOH DCl j 1 A Q a q

2 STX DC2 II
2 B R b r

3 ETX DCS # 3 C S c s

4 EOT DC4 $ 4 D T d t

5 ENQ NAK % 5 E U e u

6 ACK SYN & 6 F V f V

7 BEL ETB 1
7 G w g w
8 BS CAN (
8 H X h X

9 HT EM ) 9 I Y i y

A(10) LF SUB : J Z J z

B(ll) VT ESC + )
K [ k {

C(12) FF FS )
< L \ 1
1

D(13) CR OS - = M ] m }
" ~
E(14) SO RS • > N n

F(15) SI US / 7 - DEL
A1.2 Abbreviations for Control Characters

NUL null DCl device control 1

SOH start of heading DC2 device control 2


STX start of text DCS device control 3
ETX end of text DC4 device control 4
EOT end of transmission NAK negative acknowledge
ENQ enquiry- SYN synchronous idle

ACK acknowledge ETB end of transmission block


BEL bell CAN cancel
BS backspace EM end of medium
HT horizontal tabulation SUB substitute
LF linefeed ESC escape
VT vertical tabulation FS file separator
FF form feed GS group separator
CR carriage return RS record separator
SO shift out US unit separator
SI shift in SP space
DLE data link escape DEL delete

A1.3 How to Use the ASCII Table

ASCII stands for the American Standard Code for Information Interchange.
(Scheme now supports the broader International Character Code.) The num-
ber at the top of a column represents the first digit, and the number at the
left of a row represents the second digit of the hexadecimal number that is

used to represent the character in that row and column. Thus the hexadeci-
mal code for the letter G, which is in the 4*^^ column and 7*'^ row, is 47. The
hexadecimal code for the letter g is 67, and for M is 4D. To get the decimal
representation for the ASCII code, multiply the number at the top of the

column by 16 and add the number at the left of the row


of the given char-
acter. Then G has decimal code (4 x 16) + 7 = 71, and M has decimal code

(4 X 16) -(- 13 = 77. The first two columns contain the control characters that
do not print. To enter those characters from the keyboard, we hold down the
CONTROL key while pressing the key for the character located four columns
to the right of the desired control character. For example, to enter BEL, we
press <CTRL>G, and to enter ESC, we press <CTRL>[.

580 The ASCII Character Set


References

Abelson, Harold, and Gerald J.Sussman, with Julie Sussman. 1985. Structure and
Interpretation of Computer Programs Cambridge, MA:
, MIT Press and New York,
NY: McGraw-Hill Book Company.
Bronowski, Jacob. 1978. The Visionary Eye. Cambridge, MA: MIT Press.

Church, Alonzo. 1941. The Ca/cuh o/ Lambda-conversion, Annals of Math. Studies,


No. 6. Princeton, NJ: Princeton Univ. Press.

Dybvig, R. Kent. 1987. The Scheme Programming Language. Englewood Cliffs,

NJ: Prentice-Hall.
Eisenberg, Michael. 1988. Programming in Scheme. Redwood City, CA: Scientific
Press.

Feller, WiUiam. 1950. An Introduction to Probability Theory an its Applications,


Vol. 1. New York, NY: John WUey and Sons.

Friedman, Daniel P., and Matthias FeUeisen. 1988. The Little LISPer, Third Edi-
tion, Chicago, IL: SRA, and Trade Edition, Cambridge, MA: MIT Press.

Grune, Dick. 1977. A view of coroutines. In ACM SIGPLAN Notices. 12(7):75-81.

Hein, Piet. 1966. Crooks. Cambridge, MA: MIT Press.

Hofstadter, Douglas R. 1985. Metamagical Themas: Questing for the Essence of


Mind and Pattern. New York, NY: Basic Books, Inc.

Kohlbecker, Eugene E. 1986. Syntactic Extensions in the Programming Language


Lisp. Ph.D. Thesis, Indiana University, Bloomington, IN.
Marling, WiUiam. 1990. Maestro of Many Keyboards. In Case Alumnus. 67(8):2-7.

McCarthy, John. 1960. Recursive functions of symboHc expressions and their com-
putation by machine. In Communications of the ACM, 3(4):184-195.

Minsky, Marvin L. 1967. Computation: Finite and Infinite Machines. Englewood


CUfFs, NJ: Prentice-HaU.

Park, Stephen K., and Keith W. MUler. 1988. Random number generators: Good
ones are hard to find. Communications of the ACM. 31(10):1192-1201.
Rees, Jonathan, and William Clinger, editors. 1986. The revised report on the
algorithmic language Scheme. In ACM SIGPLAN Noticies 2l(l2):37-79.

Semantic Microsystems. 1987. MacScheme + Toolsmith , a LISP for the future.


Beaverton, OR: Semantic Microsystems.

Schonfinkel, Moses. 1924. On the building blocks of mathematical logic. In From


Frege to Godel, A source book in mathematical logic, 1879-1931 . Edited by Jean
van Heijenoort, Cambridge, MA: Harvard Univ. Press, 1977.

Smith, Jerry D. 1988. An Introduction to Scheme. New York, NY: Prentice-Hall.

Steele,Guy Lewis, Jr., and Gerald Jay Sussman. 1978. The revised report on
Scheme, a dialect of Lisp. Memo 452, MIT Artificial Intelligence Laboratory.

Sussman, Gerald Jay, and Guy Lewis Steele Jr. 1975. Scheme: an Interpreter for
Extended Lambda Calculus. Memo 349, MIT Artificial Intelligence Laboratory.
Texas Instruments. 1988. PC Scheme, User's Guide & Language Reference Manual.
Redwood City, CA: The Scientific Press.

582 References

You might also like