On occasion, a function consumes two arguments that belong to classes with nontrivial data definitions. In some cases, one of the arguments should be treated as if it were atomic; a precisely formulated purpose statement typically clarifies this. In other cases, the two arguments must be processed in lockstep. Finally, in a few rare cases, the function must take into account all possible cases and process the arguments accordingly. This section illustrates the three cases with examples and provides an augmented design recipe for the last one. The last section discusses the equality of compound data and its relationship to testing; it is essential for automating test suites for functions.
Consider the following contract, purpose statement, and header:
;;replaceeolwith : listofnumbers listofnumbers > listofnumbers
;; to construct a new list by replacingempty
in alon1 with alon2 (define (replaceeolwith alon1 alon2) ...)
The contract says that the function consumes two lists, which we haven't seen in the past. Let's see how the design recipe works in this case.
First, we make up examples. Suppose the first input is empty
. Then
replaceeolwith
should produce the second argument, no matter
what it is:
(replaceeolwith empty L) = L
In this equation, L
stands for an arbitrary list of numbers. Now
suppose the first argument is not empty
. Then the purpose
statement requires that we replace empty
at the end of alon1
with alon2
:
(replaceeolwith (cons 1 empty) L) ;; expected value: (cons 1 L)
(replaceeolwith (cons 2 (cons 1 empty)) L) ;; expected value: (cons 2 (cons 1 L))
(replaceeolwith (cons 2 (cons 11 (cons 1 empty))) L) ;; expected value: (cons 2 (cons 11 (cons 1 L)))
Again, L
stands for any list of numbers in these examples.
The examples suggest that it doesn't matter what the second argument
is  as long as it is a list; otherwise, it doesn't even make sense to
replace empty
with the second argument. This implies that the
template should be that of a listprocessing function with respect to the
first argument:
(define (replaceeolwith alon1 alon2) (cond ((empty? alon1) ...) (else ... (first alon1) ... (replaceeolwith (rest alon1) alon2) ... )))
The second argument is treated as it were an atomic piece of data.
Let's fill the gaps in the template, following the design recipe and using
our examples. If alon1
is empty
,
replaceeolwith
produces alon2
according to our
examples. For the second cond
clause, when alon1
is not
empty
, we must proceed by inspecting the available expressions:
(first alon1)
evaluates to the first item on the list, and
(replaceeolwith (rest alon1) alon2)
replaces
empty
in (rest alon1)
with alon2
.
To gain a better understanding of what this means, consider one of the examples:
(replaceeolwith (cons 2 (cons 11 (cons 1 empty))) L) ;; expected value: (cons 2 (cons 11 (cons 1 L)))
Here (first alon1)
is 2
, (rest alon1)
is
(cons 11 (cons 1 empty))
, and (replaceeolwith (rest
alon1) alon2)
is (cons 11 (cons 1 alon2))
. We can combine
2
and the latter with cons
and can thus obtain the
desired result. More generally,
(cons (first alon1) (replaceeolwith (rest alon1) alon2))
is the answer in the second cond
clause. Figure 45
contains the complete definition.
Exercise 17.1.1.
In several exercises, we have used the Scheme operation append
,
which consumes three lists and juxtaposes their items:
(append (list 'a) (list 'b 'c) (list 'd 'e 'f)) ;; expected value: (list 'a 'b 'c 'd 'e 'f)
Use replaceeolwith
to define ourappend
, which acts
just like Scheme's append
.
Solution
Exercise 17.1.2.
Develop cross
. The function consumes a list of symbols and a list
of numbers and produces all possible pairs of symbols and numbers.
Example:
(cross '(a b c) '(1 2)) ;; expected value: (list (list 'a 1) (list 'a 2) (list 'b 1) (list 'b 2) (list 'c 1) (list 'c 2))
In section 10.1, we developed the function
hours>wages
for the computation of weekly wages. It consumed a
list of numbers  hours worked per week  and produced a list of weekly
wages. We had based the function on the simplifying assumption that all
employees received the same pay rate. Even a small company, however,
employs people at different rate levels. Typically, the company's
accountant also maintains two collections of information: a permanent one
that, among other things, includes an employee's personal payrate, and a
temporary one that records how much time an employee has worked during the
past week.
The revised problem statement means that the function should consume two lists. To simplify the problem, let us assume that the lists are just lists of numbers, pay rates and weekly hours. Then here is the problem statement:
;;hours>wages : listofnumbers listofnumbers > listofnumbers
;; to construct a new list by multiplying the corresponding items on ;;alon1
andalon2
;; ASSUMPTION: the two lists are of equal length (define (hours>wages alon1 alon2) ...)
We can think of alon1
as the list of payrates and of
alon2
as the list of hours worked per week. To get the list of
weekly wages, we must multiply the corresponding numbers in the two input
lists.
Let's look at some examples:
(hours>wages empty empty) ;; expected value: empty
(hours>wages (cons 5.65 empty) (cons 40 empty)) ;; expected value: (cons 226.0 empty)
(hours>wages (cons 5.65 (cons 8.75 empty)) (cons 40.0 (cons 30.0 empty))) ;; expected value: (cons 226.0 (cons 262.5 empty))
For all three examples the function is applied to two lists of equal length. As stated in the addendum to the purpose statement, the function assumes this and, indeed, using the function makes no sense if the condition is violated.
The condition on the inputs can also be exploited for the development of
the template. Put more concretely, the condition says that (empty?
alon1)
is true if, and only if, (empty? alon2)
is true; and
furthermore, (cons? alon1)
is true if, and only if, (cons?
alon2)
is true. In other words, the condition simplifies the design of the
template's cond
structure, because it says the template is similar
to that of a plain listprocessing function:
(define (hours>wages alon1 alon2) (cond ((empty? alon1) ...) (else ... )))
In the first cond
clause, both alon1
and alon2
are empty
. Hence no selector expressions are needed. In the
second clause, both alon1
and alon2
are
cons
tructed lists, which means we need four selector expressions:
(define (hours>wages alon1 alon2) (cond ((empty? alon1) ...) (else ... (first alon1) ... (first alon2) ... ... (rest alon1) ... (rest alon2) ... )))
Finally, because the last two are lists of equal length, they make up a
natural candidate for the natural recursion of hours>wages
:
(define (hours>wages alon1 alon2) (cond ((empty? alon1) ...) (else ... (first alon1) ... (first alon2) ... ... (hours>wages (rest alon1) (rest alon2)) ... )))
The only unusual aspect of this template is that the recursive application
consists of two expressions, both selector expressions for the two
arguments. But, as we have seen, the idea is easily explicable owing to the
assumption that alon1
and alon2
are of equal length.

To define the function from here, we follow the design recipe. The first
example implies that the answer for the first cond
clause is
empty
. In the second one, we have three values available:
(first alon1)
evaluates to the first item on the list of
payrates;
(first alon2)
evaluates to the first item on the list of
hours worked; and
(hours>wages (rest alon1) (rest alon2))
computes the list
of weekly wages for the remainders of alon1
and alon2
.
We merely need to combine these values to get the final answer. More
specifically, given the purpose statement, we must compute the weekly wage
for the first employee and cons
truct a list from that wage and the
rest of the wages. This suggests the following answer for the second
cond
clause:
(cons (weeklywage (first alon1) (first alon2)) (hours>wages (rest alon1) (rest alon2)))
The auxiliary function weeklywage
consumes the two first items and
computes the weekly wage. Figure 46 contains the complete
definitions.
Exercise 17.2.1.
In the real world, hours>wages
consumes lists of employee
structures and lists of work structures. An employee structure contains an
employee's name, social security number, and pay rate. A work structure
contains an employee's name and the number of hours worked in a week. The
result is a list of structures that contain the name of the employee and
the weekly wage.
Modify the function in figure 46 so that it works on these classes of data. Provide the necessary structure definitions and data definitions. Use the design recipe to guide the modification process. Solution
Exercise 17.2.2.
Develop the function zip
, which combines a list of names and a list
phone numbers into a list of phone records. Assuming the following structure
definition:
(definestruct phonerecord (name number)) ,
a phone record is constructed with (makephonerecord s n)
where
s
is a symbol and n
is a number. Assume the lists are of
equal length. Simplify the definition, if possible.
Solution
Here is a third problem statement, given as in the form of a function contract, purpose statement, and header:
;;listpick : listofsymbols N[>= 1] > symbol
;; to determine then
th symbol fromalos
, counting from1
; ;; signals an error if there is non
th item (define (listpick alos n) ...)
That is, the problem is to develop a function that consumes a natural number and a list of symbols. Both belong to classes with complex data definitions, though, unlike for the previous two problems, the classes are distinct. Figure 47 recalls the two definitions.
Because the problem is nonstandard, we should ensure that our examples
cover all important cases. We usually accomplish this goal by picking one
item per clause in the definition and choosing elements from basic forms of
data on a random basis. In this example, this procedure implies that we
pick at least two elements from listofsymbols
and two from
N[>= 1]
. Let's choose empty
and
(cons 'a empty)
for the former, and 1
and 3
for
the latter. But two choices per argument means four examples total; after
all, there is no immediately obvious connection between the two arguments
and no restriction in the contract:
(listpick empty 1) ;; expected behavior: (error 'listpick "...")
(listpick (cons 'a empty) 1) ;; expected value: 'a
(listpick empty 3) ;; expected behavior: (error 'listpick "...")
(listpick (cons 'a empty) 3) ;; expected behavior: (error 'listpick "...")
Only one of the four results is a symbol; in the other cases, we see an error, indicating that the list doesn't contain enough items.
The discussion on examples indicates that there are indeed four possible, independent cases that we must consider for the design of the function. We can discover the four cases by arranging the necessary conditions in a table format:

listpick
must ask about the list argument; the vertical dimension
lists the questions about the natural number. Furthermore, the partitioning
of the table yields four squares. Each square represents the case when both
the condition on the horizontal and the one on the vertical are true. We
can express this fact with andexpressions in the squares:

true
.Using our cases analysis, we can now design the first part of the template, the conditional expression:
(define (listpick alos n) (cond [(and (= n 1) (empty? alos)) ...] [(and (> n 1) (empty? alos)) ...] [(and (= n 1) (cons? alos)) ...] [(and (> n 1) (cons? alos)) ...]))
The condexpression asks all four questions, thus distinguishing
all possibilities. Next we must add selector expressions to each
cond
clause if possible:
(define (listpick alos n) (cond [(and (= n 1) (empty? alos)) ...] [(and (> n 1) (empty? alos)) ... (sub1 n) ...] [(and (= n 1) (cons? alos)) ... (first alos) ... (rest alos)...] [(and (> n 1) (cons? alos)) ... (sub1 n) ... (first alos) ... (rest alos) ...]))
For n
, a natural number, the template contains at most one
selector expression, which determines the predecessor of n
. For
alos
, it might contain two. In those cases where either (=
n 1)
or (empty? alos)
holds, one of the two arguments is atomic
and there is no need for a corresponding selector expression.
The final step of the template construction demands that we annotate the
template with recursions where the results of selector expressions belong
to the same class as the inputs. In the template for listpick
,
this makes sense only in the last cond
clause, which contains both
a selector expression for N[>= 1]
and one for
listofsymbols
. All other clauses contain at most one relevant
selector expression. It is, however, unclear how to form the natural
recursions. If we disregard the purpose of the function, and the template
construction step asks us to do just that, there are three possible
recursions:
(listpick (rest alos) (sub1 n))
(listpick alos (sub1 n))
(listpick (rest alos) n)
Since we cannot know which one matters or whether all three matter, we move on to the next development stage.

Following the design recipe, let us analyze each cond
clause in
the template and decide what a proper answer is:
If (and (= n 1) (empty? alos))
holds, listpick
was
asked to pick the first item from an empty list, which is impossible. The
answer must be an application of error.
If (and (> n 1) (empty? alos))
holds, listpick
was
again asked to pick an item from an empty list. The answer is also
an error.
If (and (= n 1) (cons? alos))
holds, listpick
is
supposed to produce the first item from some list. The selector expression
(first alos)
reminds us how to get this item. It is the answer.
For the final clause, if (and (> n 1) (cons? alos))
holds,
we must analyze what the selector expressions compute:
(first alos)
selects the first item from the list of
symbols;
(rest alos)
is the rest of the list; and
(sub1 n)
is one less that the original given list index.
Let us consider an example to illustrate the meaning of these
expressions. Suppose listpick
is applied to (cons 'a
(cons 'b empty))
and 2
:
(listpick (cons 'a (cons 'b empty)) 2)
The answer must be 'b
, (first alos)
is 'a
, and
(sub1 n)
is 1
. Here is what the three natural recursions
would compute with these values:
(listpick (cons 'b empty) 1)
produces 'b
, the
desired answer;
(listpick (cons 'a (cons 'b empty)) 1)
evaluates to
'a
, which is a symbol, but the the wrong answer for the original
problem; and
(listpick (cons 'b empty) 2)
signals an error because
the index is larger than the length of the list.
This suggests that we use (listpick (rest alos) (sub1 n))
as the
answer in the last cond
clause. But, examplebased reasoning is
often treacherous, so we should try to understand why the expression works
in general.
Recall that, according to the purpose statement,
(listpick (rest alos) (sub1 n))
picks the (n  1)st item from (rest alos)
. In other words, for
the second application, we have decreased the index by 1
,
shortened the list by one item, and now look for an item. Clearly,
the second application always produces the same answer as the first one,
assuming alos
and n
are ``compound'' values. Hence our
choice for the last clause is truly justified.
Exercise 17.3.1.
Develop listpick0
, which picks items from a list
like listpick
but starts counting at 0
.
Examples:
(symbol=? (listpick0 (list 'a 'b 'c 'd) 3) 'd)
(listpick0 (list 'a 'b 'c 'd) 4) ;; expected behavior: (error 'listpick0 "the list is too short")
The listpick
function in figure 48 is more
complicated than necessary. Both the first and the second
cond
clause produce the same answer: an error. In other words,
if either
(and (= n 1) (empty? alos))
or
(and (> n 1) (empty? alos))
evaluates to true
, the answer is an error. We can translate this
observation into a simpler condexpression:
(define (listpick alos n) (cond [(or (and (= n 1) (empty? alos)) (and (> n 1) (empty? alos))) (error 'listpick "list too short")] [(and (= n 1) (cons? alos)) (first alos)] [(and (> n 1) (cons? alos)) (listpick (rest alos) (sub1 n))]))
The new expression is a direct transliteration of our English observation.
To simplify this function even more, we need to get acquainted with an algebraic law concerning booleans:
(or (and condition1 acondition) (and condition2 acondition)) = (and (or condition1 condition2) acondition)
The law is called de Morgan's law of distributivity. Applying it to our function yields the following:
(define (listpick n alos) (cond [(and (or (= n 1) (> n 1)) (empty? alos)) (error 'listpick "list too short")] [(and (= n 1) (cons? alos)) (first alos)] [(and (> n 1) (cons? alos)) (listpick (rest alos) (sub1 n))]))
Now consider the first part of the condition: (or (= n 1) (> n
1))
. Because n
belongs to N[>= 1]
, the
condition is always true. But, if we replace it with true
we get
(and true (empty? alos))
which is clearly equivalent to (empty? alos)
. In other words, the
function can be written as
(define (listpick alos n) (cond [(empty? alos) (error 'listpick "list too short")] [(and (= n 1) (cons? alos)) (first alos)] [(and (> n 1) (cons? alos)) (listpick (rest alos) (sub1 n))]))
which is already significantly simpler than that in figure 48.
Still, we can do even better than that. The first condition in the
latest version of listpick
filters out all those cases when
alos
is empty. Hence (cons? alos)
in the next two
clauses is always going to evaluate to true
. If we replace the
condition with true
and simplify the andexpressions, we get
the simplest possible version of listpick
, which is displayed in
figure 49. While this last function is simpler than the
original, it is important to understand that we designed both the original
and the simplified version in a systematic manner and that we can therefore
trust both. If we try to find the simple versions directly, we sooner or
later fail to consider a case and produce flawed functions.

Exercise 17.4.1.
Develop the function replaceeolwith
following the strategy of
section 17.2. Then simplify it
systematically.
Solution
Exercise 17.4.2.
Simplify the function listpick0
from exercise 17.3.1 or
explain why it can't be simplified.
Solution
On occasion, we will encounter problems that require functions on two complex classes of inputs. The most interesting situation occurs when both inputs are of unknown size. As we have seen in the first three subsections, we may have to deal with such functions in three different ways.
The proper approach to this problem is to follow the general design recipe. In particular, we must conduct a data analysis and we must define the relevant classes of data. Then we can state the contract and the purpose of the function, which, in turn, puts us in a position where we can think ahead. Before we continue from this point, we should decide which one of the following three situations we are facing:
In some cases, one of the parameters plays a dominant role. Conversely, we can think of one of the parameters as an atomic piece of data as far as the function is concerned.
In some other cases, the two parameters are synchronized. They must range over the same class of values, and they must have the same structure. For example, if we are given two lists, they must have the same length. If we are given two Web pages, they must have the same length, and where one of them contains an embedded page, the other one does, too. If we decide that the two parameters have this equal status and must be processed in a synchronized manner, then we can pick one of them and organize the function around it.
Finally, in some rare cases, there may not be any obvious connection between the two parameters. In this case, we must analyze all possible cases before we pick examples and design the template.
For the first two cases, we use an existing design recipe. The last case deserves some special consideration.
After we have decided that a function falls into the third category but
before we develop examples and the function template, we develop a
twodimensional table. Here is the table for listpick
again:

Along the horizontal direction we enumerate the conditions that recognize the subclasses for the first parameter, and along the vertical direction we enumerate the conditions for the second parameter.
The table guides the development of both the set of function examples and the function template. As far as the examples are concerned, they must cover all possible cases. That is, there must be at least one example for each cell in the table.
As far as the template is concerned, it must have one cond
clause
per cell. Each cond
clause, in turn, must contain all feasible
selector expressions for both parameters. If one of the parameters is
atomic, there is no need for a selector expression. Finally, instead of a
single natural recursion,
we might have several. For listpick
,
we discovered three cases. In general, all possible combinations of
selector expressions are candidates for a natural recursion. Because we
can't know which ones are necessary and which ones aren't, we write them
all down and pick the proper ones for the actual function definition.
In summary, the design of multiparameter functions is just a variation on the old designrecipe theme. The key idea is to translate the data definitions into a table that shows all feasible and interesting combinations. The development of function examples and the template exploit the table as much as possible. Filling in the gaps in the template takes practice, just like anything else.
Exercise 17.6.1.
Develop the function merge
. It consumes two lists of numbers,
sorted in ascending order. It produces a single sorted list of numbers
that contains all the numbers on both inputs lists (and nothing else). A
number occurs in the output as many times as it occurs on the two input
lists together.
Examples:
(merge (list 1 3 5 7 9) (list 0 2 4 6 8)) ;; expected value: (list 0 1 2 3 4 5 6 7 8 9)
(merge (list 1 8 8 11 12) (list 2 3 4 8 13 14)) ;; expected value: (list 1 2 3 4 8 8 8 11 12 13 14) ; Solution
Exercise 17.6.2. The goal of this exercise is to develop a version of the Hangman game of section 6.7 for words of arbitrary length.
Provide a data definition for representing words of
arbitrary length with lists. A letter
is represented with the
symbols 'a
through 'z
plus '_
.
Develop the function reveallist
. It consumes three arguments:
the chosen word, which is the word that we have to guess;
the status word, which states how much of the word we have guessed so far; and
a letter, which is our current guess.
It produces a new status word, that is, a word that contains
ordinary letters and '_
. The fields in the new status word are
determined by comparing the guess with each pair of letters from the status
word and the chosen word:
If the guess is equal to the letter in the chosen word, the guess is the corresponding letter in the new status word.
Otherwise, the new letter is the corresponding letter from the status word.
Test the function with the following examples:
(reveallist (list 't 'e 'a) (list '_ 'e '_) 'u)
(reveallist (list 'a 'l 'e) (list 'a '_ '_) 'e)
(reveallist (list 'a 'l 'l) (list '_ '_ '_) 'l)
First determine what the result should be.
Use the teachpack hangman.ss and the functions drawnextpart
(from exercise 6.7.1) and reveallist
to play the
Hangman game. Evaluate the following expression:
(hangmanlist reveallist drawnextpart)
The function hangmanlist
chooses a word randomly and pops up a
window with a choice menu for letters. Choose letters and, when ready,
click on the Check button to see whether your guess is
correct. Enjoy!
Solution
Exercise 17.6.3. In a factory, employees punch time cards as they arrive in the morning and leave in the evening. In the modern age of electronic punch cards, a punch card contains an employee number and the number of hours worked. Also, employee records always contain the name of the employee, an employee number, and a pay rate.
Develop the function hours>wages2
. The function consumes a list of
employee records and a list of (electronic) punch cards. It computes the
weekly wage for each employee by matching the employee record with a punch
card based on employee numbers. If a pair is missing or if a pair's
employee numbers are mismatched, the function stops with an appropriate
error message. Assume that there is at most one card per employee and
employee number.
Hint: An accountant would sort the two lists by employee number first. Solution
Exercise 17.6.4. A linear combination is the sum of many linear terms, that is, products of variables and numbers. The latter are called coefficients in this context. Here are some examples:
In all three examples, the coefficient of x is 5, that of y is 17, and the one for z is 3.
If we are given values for variables, we can determine the value of a polynomial. For example, if x = 10, the value of 5 · x is 50; if x = 10 and y = 1, the value of 5 · x + 17 · y is 67; and if x = 10, y = 1, and z = 2, the value of 5 · x + 17 · y + 3 · z is 73.
In the past, we would have developed functions to compute the values of linear combinations for specific values. An alternative representation is a list of its coefficients. The above combinations would be represented as:
(list 5) (list 5 17) (list 5 17 3)
This representation assumes that we always agree on using variables in a fixed order.
Develop the function value
. It consumes the representation of a
linear combination and a list of numbers. The lists are of equal length. It
produces the value of the combination for these values.
Solution
Exercise 17.6.5. Louise, Jane, Laura, Dana, and Mary are sisters who would like to save money and work spent on Christmas gifts. So they decide to hold a lottery that assigns to each one of them a single gift recipient. Since Jane is a computer programmer, they ask her to write a program that performs the lottery in an impartial manner. Of course, the program must not assign any of the sisters to herself.
Here is the definition of giftpick
. It consumes a list of
distinct names (symbols) and randomly picks one of those arrangements of
the list that do not agree with the original list at any position:
;;giftpick: listofnames > listofnames
;; to pick a ``random'' nonidentity arrangement ofnames
(define (giftpick names) (randompick (nonsame names (arrangements names))))
Recall that arrangements
(see exercise 12.4.2)
consumes a list of symbols and produces the list of all rearrangements of
the items in the list.
Develop the auxiliary functions
randompick : listoflistofnames > listofnames
, which
consumes a list of items and randomly picks one of them as the result;
nonsame : listofnames listoflistofnames > listoflistofnames
,
which consumes a list of names L
and a list of arrangements and
produces the list of those that do not agree with L
at any
position.
Two permutations agree at some position if we can extract the same name
from both lists by applying first
and the same number of
rest
operations to both. For example, (list 'a 'b 'c)
and (list 'c 'a 'b)
do not agree, but (list 'a 'b 'c)
and (list 'c 'b 'a)
agree at the second position. We can prove
that by applying rest
followed by first
to both lists.
Follow the appropriate recipe in each case carefully.
Hint: Recall that (random n)
picks a random number between
0
and n1
(compare with
exercise 11.3.1).
Solution
Exercise 17.6.6.
Develop the function DNAprefix
. The function takes two arguments,
both lists of symbols (only 'a
, 'c
, 'g
, and
't
occur in DNA, but we can safely ignore this issue here). The
first list is called a pattern,
the second one a searchstring. The function returns true
if the pattern is a
prefix of the searchstring. In all other cases, the function returns
false
.
Examples:
(DNAprefix (list 'a 't) (list 'a 't 'c))
(not (DNAprefix (list 'a 't) (list 'a)))
(DNAprefix (list 'a 't) (list 'a 't))
(not (DNAprefix (list 'a 'c 'g 't) (list 'a 'g)))
(not (DNAprefix (list 'a 'a 'c 'c) (list 'a 'c)))
Simplify DNAprefix
, if possible.
Modify DNAprefix
so that it returns the first item beyond the
pattern in the searchstring if the pattern is a proper prefix of the
searchstring. If the lists do not match or if the pattern is no shorter
than the searchstring, the modified function should still return
false
. Similarly, if the lists are equally long and match, the result
is still true
.
Examples:
(symbol=? (DNAprefix (list 'a 't) (list 'a 't 'c)) 'c)
(not (DNAprefix (list 'a 't) (list 'a)))
(DNAprefix (list 'a 't) (list 'a 't))
Can this variant of DNAprefix
be simplified? If so, do it. If
not, explain.
Solution
The goal of this section is to extend the evaluator of section 14.4 so that it can cope with function applications and function definitions. In other words, the new evaluator simulates what happens in DrScheme when we enter an expression in the Interactions window after clicking Execute. To make things simple, we assume that all functions in the Definitions window consume one argument.
Exercise 17.7.1.
Extend the data definition of exercise 14.4.1 so that we can
represent the application of a userdefined function to an expression such
as (f (+ 1 1))
or (* 3 (g 2))
.
The application should be
represented as a structure with two fields. The first field contains the
name of the function, the second one the representation of the argument
expression.
Solution
A fullfledged evaluator can also deal with function definitions.
Exercise 17.7.2. Provide a structure definition and a data definition for definitions. Recall that a function definition has three essential attributes:
the function's name,
the parameter name, and
the function's body.
This suggests the introduction of a structure with three fields. The first two contain symbols, the last one a representation of the function's body, which is an expression.
Translate the following definitions into Scheme values:
(define (f x) (+ 3 x))
(define (g x) (* 3 x))
(define (h u) (f (* 2 u)))
(define (i v) (+ (* v v) (* v v)))
(define (k w) (* (h w) (i w)))
Make up more examples and translate them, too. Solution
Exercise 17.7.3.
Develop evaluatewithonedef
. The function consumes (the
representation of) a Scheme expression and (the representation of) a single
function definition, P
.
The remaining expressions from exercise 14.4.1 are evaluated
as before. For (the representation of) a variable, the function signals an
error. For an application of the function P
,
evaluatewithonedef
evaluates the argument;
substitutes the value of the argument for the function parameter in the function's body; and
evaluates the new expression via recursion. Here is a sketch:^{42}
(evaluatewithonedef (subst ... ... ...) afundef)
For all other function applications, evaluatewithonedef
signals
an error.
Solution
Exercise 17.7.4.
Develop the function evaluatewithdefs
. The function consumes (the
representation of) a Scheme expression and a list of (representations of)
function definitions, defs
. The function produces the number that
DrScheme would produce if we were to evaluate the actual Scheme expression
in the Interactions
window and if the Definitions
window
contained the actual definitions.
The remaining expressions from exercise 14.4.1 are evaluated
as before. For an application of the function P
,
evaluatewithdefs
evaluates the argument;
looks up the definition of P
in defs
;
substitutes the value of the argument for the function parameter in the function's body; and
evaluates the new expression via recursion.
Like DrScheme, evaluatewithdefs
signals an error for a function
application whose function name is not on the list and for (the
representation of) a variable.
Solution
Many of the functions we designed produce lists. When we test these functions, we must compare their results with the predicted value, both of which are lists. Comparing lists by hand is tedious and errorprone. Let's develop a function that consumes two lists of numbers and determines whether they are equal:
;;list=? : listofnumbers listofnumbers > boolean
;; to determine whetheralist
andanotherlist
;; contain the same numbers in the same order (define (list=? alist anotherlist) ...)
The purpose statement refines our general claim and reminds us that, for
example, shoppers may consider two lists equal if they contain the same
items, regardless of the order, but programmers are more specific and
include the order in the comparison. The contract and the purpose statement
also show that list=?
is a function that processes two complex
values, and indeed, it is an interesting case study.
Comparing two lists means looking at each item in both lists. This rules
out designing list=?
along the lines of replaceeolwith
in section 17.1. At first glance, there is also no
connection between the two lists, which suggests that we should use the
modified design recipe.
Let's start with the table:

cond
clauses in the template. Here are five tests:
(list=? empty empty)
(not (list=? empty (cons 1 empty)))
(not (list=? (cons 1 empty) empty))
(list=? (cons 1 (cons 2 (cons 3 empty))) (cons 1 (cons 2 (cons 3 empty))))
(not (list=? (cons 1 (cons 2 (cons 3 empty))) (cons 1 (cons 3 empty))))
The second and third show that list=?
must deal with its arguments
in a symmetric fashion. The last two show how list=?
can produce
true
and false
.
Three of the template's four cond
clauses contain selector
expressions and one contains natural recursions:
(define (list=? alist anotherlist) (cond [(and (empty? alist) (empty? anotherlist)) ...] [(and (cons? alist) (empty? anotherlist)) ... (first alist) ... (rest alist) ...] [(and (empty? alist) (cons? anotherlist)) ... (first anotherlist) ... (rest anotherlist) ...] [(and (cons? alist) (cons? anotherlist)) ... (first alist) ... (first anotherlist) ... ... (list=? (rest alist) (rest anotherlist)) ... ... (list=? alist (rest anotherlist)) ... ... (list=? (rest alist) anotherlist) ...]))
There are three natural recursions in the fourth clause because we can pair the two selector expressions and we can pair each parameter with one selector expression.
From the template to the complete definition is only a small step. Two
lists can contain the same items only if they are both empty or
cons
tructed. This immediately implies true
as the answer
for the first clause
and false
for the next two. In the
last clause, we have two numbers, the first of both lists, and three
natural recursions. We must compare the two numbers. Furthermore,
(list=? (rest alist) (rest anotherlist))
computes whether the
rest of the two lists are equal. The two lists are equal if, and only if,
both conditions hold, which means we must combine them with an
and
:
(define (list=? alist anotherlist) (cond [(and (empty? alist) (empty? anotherlist)) true] [(and (cons? alist) (empty? anotherlist)) false] [(and (empty? alist) (cons? anotherlist)) false] [(and (cons? alist) (cons? anotherlist)) (and (= (first alist) (first anotherlist)) (list=? (rest alist) (rest anotherlist)))]))
The other two natural recursions play no role.
Let us now take a second look at the connection between the two parameters. The first development suggests that the second parameter must have the same shape as the first one, if the two lists are to be equal. Put differently, we could develop the function based on the structure of the first parameter and check structure of the other one as needed.
The first parameter is a list of numbers, so we can reuse the template for listprocessing functions:
(define (list=? alist anotherlist) (cond [(empty? alist) ...] [(cons? alist) ... (first alist) ... (first anotherlist) ... ... (list=? (rest alist) (rest anotherlist)) ...]))
The only difference is that the second clause processes the second
parameter in the same way as the first one. This mimics the development of
hours>wages
in section 17.2.
Filling the gaps in this template is more difficult than for the first
development of list=?
. If alist
is empty
, the
answer depends on anotherlist
. As the examples show, the answer
is true
if, and only if, anotherlist
is also
empty
. Translated into Scheme this means that the answer in the
first cond
clause is (empty? anotherlist)
.
If alist
is not empty, the template suggests that we compute the
answer from
(first alist)
, the first number of alist
;
(first anotherlist)
, the first number on
anotherlist
; and
(list=? (rest alist) (rest anotherlist))
, which determines
whether the rest of the two lists are equal.
Given the purpose of the function and the examples, we now simply compare
(first alist)
and (first anotherlist)
and combine the
result with the natural recursion in an andexpression:
(and (= (first alist) (first anotherlist)) (list=? (rest alist) (rest anotherlist)))
While this step appears to be simple and straightforward, the result is an
improper definition. The purpose of spelling out the conditions in a
condexpression is to ensure that all selector expressions are
appropriate. Nothing in the specification of list=?
, however,
suggests that anotherlist
is cons
tructed if
alist
is cons
tructed.
We can overcome this problem with an additional condition:
(define (list=? alist anotherlist) (cond [(empty? alist) (empty? anotherlist)] [(cons? alist) (and (cons? anotherlist) (and (= (first alist) (first anotherlist)) (list=? (rest alist) (rest anotherlist))))]))
The additional condition is (cons? anotherlist)
, which means that
list=?
produces false
if (cons? alist)
is true
and (cons? anotherlist)
is empty. As the examples show, this is
the desired outcome.
In summary, list=?
shows that, on occasion, we can use more than one
design recipe to develop a function. The outcomes are different, though closely
related; indeed, we could prove that the two always produce the same results for
the same inputs. Also, the second development benefited from the first one.
Exercise 17.8.1.
Test both versions of list=?
.
Solution
Exercise 17.8.2.
Simplify the first version of list=?
. That is, merge neighboring
cond
clauses with the same result by combining their conditions in
an orexpression; switch cond
clauses as needed; and use else
in the last clause of the final version.
Solution
Exercise 17.8.3.
Develop symlist=?
. The function determines whether two lists of
symbols are equal.
Solution
Exercise 17.8.4.
Develop containssamenumbers
. The function determines whether
two lists of numbers contain the same numbers, regardless of the ordering. Thus,
for example,
(containssamenumbers (list 1 2 3) (list 3 2 1))
evaluates to true
.
Solution
Exercise 17.8.5. The class of numbers, symbols, and booleans are sometimes called atoms:^{43}
a number
a boolean
a symbol
Develop the function listequal?
, which consumes two
lists of atoms and determines whether they are
equal.
Solution
A comparison between the two versions of list=?
suggests that the
second one is easier to understand than the first. It says that two compound
values are equal if the second is made from the same constructor and the
components are equal. In general, this idea is a good guide for the development
of other equality functions.
Let's look at an equality function for simple Web pages to confirm this conjecture:
;;web=? : webpage webpage > boolean
;; to determine whetherawp
andanotherwp
have the same tree shape ;; and contain the same symbols in the same order (define (web=? awp anotherwp) ...)
Recall the data definition for simple Web pages:
A Webpage (short: WP) is either
empty
;
(cons s wp)
where s
is a symbol and wp
is a Web page; or
(cons ewp wp)
where both ewp
and wp
are Web pages.
The data definition has three clauses, which means that if we were to
develop web=?
with the modified design recipe, we would need to study
nine cases. By using the insight gained from the development of list=?
instead, we can start from the plain template for Web sites:
(define (web=? awp anotherwp) (cond [(empty? awp) ...] [(symbol? (first awp)) ... (first awp) ... (first anotherwp) ... ... (web=? (rest awp) (rest anotherwp)) ...] [else ... (web=? (first awp) (first anotherwp)) ... ... (web=? (rest awp) (rest anotherwp)) ...]))
In the second cond
clause, we follow the example of
hours>wages
and list=?
again. That is, we say that
anotherwp
must have the same shape as awp
if it is to
be equal and process the two pages in an analogous manner. The reasoning
for the third clause is similar.
As we refine this template into a full definition now, we must again add
conditions on anotherwp
to ensure that the selector expressions
are justified:
(define (web=? awp anotherwp) (cond [(empty? awp) (empty? anotherwp)] [(symbol? (first awp)) (and (and (cons? anotherwp) (symbol? (first anotherwp))) (and (symbol=? (first awp) (first anotherwp)) (web=? (rest awp) (rest anotherwp))))] [else (and (and (cons? anotherwp) (list? (first anotherwp))) (and (web=? (first awp) (first anotherwp)) (web=? (rest awp) (rest anotherwp))))]))
In particular, we must ensure in the second and third clause that
anotherwp
is a cons
tructed list and that the first item
is a symbol or a list, respectively. Otherwise the function is analogous to
list=?
and works in the same way.
Exercise 17.8.6.
Draw the table based on the data definition for simple Web pages. Develop
(at least) one example for each of the nine cases. Test web=?
with
these examples.
Solution
Exercise 17.8.7.
Develop the function posn=?
, which consumes two binary
posn
structures and determines whether they are
equal.
Solution
Exercise 17.8.8.
Develop the function tree=?
, which consumes two binary trees and
determines whether they are equal.
Solution
Exercise 17.8.9. Consider the following two, mutually recursive data definitions:
empty
(cons s sl)
where s
is a Sexpr
and
sl
is a Slist
.
A Sexpr is either
a number
a boolean
a symbol
a Slist
Develop the function Slist=?
, which consumes two
Slist
s and determines whether they are equal. Like lists of
numbers, two Slist
s are equal if they contain the same item at
analogous positions.
Solution
Now that we have explored the idea of equality of values, we can return to
the original motivation of the section: testing functions.
Suppose we wish
to test hours>wages
from section 17.2:
(hours>wages (cons 5.65 (cons 8.75 empty)) (cons 40 (cons 30 empty))) = (cons 226.0 (cons 262.5 empty))
If we just type in the application into Interactions window or add it to the bottom of the Definitions window, we must compare the result and the predicted value by inspection. For short lists, like the ones above, this is feasible; for long lists, deep Web pages, or other large compound data, manual inspection is errorprone.
Using equality functions like list=?
, we can greatly reduce the need
for manual inspection of test results. In our running example, we can add the
expression
(list=? (hours>wages (cons 5.65 (cons 8.75 empty)) (cons 40 (cons 30 empty))) (cons 226.0 (cons 262.5 empty)))
to the bottom of the Definitions
window. When we click the
Execute button now, we just need to make sure that all test cases
produce true
as their results are displayed in the
Interactions
window.

Indeed, we can go even further. We can write a test function like the one
in figure 50. The class of testresult
s
consists of the value true
and lists of four items: the string
"bad test result:"
followed by three lists. Using this new
auxiliary function, we can test hours>wages
as follows:
(testhours>wages (cons 5.65 (cons 8.75 empty)) (cons 40 (cons 30 empty)) (cons 226.0 (cons 262.5 empty)))
If something goes wrong with the test, the fouritem list will stand out and specify precisely which test case failed.
Testing with equal?: The designers of Scheme anticipated the need of a general equality procedure and provide:
;; equal? : anyvalue anyvalue > boolean
;; to determine whether two values are structurally equivalent
;; and contain the same atomic values in analogous positions
When equal?
is applied to two lists, it compares them in the same
manner as list=?
; when it encounters a pair of structures, it
compares their corresponding fields, if they are the same kind of
structures; and when it consumes a pair of atomic values, it compares them
with =
, symbol=?
, or boolean=?
, whatever is
appropriate.
Guideline on Testing 
Use 
Unordered Lists: On some occasions, we use lists even though the
ordering of the items doesn't play a role. For those cases, it is important
to have functions such as containssamenumbers
(see
exercise 17.8.4) if we wish to determine whether the result of
some function application contains the proper items.
Exercise 17.8.10.
Define a test function for replaceeolwith
from
section 17.1 using equal?
and formulate the
examples as test cases using this function.
Solution
Exercise 17.8.11.
Define the function testlistpick
, which manages test cases for
the listpick
function from
section 17.3. Formulate the examples from the section
as test cases using testlistpick
.
Solution
Exercise 17.8.12.
Define testinterpret
, which tests interpretwithdefs
from exercise 17.7.4, using equal?
. Reformulate
the test cases using this function.
Solution