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

results matching ""

    No results matching ""