4.7 Conditionals
大多数用于分支的函数,如 < 和 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 Simple Branching: if
(part ("(lib scribblings/reference/reference.scrbl)" "if")) in (part ("(lib scribblings/reference/reference.scrbl)" "top")) also documents 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,我们将在 Sequencing 中描述它们。
4.7.2 Combining Tests: and and or
(part ("(lib scribblings/reference/reference.scrbl)" "if")) in (part ("(lib scribblings/reference/reference.scrbl)" "top")) also documents and and or.
Racket 的 and 和 or 是语法形式,而不是函数。与函数不同,and 和 or 形式可以在前面的表达式确定答案时跳过后续表达式的求值。
(and expr ...)
如果任何 expr 产生 #f,则 and 形式产生 #f。否则,它产生最后一个 expr 的值。作为特例,(and) 产生 #t。
(or expr ...)
如果所有 expr 都产生 #f,则 or 形式产生 #f。否则,它产生其 expr 中第一个非 #f 的值。作为特例,(or) 产生 #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
如果求值到达 and 或 or 形式的最后一个 expr,那么该 expr 的值直接决定 and 或 or 的结果。因此,最后一个 expr 处于尾部位置,这意味着上面的 got-milk? 函数在常量空间中运行。
Tail Recursion introduces tail calls and tail positions.
4.7.3 Chaining Tests: cond
cond 形式将一系列测试链接起来以选择结果表达式。粗略地说,cond 的语法如下:
(part ("(lib scribblings/reference/reference.scrbl)" "if")) in (part ("(lib scribblings/reference/reference.scrbl)" "top")) also documents cond.
(cond [test-expr body ...+] ...)
每个 test-expr 按顺序求值。如果它产生 #f,则忽略相应的 body,继续对下一个 test-expr 求值。一旦某个 test-expr 产生真值,就对关联的 body 求值以产生 cond 表达式的结果,并且不再对后续的 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 表达式的结果。