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”一章中讨论。