Scheme 编程/条件语句
在本节中,我们介绍 Scheme 的条件表达式。这些形式使我们能够做出选择——这是我们迄今为止看到的简单程序中所缺乏的东西。
我们已经在前面的例子中看到了布尔值 #t(“真”)和 #f(“假”),作为诸如 (< 3 5) 这样的表达式的值。布尔值只有少数过程[1];最常见的可能是 not,它对其参数取反。
> (not #t)
#f
> (not #f)
#t
在 Scheme 中,布尔值不是唯一具有真值的物件;Scheme 的惯例是将 #f 视为假,并将所有其他 Scheme 值视为真。Scheme 中一个非常常见的习惯用法是,过程在成功时返回一些有用的值(很少是 #t),而在失败时返回 #f。虽然我们的过程将坚持返回 #t 的做法,但了解这种习惯用法很重要。
and 和 or 形式[2] 使我们能够以熟悉的方式操作真值。
> (and #t #f)
#f
> (or #f (not #f))
#t
> (and (> 3 2) #t)
#t
> (or (<= 3 2) (zero? (- 10 3)))
#f
在这种基本用法中,这些形式分别为我们提供了它们的两个参数(称为测试)的布尔 AND 和 OR。这通常已经足够了。然而,and 和 or 形式更灵活一些。首先,它们可以接受任意数量的测试。
> (and (> 3 2) (>= 5 4) (> 10 9))
#t
> (or (<= 2 3)
(zero? (- 10 3))
#f
(> 3 5))
#t
因此,当所有测试都为真时,and 表达式为真;当至少一个测试为真时,or 表达式为真。
如上所述,我们还可以进行评估结果不为 #t 和 #f 的测试。
> (and (> 3 2) 5)
5 ; ?
由于第一个测试表达式评估为 #t,而第二个测试表达式评估为 5(它不是 #f,#f 是 Scheme 中唯一被认为是假的的值),因此这个 and 表达式必须为真——但是它为什么评估为 5 呢?Scheme 的约定是,当所有测试都为真时,and 返回最后一个测试的值;or 返回第一个真测试的值。通过这种方式,我们可以在不影响表达式真值的情况下返回比 #t 更实用的值。
Scheme 中最简单的条件语句是 if 形式。以下是一些示例。
> (if #t 1 0)
1
> (if (>= 5 8) 3 (+ 7 2))
9
像 and 和 or 一样,if 是一种具有自身语法的特殊形式。以下是通用形式。
(if test consequent alternative)
if 表达式的第一个组件是测试表达式,它首先被评估。如果它的值为真,则返回第二个组件表达式的值(结果)。另一方面,如果测试评估为假,那么我们得到第三个组件的值(备选)。[3] 让我们看看这在上面的例子中是如何工作的。在第一个例子中,测试表达式只是 #t,因此 if 形式的值是结果表达式的值,即 1。为了评估第二个例子,我们首先考虑测试 (>= 5 8)。这评估为 #f,因此我们评估备选 (+ 7 2) 并将其值作为整个 if 表达式的值返回。
使用 if,我们可以编写一些有用的过程。一个简单的示例。
(define (absolute-value n)
(if (positive? n)
n
(- n))) ; with one argument, - gives the additive inverse
在测试表达式中使用 and 和 or 形式,我们可以组合多个测试。
(define (days-in-year y)
(if (or (= (floor-remainder y 400) 0)
(and (= (floor-remainder y 4) 0)
(not (= (floor-remainder y 100) 0))))
366 ; leap year
365))
| 练习 |
|---|
|
我们可以使用 if 编写大量的有用 Scheme 程序。实际上,通过将 if 连在一起,我们可以编写任何我们想要的条件表达式。不幸的是,这些表达式很快就会变得复杂。当嵌套 if 时,很难跟踪所有子句,并且可能需要多次编写结果。(例如,在最后一个练习中,有多少种情况下 366 是答案?)在这些情况下,我们需要一些更方便的东西。
为此,Scheme 提供了 cond,这是一种非常灵活的条件形式,它允许轻松地表达多路选择。以下是一个简短的示例。
(cond ((> 5 6) 7)
((= 3 (+ 1 2)) 4)
(else 8))
在最简单的形式中,cond 接受多个子句,每个子句都包含一个测试和一个结果表达式。为了评估 cond 表达式,我们依次评估每个子句的测试;如果其中一个测试评估为真,则 cond 返回该子句的结果表达式的值,并跳过任何剩余的子句。一个仅仅是 else 的测试总是为真,因此 cond 将始终“选择”一个以 else 作为其测试的子句的结果。通常,cond 的最后一个子句是“else 子句”。
cond 表达式的简化形式是
(cond clause1 clause2 ...)
其中每个子句的形式为 (test result) 或 (else result)。[4]
让我们逐步分析一个简单的 cond 表达式的评估过程。
(cond ((>= 5 8) 3)
(else (+ 7 2)))
为了评估这个表达式,我们查看第一个子句 ((>= 5 8) 3)。这个子句的测试表达式是 (>= 5 8),因此我们评估它;由于它的值为 #f,因此我们跳过这个子句,并继续下一个子句 (else (+ 7 2))。这个子句的测试是 else,它总是为“真”。我们评估这个子句的结果表达式 (+ 7 2),并将它的值 9 作为整个 cond 表达式的值返回。由于这个 cond 表达式在第一个子句的测试为真时返回 3,否则返回 9,因此它与以下 if 形式完全等效。
(if (>= 5 8) 3 (+ 7 2))
因此,我们可以用一个等效的 cond 替换任何 if 表达式。实际上,cond 非常通用,我们可以用它来代替任何其他条件形式。
- ↑ R7RS § 6.3
- ↑ 虽然
and和or表达式看起来非常像应用,但它们不是过程,而是特殊形式。Scheme 具有许多特殊形式,每种形式都有自己的语法和语义。本节中描述的条件形式是一些最常见的特殊形式。 - ↑ 第三个表达式是可选的。形式为
(if b x)的表达式,有时被称为“单臂 if”,是合法的 Scheme。如果b为真,则它给出x的值,否则给出未指定的值(可以是任何值)。我们将在“高级 Scheme”一章中看到如何使用它;现在,我们始终将包含备选。 - ↑ 如上所述,这是简化的。一个子句实际上可以接受任意数量的结果表达式,从而得到通用形式
(test expression1 expression2 ...)。如果test 为真,则依次评估这些表达式,并返回最后一个表达式的值。如果只有一个结果表达式,则等效于我们上面描述的内容。cond还提供了一种称为“cond 箭头”的表示法,我们将在“高级 Scheme”一章中讨论。