Scheme Programming/Conditionals

In this section we introduce Scheme's conditional expressions. These forms allow us to make choices--something that's been lacking from the simple programs we've seen so far.

Contents

Truth values in Scheme

We've already seen the boolean values #t ("true") and #f ("false") in previous examples, as the values of expressions like ( < 3 5) . Booleans have few procedures [ 1 ] ; the most commonly seen is probably not , which negates its argument:

> (not #t) #f > (not #f) #t 

In Scheme, booleans are not the only objects with truth values; the convention of Scheme is to consider #f to be false and every other Scheme value to be true. A very common idiom in Scheme is for procedures to return some useful value (which is rarely #t ) on success, and #f otherwise. While our procedures will stick to the practice of returning #t , it's important to be aware of this idiom.

and and or

The and and or forms [ 2 ] let us manipulate truth values in familiar ways:

> (and #t #f) #f > (or #f (not #f)) #t > (and (> 3 2) #t) #t > (or ( 3 2) (zero? (- 10 3))) #f 

In this basic usage, these forms give us the Boolean AND and OR, respectively, of their two arguments (called tests). This is often perfectly sufficient. The and and or forms, however, are a bit more flexible. For one thing, they can take any number of tests:

> (and (> 3 2) (>= 5 4) (> 10 9)) #t > (or ( 2 3) (zero? (- 10 3)) #f (> 3 5)) #t 

An and expression is thus true if all the tests are true, and an or expression is true if at least one of the tests is true.

As mentioned above, we can also have tests that evaluate to things other than #t and #f :

> (and (> 3 2) 5) 5 ; ? 

Since the first test expression evaluates to #t and the second to 5 (which is not #f , the only Scheme value considered false), this and expression must be true--but why does it evaluate to 5 ? The Scheme convention is that and , when all the tests are true, returns the value of the last test; or returns the value of the first true test. In this way, we can return values more useful than #t without affecting the truth values of our expressions.

Simple Conditionals: if

The simplest conditional in Scheme is the if form. Here are some examples:

> (if #t 1 0) 1 > (if (>= 5 8) 3 (+ 7 2)) 9 

Like and and or , if is a special form with its own syntax. Here's the general form:

(if test consequent alternative) 

The first component of an if expression is a test expression, which is evaluated first. If its value is true, the value of the second component expression (the consequent) is returned. If the test evaluates to false, on the other hand, we get the value of the third component (the alternative). [ 3 ] Let's see how this works in the above examples. In the first, the test expression is just #t , so the value of the if form is the value of the consequent expression, 1 . To evaluate the second example, we first consider the test, (>= 5 8) . This evaluates to #f , so we evaluate the alternative (+ 7 2) and return its value as the value of the entire if expression.

Using if , we can write some useful procedures. A simple example:

(define (absolute-value n) (if (positive? n) n (- n))) ; with one argument, - gives the additive inverse 

Using and and or forms in our test expressions, we can combine several tests:

(define (days-in-year y) (if (or (= (floor-remainder y 400) 0) (and (= (floor-remainder y 4) 0) (not (= (floor-remainder y 100) 0)))) 366 ; leap year 365)) 

cond

We can write a great number of useful Scheme programs with if . In fact, by chaining together if s, we can write any conditional expression we want. Unfortunately, these expressions get complicated very quickly. When nesting if s, it can be difficult to keep track of all the clauses and it may be necessary to write results several times. (For example, in the last exercise, how many cases are there in which 366 is the answer?) In these cases, we'd like something more convenient.

For this reason, Scheme provides cond , an extremely flexible conditional form which allows multi-way choices to be expressed easily. Here's a short example:

(cond ((> 5 6) 7) ((= 3 (+ 1 2)) 4) (else 8)) 

In its simplest form, cond takes a number of clauses, each of which consists of a test and a result expression. To evaluate a cond expression, we evaluate the tests of each clause in turn; if one evaluates to true, cond returns the value of that clause's result expression, skipping any remaining clauses. A test which is just the word else is always true, and thus a cond will always "choose" the result of a clause with else as its test. Typically, the last clause of a cond is an "else clause".

A simplified version of the form of a cond expression is:

(cond clause1 clause2 . ) 

where each clause is of the form (test result) or (else result) . [ 4 ]

Let's step through the evaluation of a simple cond expression.

(cond ((>= 5 8) 3) (else (+ 7 2))) 

To evaluate this expression, we look at the first clause, ((>= 5 8) 3) . The test expression of this clause is (>= 5 8) , so we evaluate that; since its value is #f , we skip this clause and go on to the next, (else (+ 7 2)) . The test of this clause is else , which is "always true". We evaluate this clause's result expression, (+ 7 2) , and return its value, 9 , as the value of the whole cond expression. Since this cond expression returns 3 when the first clause's test is true and 9 otherwise, it's precisely equivalent to the following if form:

(if (>= 5 8) 3 (+ 7 2)) 

Thus, we can replace any if expression with an equivalent cond . In fact, cond is so general that we can use it in place of any other conditional form.

Notes

  1. ↑ R7RS § 6.3
  2. ↑ While and and or expressions look very much like applications, these are not procedures but special forms. Scheme has many special forms, each of which has its own syntax and semantics. The conditional forms described in this section are some of the most commonly-seen special forms.
  3. ↑ The third expression is optional. An expression of the form (if b x) , sometimes called a "one-armed if", is legal Scheme. It gives the value of x if b is true, and an unspecified value (which can be anything) otherwise. We'll see how this can be used in the "Advanced Scheme" chapter; for now, we'll always include the alternative.
  4. ↑ As we noted above, this is simplified. A clause can actually take any number of result expressions, giving us the general form (test expression1 expression2 . ) . If test is true, each of these expressions is evaluated in turn, and the value of the last is returned. With only one result expression, this is equivalent to what we've described above. cond also provides a notation called "cond arrow", which we'll discuss in the Advanced Scheme chapter.