4.7 条件分支
大部分用于条件分支的函数(如 < 何 string?)会产生 #t 或 #f。然而 Racket 的分支形式会将除 #f 之外的任何值都视为真。我们用真值表示除 #f 以外的任何值。
这种对“真值”的约定能够很好地与用 #f 表示失败或可选的值不被支持的协议相融合。 (请注意不要滥用此技巧,异常通常是更好的失败报告机制。)
例如,member 函数有两种,它既可用于查找以某个特定项开始的列表的尾部, 也可用于简单地检查某项是否在列表中:
> (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 简单分支:if
The Racket Reference的Conditionals: if, cond, and, and or一节中也提供了if的文档。
在 if 形式
(if test-expr then-expr else-expr)
中,test-expr 总是会被求值。如果它产生了除 #f 以外的任何值, 那么 then-expr 就会被求值。否则 else-expr 就会被求值。
if 必须同时有 then-expr 和 else-expr, 后者并不是可选的。要处理(或跳过)基于 test-expr 的副作用,请使用 when 或 unless,我们会在之后的 序列 一节中讨论它。
4.7.2 组合测试:and 与 or
The Racket Reference的Conditionals: if, cond, and, and or一节中也提供了and 与 or的文档。
Racket 的 and 与 or 为语法形式而非函数。与函数不同, and 与 or 形式在前一个表达式确定结果后可跳过后面表达式的求值。
(and expr ...)
and 形式会在其任意一个 expr 产生 #f 时产生 #f。否则,它会产生其最后一个 expr 的值。作为特例, (and) 会产生 #t。
(or expr ...)
or 形式会在其所有 expr 产生 #f 时产生 #f。否则,它会产生其 expr 中的第一个非 #f 的值。 作为特例,(or) 会产生 #f。
> (define (got-milk? lst) (and (not (null? lst)) (or (eq? 'milk (car lst)) (got-milk? (cdr lst))))) ; 仅在需要时递归 > (got-milk? '(apple banana)) #f
> (got-milk? '(apple milk banana)) #t
若求值抵达了 and 或 or 形式的最后一个 expr,那么 expr 的值会直接确定 and 或 or 的结果。因此,最后一个 expr 在尾部,这意味着 got-milk? 函数的执行只需要常量空间。
尾递归 介绍了尾调用和尾部位置。
4.7.3 链式测试:cond
cond 形式将一系列测试作为测试链来从中选取一个结果表达式。 cond 的语法大致如下:
The Racket Reference的Conditionals: if, cond, and, and or一节中也提供了cond的文档。
(cond [test-expr body ...+] ...)
每个 test-expr 都会按顺序求值。若它产生 #f,那么其对应的 body 就会被忽略,然后对下一个 test-expr 进行求值。只要 test-expr 产生了真值,其 body 就会被求值并产生 cond 的结果,之后的 further test-expr 则不再被求值。
cond 中的最后一个 test-expr 可替换为 else。 从求值的角度来说,else 就是 #t 的同义词, 不过它能清除地说明最后一个从句表示捕获所有剩余的情况。若 else 未被使用,那么 test-expr 可能不会产生任何真值。此时, cond 表达式的结果为 #<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
cond 的完整语法还包括另外两种从句:
(cond cond-clause ...)
cond-clause = [test-expr then-body ...+] | [else then-body ...+] | [test-expr => proc-expr] | [test-expr]
=> 的变体会捕获其 test-expr 为真的结果并将其传给 proc-expr 的结果中,它必须为单参数函数。
> (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
只包含一个 test-expr 的从句很少被使用。它会捕获 test-expr 为真的结果,并简单地将其作为整个 cond 表达式的结果返回。