racket/collects/scribblings/guide/cond.scrbl
2010-04-27 14:09:51 -06:00

169 lines
5.6 KiB
Racket

#lang scribble/doc
@(require scribble/manual
scribble/eval
"guide-utils.ss")
@title[#:tag "conditionals"]{Conditionals}
Most functions used for branching, such as @racket[<] and
@racket[string?], produce either @racket[#t] or @racket[#f]. Racket's
branching forms, however, treat any value other than @racket[#f] as
true. We say a @defterm{true value} to mean any value other than
@racket[#f].
This convention for ``true value'' meshes well with protocols where
@racket[#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 @racket[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:
@interaction[
(member "Groucho" '("Harpo" "Zeppo"))
(member "Groucho" '("Harpo" "Groucho" "Zeppo"))
(if (member "Groucho" '("Harpo" "Zeppo"))
'yep
'nope)
(if (member "Groucho" '("Harpo" "Groucho" "Zeppo"))
'yep
'nope)
]
@;------------------------------------------------------------------------
@section{Simple Branching: @racket[if]}
@refalso["if"]{@racket[if]}
In an @racket[if] form,
@specform[(if test-expr then-expr else-expr)]
the @racket[_test-expr] is always evaluated. If it produces any value
other than @racket[#f], then @racket[_then-expr] is
evaluated. Otherwise, @racket[_else-expr] is evaluated.
An @racket[if] form must have both a @racket[_then-expr] and an
@racket[_else-expr]; the latter is not optional. To perform (or skip)
side-effects based on a @racket[_test-expr], use @racket[when] or
@racket[unless], which we describe later in @secref["begin"].
@;------------------------------------------------------------------------
@section[#:tag "and+or"]{Combining Tests: @racket[and] and @racket[or]}
@refalso["if"]{@racket[and] and @racket[or]}
Racket's @racket[and] and @racket[or] are syntactic forms, rather than
functions. Unlike a function, the @racket[and] and @racket[or] forms
can skip evaluation of later expressions if an earlier one determines
the answer.
@specform[(and expr ...)]
An @racket[and] form produces @racket[#f] if any of its @racket[_expr]s
produces @racket[#f]. Otherwise, it produces the value of its last
@racket[_expr]. As a special case, @racket[(and)] produces
@racket[#t].
@specform[(or expr ...)]
The @racket[or] form produces @racket[#f] if all of its
@racket[_expr]s produce @racket[#f]. Otherwise, it produces the first
non-@racket[#f] value from its @racket[expr]s. As a special case,
@racket[(or)] produces @racket[#f].
@examples[
(code:line
(define (got-milk? lst)
(and (not (null? lst))
(or (eq? 'milk (car lst))
(got-milk? (cdr lst))))) (code:comment @#,t{recurs only if needed}))
(got-milk? '(apple banana))
(got-milk? '(apple milk banana))
]
If evaluation reaches the last @racket[_expr] of an @racket[and] or
@racket[or] form, then the @racket[_expr]'s value directly determines
the @racket[and] or @racket[or] result. Therefore, the last
@racket[_expr] is in tail position, which means that the above
@racket[got-milk?] function runs in constant space.
@guideother{@secref["tail-recursion"] introduces tail calls and tail positions.}
@;------------------------------------------------------------------------
@section[#:tag "cond"]{Chaining Tests: @racket[cond]}
The @racket[cond] form chains a series of tests to select a result
expression. To a first approximation, the syntax of @racket[cond] is
as follows:
@refalso["if"]{@racket[cond]}
@specform[(cond [test-expr expr ...+]
...)]
Each @racket[_test-expr] is evaluated in order. If it produces
@racket[#f], the corresponding @racket[_expr]s are ignored, and
evaluation proceeds to the next @racket[_test-expr]. As soon as a
@racket[_test-expr] produces a true value, its @racket[_text-expr]s
are evaluated to produce the result for the @racket[cond] form, and no
further @racket[_test-expr]s are evaluated.
The last @racket[_test-expr] in a @racket[cond] can be replaced by
@racket[else]. In terms of evaluation, @racket[else] serves as a
synonym for @racket[#t], but it clarifies that the last clause is
meant to catch all remaining cases. If @racket[else] is not used, then
it is possible that no @racket[_test-expr]s produce a true value; in
that case, the result of the @racket[cond] expression is
@|void-const|.
@examples[
(cond
[(= 2 3) (error "wrong!")]
[(= 2 2) 'ok])
(cond
[(= 2 3) (error "wrong!")])
(cond
[(= 2 3) (error "wrong!")]
[else 'ok])
]
@def+int[
(define (got-milk? lst)
(cond
[(null? lst) #f]
[(eq? 'milk (car lst)) #t]
[else (got-milk? (cdr lst))]))
(got-milk? '(apple banana))
(got-milk? '(apple milk banana))
]
The full syntax of @racket[cond] includes two more kinds of clauses:
@specform/subs[#:literals (else =>)
(cond cond-clause ...)
([cond-clause [test-expr then-expr ...+]
[else then-expr ...+]
[test-expr => proc-expr]
[test-expr]])]
The @racket[=>] variant captures the true result of its
@racket[_test-expr] and passes it to the result of the
@racket[_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"))
(after-groucho '("Harpo" "Zeppo"))
]
A clause that includes only a @racket[_test-expr] is rarely used. It
captures the true result of the @racket[_test-expr], and simply
returns the result for the whole @racket[cond] expression.