6.1 KiB
Conditionals
Most functions used for branching, such as <
and string?
, produce
either #t
or #f
. Racket’s branching forms, however, treat any value
other than #f
as true. We say a true value to mean any value other
than #f
.
This convention for “true value” meshes well with protocols where #f
can serve as failure or to indicate that an optional value is not
supplied. (Beware of overusing this trick, and remember that an
exception is usually a better mechanism to report failure.)
For example, the member
function serves double duty; it can be used to
find the tail of a list that starts with a particular item, or it can be
used to simply check whether an item is present in a list:
> (member "Groucho" '("Harpo" "Zeppo"))
#f
> (member "Groucho" '("Harpo" "Groucho" "Zeppo"))
'("Groucho" "Zeppo")
> (if (member "Groucho" '("Harpo" "Zeppo"))
'yep
'nope)
'nope
> (if (member "Groucho" '("Harpo" "Groucho" "Zeppo"))
'yep
'nope)
'yep
1. Simple Branching: if
+[missing] in [missing] also documents
if
.
In an if
form,
(if test-expr then-expr else-expr)
the test-expr
is always evaluated. If it produces any value other than
#f
, then then-expr
is evaluated. Otherwise, else-expr
is
evaluated.
An if
form must have both a then-expr
and an else-expr
; the latter
is not optional. To perform or skip
side-effects based on a
test-expr
, use when
or unless
, which we describe later in
missing
2. Combining Tests: and
and or
+[missing] in [missing] also documents
and
andor
.
Racket’s and
and or
are syntactic forms, rather than functions.
Unlike a function, the and
and or
forms can skip evaluation of later
expressions if an earlier one determines the answer.
(and expr ...)
An and
form produces #f
if any of its expr
s produces #f
.
Otherwise, it produces the value of its last expr
. As a special case,
(and)
produces #t
.
(or expr ...)
The or
form produces #f
if all of its expr
s produce #f
.
Otherwise, it produces the first non-#f
value from its expr
s. As a
special case, (or)
produces #f
.
Examples:
> (define (got-milk? lst)
(and (not (null? lst))
(or (eq? 'milk (car lst))
(got-milk? (cdr lst))))) ; recurs only if needed
> (got-milk? '(apple banana))
#f
> (got-milk? '(apple milk banana))
#t
If evaluation reaches the last expr
of an and
or or
form, then the
expr
’s value directly determines the and
or or
result. Therefore,
the last expr
is in tail position, which means that the above
got-milk?
function runs in constant space.
+[missing] introduces tail calls and tail positions.
3. Chaining Tests: cond
The cond
form chains a series of tests to select a result expression.
To a first approximation, the syntax of cond
is as follows:
+[missing] in [missing] also documents
cond
.
(cond [test-expr body ...+]
...)
Each test-expr
is evaluated in order. If it produces #f
, the
corresponding body
s are ignored, and evaluation proceeds to the next
test-expr
. As soon as a test-expr
produces a true value, its body
s
are evaluated to produce the result for the cond
form, and no further
test-expr
s are evaluated.
The last test-expr
in a cond
can be replaced by else
. In terms of
evaluation, else
serves as a synonym for #t
, but it clarifies that
the last clause is meant to catch all remaining cases. If else
is not
used, then it is possible that no test-expr
s produce a true value; in
that case, the result of the cond
expression is #<void>
.
Examples:
> (cond
[(= 2 3) (error "wrong!")]
[(= 2 2) 'ok])
'ok
> (cond
[(= 2 3) (error "wrong!")])
> (cond
[(= 2 3) (error "wrong!")]
[else 'ok])
'ok
(define (got-milk? lst)
(cond
[(null? lst) #f]
[(eq? 'milk (car lst)) #t]
[else (got-milk? (cdr lst))]))
> (got-milk? '(apple banana))
#f
> (got-milk? '(apple milk banana))
#t
The full syntax of cond
includes two more kinds of clauses:
(cond cond-clause ...)
cond-clause = [test-expr then-body ...+]
| [else then-body ...+]
| [test-expr => proc-expr]
| [test-expr]
The =>
variant captures the true result of its test-expr
and passes
it to the result of the proc-expr
, which must be a function of one
argument.
Examples:
> (define (after-groucho lst)
(cond
[(member "Groucho" lst) => cdr]
[else (error "not there")]))
> (after-groucho '("Harpo" "Groucho" "Zeppo"))
'("Zeppo")
> (after-groucho '("Harpo" "Zeppo"))
not there
A clause that includes only a test-expr
is rarely used. It captures
the true result of the test-expr
, and simply returns the result for
the whole cond
expression.