2.7 条件表达式
到此为止,我们学习了无条件地执行一个给定的任务。假设我们希望写一个函数abs
求一个数的绝对值,如果参数x
是负数,返回-x
,否则就返回x
。
(define abs
(lambda (n)
(if (< n 0)
(- 0 n)
n)))
(abs 77) => 77
(abs -77) => 77
if
表达式的形式为(if test consequent alternative)
,如果test
表达式的结果为真,则对consequent
表达式求值,如果为假,则对alternative
表达式求值。
if
是特殊形式,它并不是一个函数。为什么呢?让我们重新审视第一章中的 reciprocal
函数。
(define reciprocal
(lambda (n)
(if (= n 0)
"oops!"
(/ 1 n))))
在第二个子句中,n 不能是 0, 如果 n 的值为零,在数学上是无意义的。如果 if
是一个函数,它的参数(包括(/ 1 n)
)在选择要执行 consequent 或者 alternative 之前都会被求值。对(/ 1 0)
进行求值显然是一个错误。与quote
并不对它唯一的子表达式求值类似,if
并不对它的所有子表达式求值,所以它不能是一个函数。
or
语法形式在操作上与if
类似,常见的or
表达式为(or expr ...)
。如果没有子表达式,返回#f
. 否则,依次对所有子表达式求值。这里有两种情况: a.如果发现其中一个子表达式的结果不是#f
,立即停止对后面的子表达式求值,返回该子表达式的值作为整个or
表达式的值; b.如果所有的子表达式的值都是#f
,则返回最后一个子表达式的值,也就是#f
。
每一个Scheme对象,在条件表达式中都有隐含的布尔值,只有#f
被看作是“假”,所有其它对象都被当作#t
对待。
(if #t 'true 'false) => true
(if #f 'true 'false) => false
(if '() 'true 'false) => true
(if 1 'true 'false) => true
(if '(a b c) 'true 'false) => true
(not #t) => #f
(not "false") => #f
(not #f) => #t
(or) => #f
(or #f) => #f
(or #f #t) => #t
(or #f 'a #f) => a
and
表达式与or
表达式类似,但正好相反。如果and
表达式的所有子表达式不为#f
,则整个表达式的值就是最后一个子表达式的值,只要其中一个子表达式的值为#f
则立即停止对后面的子表达式求值,立即返回#f
。
通过使用and
, 我们可以定义一个稍微有些不同的reciprocal
函数。
(define reciprocal
(lambda (n)
(and (not (= n 0))
(/ 1 n))))
(reciprocal 3) => 1/3
(reciprocal 0.5) => 2.0
(reciprocal 0) => #f
在这个版本里,如果n的值为零则返回#f
,否则就返回(/ 1 n)
的值。
=, <, >, <=, >=
这些函数被称作谓词。谓词是这样一类函数,它回答与它的参数有关的一个问题,并且返回#t
或者#f
。大多数谓词的后面跟着一个问号。不过上面列出的数学谓词是例外。当然,并非所有的谓词都需要数字型参数。比如谓词null?
,当它的参数是空列表'()
的时候返回 #t
,其它所有情况下返回#f
。
(null? '()) => #t
(null? 'abc) => #f
(null? '(x y z)) => #f
(null? (cdddr '(x y z))) => #t
在 Scheme 里,cdr
函数不接受空列表做为参数,而在 Common Lisp 里可以这样做, (cdr '())
的结果仍然为()
。下面的函数 lisp-cdr
是对 Lisp 的一个模仿。
(define lisp-cdr
(lambda (x)
(if (null? x)
'()
(cdr x))))
(lisp-cdr '(a b c)) => (b c)
(lisp-cdr '(c)) => ()
(lisp-cdr '()) => ()
另一个有用的谓词是eqv?
,它需要两个参数,如果它们相等返回#t
, 否则返回#f
。
(eqv? 'a 'a) => #t
(eqv? 'a 'b) => #f
(eqv? #f #f) => #t
(eqv? #t #t) => #t
(eqv? #f #t) => #f
(eqv? 3 3) => #t
(eqv? 3 2) => #f
(let ((x "Hi Mom!"))
(eqv? x x)) => #t
(let ((x (cons 'a 'b)))
(eqv? x x)) => #t
(eqv? (cons 'a 'b) (cons 'a 'b)) => #f
如你所见,对于符号,布尔值和数字,eqv?
比较它们是否相等,而对于 pair 对象,eqv?
比较它们是否是同一个对象。分别调用两次cons
产生看起来一起的 pair,但它们仍然不是同一个对象,所以结果为 #f
。
Scheme 提供了一组类型谓词来判断某个对象是否是指定的类型:pair?, symbol?, number?, string?
等等。
类型谓词用于判断传递给某个函数的参数是否是正确的类型。
(define reciprocal
(lambda (n)
(if (and (number? n) (not (= n 0)))
(/ 1 n)
"oops!")))
(reciprocal 2/3) => 3/2
(reciprocal 'a) => "oops!"
顺便提一下,调用 reciprocal
的函数同样不得不判断它的返回值是一个整数还是字符串,为了减轻调用者的任务,更好的办法通常是报告一个错误,使用assertion-violation
(define reciprocal
(lambda (n)
(if (and (number? n) (not (= n 0)))
(/ 1 n)
(assertion-violation 'reciprocal
"improper argument"
n))))
(reciprocal .25) => 4.0
(reciprocal 0) => exception in reciprocal: improper argument 0
(reciprocal 'a) => exception in reciprocal: improper argument a
assertion-violation
的第一个参数是个符号,用于标识引发错误的源头,第二个参数是一个字符串,用于说明错误, the third and subsequent arguments are "irritants" to be included with the error message.
让我们着眼于另一个多分支条件表达式cond
。
(define sign
(lambda (n)
(cond
[(< n 0) -1]
[(> n 0) +1]
[else 0])))
在sign
函数中,无需考虑各分支的先后顺序,对于参数n,在三个测试条件中只有唯一的一个为为真。下面的程序根据收入来计算税金。分支条件的顺序就很重要了:
(define income-tax
(lambda (income)
(cond
((<= income 10000) (* income .05))
((<= income 20000) (+ (* (- income 10000) .08) 500.00))
((<= income 30000) (+ (* (- income 20000) .13) 1300.00))
(else (+ (* (- income 30000) .21) 2600.00)))))
(income-tax 5000) => 250.0
(income-tax 15000) => 900.0
(income-tax 25000) => 1950.0
(income-tax 50000) => 6800.0