在本页中:
4.7.1 简单分支:if
4.7.2 组合测试:andor
4.7.3 链式测试:cond

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 ReferenceConditionals: if, cond, and, and or一节中也提供了if的文档。

if 形式

(if test-expr then-expr else-expr)

中,test-expr 总是会被求值。如果它产生了除 #f 以外的任何值, 那么 then-expr 就会被求值。否则 else-expr 就会被求值。

if 必须同时有 then-exprelse-expr, 后者并不是可选的。要处理(或跳过)基于 test-expr 的副作用,请使用 whenunless,我们会在之后的 序列 一节中讨论它。

4.7.2 组合测试:andor

+The Racket ReferenceConditionals: if, cond, and, and or一节中也提供了andor的文档。

Racket 的 andor 为语法形式而非函数。与函数不同, andor 形式在前一个表达式确定结果后可跳过后面表达式的求值。

(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

若求值抵达了 andor 形式的最后一个 expr,那么 expr 的值会直接确定 andor 的结果。因此,最后一个 expr 在尾部,这意味着 got-milk? 函数的执行只需要常量空间。

+尾递归 介绍了尾调用和尾部位置。

4.7.3 链式测试:cond

cond 形式将一系列测试作为测试链来从中选取一个结果表达式。 cond 的语法大致如下:

+The Racket ReferenceConditionals: 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 表达式的结果返回。