When Scheme encounters a procedure call, it looks at all of the subexpressions within the parentheses and tries to evaluate each one (unless evaluation is blocked by a single quotation mark). Sometimes, however, one would like Scheme to exercise some discretion -- to select just one subexpression for evaluation from two or more alternatives. In such cases, one uses a conditional expression -- one that tests whether some condition is met and selects the subexpression to evaluate on the basis of the outcome of that test.
For instance, a Scheme programmer creating a procedure definition might
need to divide one number, dividend, by another,
divisor, without knowing whether divisor might be
zero. Since Scheme will halt and write out an error message if the
/ procedure is called with zero as its second argument, the
programmer should anticipate this problem and avoid calling /
if divisor is zero. Here is a conditional expression that she
can use to accomplish this:
(if (zero? divisor)
'undefined
(/ dividend divisor))
Here if is a keyword, not the name of a procedure. When
Scheme encounters an if-expression, it initially evaluates
only the first subexpression after the if, which is called the
test. If the value of the test is the Boolean value
#f, then the second subexpression is skipped -- left
unevaluated -- and the third expression (the alternate) is
evaluated; its value becomes the value of the entire
if-expression. If the value of the test is anything other
than #f, then the second subexpression (the
consequent) is evaluated and the alternate is skipped.
So, in the example, a non-zero value of divisor makes the test
(zero? divisor) false, and the division is allowed to proceed;
the quotient becomes the value of the entire expression. On the other
hand, when divisor is zero, the value of the test is
#t, so the consequent 'undefined is evaluated
instead; the value of the if-expression in that case is the
symbol undefined.
Define a Scheme procedure neighbor that takes one argument, an
integer, and returns the next higher integer if its argument is even, the
next lower integer if its argument is odd.
Define a Scheme procedure affirm-if-larger that takes two
arguments, both real numbers, and returns the string "Yes!" if
the first is greater than the second, the string "No ..." if
it is not.
When there are more than two alternatives, it is often more convenient to
set them out using a cond-expression. Like if,
cond is a keyword. It is followed by zero or more lists of
expressions called cond-clauses. The first expression within
a cond-clause is a test; when the value of such a test is
found to be #f, the subexpressions that follow the test are
skipped and Scheme proceeds to the test at the beginning of the next
cond-clause. But when a test is evaluated and the value turns
out to be anything other than #f, each of the remaining
expressions in the same cond-clause is evaluated in turn, and
the value of the last one becomes the value of the entire
cond-expression. (All the subsequent
cond-clauses are skipped in this case.)
In other words, when Scheme encounters a cond-expression, it
works its way through the cond-clauses, evaluating the test at
the beginning of each one, until it reaches a test that ``succeeds'' (one
that does not have #f as its value). It then makes a
ninety-degree turn and evaluates the other expressions in the selected
cond-clause, retaining the value of the last expression.
If all of the tests in a cond-expression are found to be
false, the value of the cond-expression is unspecified (that
is, it might be anything!). To prevent the surprising results that
usually ensue when one computes with unspecified values, good programmers
customarily end every cond-expression with a
cond-clause in which the keyword else appears in
place of a test. Scheme treats such a cond-clause as if it
had a test that always succeeded; if it is reached, the subexpressions
following else will be evaluated.
Define a Scheme procedure report-victory that takes one
argument, a real number, and returns the string "I won!" if
that number is positive, the string "You won!" if it is
negative, and the string "It's a tie!" if it is zero.
Define a Scheme procedure classify-by-age that takes one
argument, a non-negative integer, and returns the symbol
infant if the argument is 0 or 1, the symbol
child if the argument is less than or equal to 12, the symbol
adolescent if the argument is less than or equal to 18, the
symbol adult if the argument is less than 65, and the symbol
senior in any other case. Test your procedure by calling it
with a representative age within each classification.
If-expressions and cond-expressions are two
examples of control structures in Scheme -- expressions that
control the order in which their subexpressions are evaluated or the
circumstances under which they are evaluated. The simplest such structure
is the begin-expression, which specifies that its
subexpressions be evaluated in order:
> (begin
(display "Hello,")
(display " ")
(display "world!")
(newline))
Hello, world!
The display procedure takes one argument and causes it to be
printed out (even if it is called during a non-interactive program load, as
when you give the name of a file to be loaded on the command line when
starting up Chez Scheme). Successive calls to display
continue to print arguments onto the same output line until the
newline procedure is called; newline, which takes
no arguments, inserts a line break so that subsequent printing will occur
at the beginning of the next output line.
In the code shown above, the begin-expression imposes an order
on the four procedure calls, so that the various pieces of the output are
generated in the correct order. (This contrasts with Scheme's approach to
preparing the arguments within a single procedure call: The argument
expressions can be evaluated in any order that Scheme happens to find
convenient.)
Scheme provides two other control structures that do conditional
evaluation: the and-expression and the
or-expression. In an and-expression, the
expressions that follow the keyword and are evaluated in
succession until one is found to have the value #f (in which
case the rest of the expressions are skipped and the #f
becomes the value of the entire and-expression) or all of the
expressions have been evaluated (in which case the value of the last
expression becomes the value of the and-expression). This
gives the programmer a way to combine several tests into one that will
succeed only if all of its parts succeed.
In an or-expression, the expressions that follow the keyword
or are evaluated in succession until one is found to have a
value other than#f (in which case the rest of the expressions
are skipped and this value becomes the value of the entire
or-expression) or all of the expressions have been evaluated
(the value of the or-expression is #f). This
gives the programmer a way to combine several tests into one that will
succeed if any of its parts succeeds.
Define a Scheme predicate symbol-or-number? that takes one
argument and returns #t if the argument is either a symbol or
a number, #f if it is neither.
Define a Scheme predicate between? that takes three arguments,
all real numbers, and determines whether the second one is intermediate in
value between the first and third (returning #t if it is,
#f if it is not).
Three line segments can be assembled into a triangle if, and only
if, the length of each of them is less than the sum of the lengths of the
other two. Define a Scheme predicate triangle? that takes
three arguments, all positive real numbers, and determines whether line
segments of those three lengths (assumed to be measured in the same units)
could be assembled into a triangle.
Exercises 2.6 through 2.9 on pages 45 and 46 of the text also deal with conditional expressions. Try them.
This document is available on the World Wide Web as
http://www.math.grin.edu/courses/Scheme/spring-1998/conditional-evaluation.html
created September 7, 1997
last revised June 21, 1998