cond
In most procedural programming languages, you can write a sequence of
if
tests using an extended version of if
, something like
this:
if test1 then action1(); else if test2 then action2(); else if test3 then action3(); else action4();
Scheme has a similar construct, a special form called cond
. The
above example might be written in Scheme as
(cond (test1 (action1)) (test2 (action2)) (test3 (action3)) (else (action4)))
Notice that each test-and-action pair is enclosed in parentheses. In
this example, test1
is just a variable reference, not a procedure
call, i.e., we're testing to see if the value of the variable test1
is #f
; if not, we'll execute (action1)
, i.e., call the procedure
action1
. If it is false, control "falls through" to the next
test, and keeps going until one of the tests evaluates to a true
value (anything but #f
).
Notice that we indent the actions corresponding to a test by one character. This way the actions are lined up directly under the tests, rather than under the opening parenthesis that groups them together.
Also notice that the cond
construct is an expression like any
other expressions, and therefore returns a value. The action
s
above are not necessarily procedure calls as shown above, but can be
arbitrary expressions. When a test
returns a true value, the
corresponding value of the action
expression will be the value of
the entire cond
expression.
The else
clause of a cond
is optional; if present,
that branch will be taken "by default"---if none of the other
tests evaluates to a true value, the else
branch will
be taken.
We don't really need the else clause, because we could get the same
effect by using a test expression that always evaluates to a true
value. For instance, we could use the literal #t
, the true
boolean, because it's always true.
(cond (test1 (action1)) (test2 (action2)) (test3 (action3)) (#t ; literal #t is always true, so (action4))) ; this branch is taken if we get this far
The code above is equivalent to a nested set of if
expressions:
(if test1 (action1) (if test2 (action2) (if test3 (action3) (if #t (action4)))))
Like an if
, a cond
returns the value of whatever "branch"
it executes. If test1
is true, for example, the above cond
will return the value returned from the procedure call (action1)
.
Remember that each branch of an if
is a single expression;
if you want to execute more than one expression in a branch,
you have to wrap the expressions in a begin
. With cond
,
no begin
is necessary. You can follow a test expression with more
than one action expression, and Scheme will evaluate all of them,
in order, and return the value of the last one, just like a begin
or a procedure body.
Suppose we want to modify the above cond
example so that it
prints out the branch it's taking, as well as evaluating the action
expression and returning its value. We can do this:
(cond (test1 (display "taking first branch") (action1)) (test2 (display "taking second branch") (action2)) (test3 (display "taking third branch") (action3)) (else (display "taking fourth (default) branch") (action4)))
This cond
will return the same value as the original, because
it always returns the value of the last expression in a branch. As
it executes, however, it also displays what it's doing. We can use
the cond
both for value and for effect.
Be particularly careful about parentheses with cond
. You
must enclose each branch with a pair of parentheses around the test
expression and the corresponding sequence of action expressions. If
you want to call a procedure in any of those expressions, you must
also put parentheses around the procedure call. In the above
example, if we wanted the first test to be a call to a procedure
test1
---rather than just fetching the value of the variable
test1
---we'd write
(cond ((test1) (display "taking first branch") (action1)) ...)
instead of
(cond (test1 (display "taking first branch") (action1)) ...)
(Note the indenting here. We usually line up a test and the corresponding sequence of actions vertically, whether or not the expression starts with a parenthesis. That is, we indent one space past the opening parenthesis of the pair of parentheses that goes around the group consisting of a test and a sequence of actions.)
The "extra" parentheses are necessary so that cond
can
tell which action sequences are grouped with which tests.
Don't be afraid to use cond
for conditionals with only one
or two branches. cond
is often more convenient than if
because it can execute a sequence of expressions, instead of just
one. It's not uncommon to see things like this:
... (cond ((foo) (bar) (baz))) ...
Don't be confused by this example--there's only one branch to this cond
,
like a one-branch if
. We could have written it
... (if (foo) (begin (bar) (baz))) ...
It's just more convenient to use cond
so that we can call bar
before calling baz
and returning
its result, without explicitly writing a begin
expression
to sequence them.
We say that cond
is syntactic sugar for nested if
s
with begin
s around the branches. There's nothing we can do
with cond
that we can't do straightforwardly with if
and begin
---cond
just gives us a "sweetened" syntax,
i.e., one that's more convenient.
Most of the special forms in Scheme are like this--they're just a convenient way of writing things that you could write using more basic special forms. (There are only five "core" special forms that are really necessary, and the others are equivalent to combinations of those special forms.)