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.
Truth values in Scheme
[edit | edit source]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
[edit | edit source]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
[edit | edit source]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))
Exercises |
---|
|
cond
[edit | edit source]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
[edit | edit source]- ↑ R7RS § 6.3
- ↑ While
and
andor
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. - ↑ 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 ofx
ifb
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. - ↑ 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.