4.7 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
4.7.1 Simple Branching: if
Conditionals: if, cond, and, and or in The Racket Reference 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 Sequencing.
4.7.2 Combining Tests: and and or
Conditionals: if, cond, and, and or in The Racket Reference also documents and and or.
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 exprs 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 exprs produce #f. Otherwise, it produces the first non-#f value from its exprs. As a special case, (or) produces #f.
> (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.
Tail Recursion introduces tail calls and tail positions.
4.7.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:
Conditionals: if, cond, and, and or in The Racket Reference also documents cond.
(cond [test-expr body ...+] ...)
Each test-expr is evaluated in order. If it produces #f, the corresponding bodys are ignored, and evaluation proceeds to the next test-expr. As soon as a test-expr produces a true value, the associated bodys are evaluated to produce the result for the cond form, and no further test-exprs 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-exprs produce a true value; in that case, the result of the cond expression is #<void>.
> (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.
> (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.