Conditional evaluation

Conditional expressions

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.


Exercise 1

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.


Exercise 2

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.


Exercise 3

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.


Exercise 4

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.


Control structures

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.


Exercise 5

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.


Exercise 6

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).


Exercise 7

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.


Additional exercises

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

John David Stone (stone@math.grin.edu)