[TOC=2]
## 5.1?簡介
上一章中,我講解了如何定義函數。本章中,我會講解如何通過條件編寫過程。這個是編寫使用程序很重要的一步。
## 5.2?if表達式
`if`表達式將過程分為兩個部分。`if`的格式如下:
~~~
(if predicate then_value else_value)
~~~
如果`predicate`部分為真,那么`then_value`部分被求值,否則`else_value`部分被求值,并且求得的值會返回給`if`語句的括號外。`true`是除`false`以外的任意值,`true`使用`#t`表示,`false`用`#f`表示。
在R5RS中,`false`(`#f`)和空表`(’())`是兩個不同的對象。然而,在MIT-Scheme中,這兩個為同一對象。這個不同可能是歷史遺留問題,在以前的標準——R4RS中,`#f`和`’()`被定義為同一對象。
因此,從兼容性角度考慮,你不應該使用表目錄作為謂詞。使用函數`null?`來判斷表是否為空。
~~~
(null? '())
;Value: #t
(null? '(a b c))
;Value: () ;#f
~~~
函數`not`可用于對謂詞取反。此函數只有一個參數且如果參數值為`#f`則返回`#t`,反之,參數值為`#t`則返回`#f`。`if`表達式是一個特殊形式,因為它不對所有的參數求值。因為如果`predicate`為真,則只有`then_value`部分被求值。另一方面,如果`predicate`為假,只有`else_value`部分被求值。
例:首項為`a0`,增長率`r`,項數為`n`的幾何增長(geometric progression)數列之和
~~~
(define (sum-gp a0 r n)
(* a0
(if (= r 1)
n
(/ (- 1 (expt r n)) (- 1 r))))) ; !!
~~~
通常來說,幾何增長數列的求和公式如下:
~~~
a0 * (1 - r^n) / (1 - r) (r ≠ 1)
a0 * n (r = 1)
~~~
如果`if`表達式對所有參數求值的話,那么有`;!!`注釋的那行就算在`r=1`時也會被求值,這將導致產生一個“除數為0”的錯誤。
你也可以省去`else_value`項。這樣的話,當`predicate`為假時,返回值就沒有被指定。如果你希望當`predicate`為假時返回`#f`,那么就要明確地將它寫出來。
`then_value`和`else_value`都應該是S-表達式。如果你需要副作用,那么就應該使用`begin`表達式。我們將在下一章討論`begin`表達式。
> 練習1
>
> 編寫下面的函數。閱讀第五節了解如何編寫謂詞。
>
> * 返回一個實數絕對值的函數。
> * 返回一個實數的倒數的函數。如果參數為`0`,則返回`#f`。
> * 將一個整數轉化為ASCII碼字符的函數。整數可以被轉化為33-126號之間的ASCII碼。使用`integer->char`可以將整數轉化為字符。如果給定的整數不能夠轉化為字符,那么就返回`#f`。
## 5.3?and和or
`and`和`or`是用于組合條件的兩個特殊形式。Scheme中的`and`和`or`不同于C語言中的約定。它們不返回一個布爾值(`#t`或`#f`),而是返回給定的參數之一。`and`和`or`可以使你的代碼更加短小。
### 5.3.1?and
`and`具有任意個數的參數,并從左到右對它們求值。如果某一參數為`#f`,那么它就返回`#f`,而不對剩余參數求值。反過來說,如果所有的參數都不是`#f`,那么就返回最后一個參數的值。
~~~
(and #f 0)
;Value: ()
(and 1 2 3)
;Value: 3
(and 1 2 3 #f)
;Value: ()
~~~
### 5.3.2?or
`or`具有可變個數的參數,并從左到右對它們求值。它返回第一個不是值`#f`的參數,而余下的參數不會被求值。如果所有的參數的值都是`#f`的話,則返回最后一個參數的值。
~~~
(or #f 0)
;Value: 0
(or 1 2 3)
;Value: 1
(or #f 1 2 3)
;Value: 1
(or #f #f #f)
;Value: ()
~~~
> 練習2
>
> 編寫下面的函數。
>
> * 一個接受三個實數作為參數的函數,若參數皆為正數則返回它們的乘積。
> * 一個接受三個實數作為參數的函數,若參數至少一個為負數則返回它們的乘積。
## 5.4?cond表達式
盡管所有的分支都可以用`if`表達式表達,但當條件有更多的可能性時,你就需要使用嵌套的`if`表達式了,這將使代碼變得復雜。處理這種情況可以使用`cond`表達式。`cond`表達式的格式如下:
~~~
(cond
(predicate_1 clauses_1)
(predicate_2 clauses_2)
......
(predicate_n clauses_n)
(else clauses_else))
~~~
在`cond`表達式中,`predicates_i`是按照從上到下的順序求值,而當`predicates_i`為真時,`clause_i`會被求值并返回。`i`之后的`predicates`和`clauses`不會被求值。如果所有的`predicates_i`都是假的話,則返回`cluase_else`。在一個子句中,你可以寫數條S-表達式,而`clause`的值是最后一條S-表達式。
> 例:城市游泳池的收費。
>
> Foo市的城市游泳池按照顧客的年齡收費:
>
> 如果?age?≤ 3 或者?age?≥ 65 則 免費;
> 如果 介于 4 ≤?age?≤ 6 則 0.5美元;
> 如果 介于 7 ≤?age?≤ 12 則 1.0美元;
> 如果 介于 13 ≤?age?≤ 15 則 1.5美元;
> 如果 介于 16 ≤?age?≤ 18 則 1.8美元;
> 其它 則 2.0美元;
>
> 那么,一個返回城市游泳池收費的函數如下:
~~~
(define (fee age)
(cond
((or (<= age 3) (>= age 65)) 0)
((<= 4 age 6) 0.5)
((<= 7 age 12) 1.0)
((<= 13 age 15) 1.5)
((<= 16 age 18) 1.8)
(else 2.0)))
~~~
> 練習 3
>
> 編寫下列函數。
>
> 成績(A-D)是由分數決定的。編寫一個將分數映射為成績的函數,映射規則如下:
> + A 如果?score?≥ 80
> + B 如果 60 ≤?score?≤ 79
> + C 如果 40 ≤?score?≤ 59
> + D 如果?score?< 40
## 5.5?做出判斷的函數
我將介紹一些用于做判斷的函數。這些函數的名字都以`'?'`結尾。
### eq?、eqv?和equal?
基本函數`eq?`、`eqv?`、`equal?`具有兩個參數,用于檢查這兩個參數是否“一致”。這三個函數之間略微有些區別。
> `eq?`
> 該函數比較兩個對象的地址,如果相同的話就返回`#t`。例如,`(eq? str str)`返回`#t`,因為`str`本身的地址是一致的。與此相對的,因為字符串`”hello”`和`”hello”`被儲存在了不同的地址中,函數將返回`#f`。不要使用`eq?`來比較數字,因為不僅在R5RS中,甚至在MIT-Scheme實現中,它都沒有指定返回值。使用`eqv?`或者`=`替代。
~~~
(define str "hello")
;Value: str
(eq? str str)
;Value: #t
(eq? "hello" "hello")
;Value: () ← It should be #f in R5RS
;;; comparing numbers depends on implementations
(eq? 1 1)
;Value: #t
(eq? 1.0 1.0)
;Value: ()
~~~
> `eqv?`
> 該函數比較兩個存儲在內存中的對象的類型和值。如果類型和值都一致的話就返回`#t`。對于過程(`lambda`表達式)的比較依賴于具體的實現。這個函數不能用于類似于表和字符串一類的序列比較,因為盡管這些序列看起來是一致的,但它們的值是存儲在不同的地址中。
~~~
(eqv? 1.0 1.0)
;Value: #t
(eqv? 1 1.0)
;Value: ()
;;; don't use it to compare sequences
(eqv? (list 1 2 3) (list 1 2 3))
;Value: ()
(eqv? "hello" "hello")
;Value: ()
;;; the following depends on implementations
(eqv? (lambda(x) x) (lambda (x) x))
;Value: ()
~~~
> `equal?`
> 該函數用于比較類似于表或者字符串一類的序列。
~~~
(equal? (list 1 2 3) (list 1 2 3))
;Value: #t
(equal? "hello" "hello")
;Value: #t
~~~
### 5.5.2?用于檢查數據類型的函數
下面列舉了幾個用于檢查類型的函數。這些函數都只有一個參數。
* `pair?`?如果對象為序對則返回`#t`;
* `list?`?如果對象是一個表則返回`#t`。要小心的是空表`’()`是一個表但是不是一個序對。
* `null?`?如果對象是空表’()的話就返回#t。
* `symbol?`?如果對象是一個符號則返回#t。
* `char?`?如果對象是一個字符則返回#t。
* `string?`?如果對象是一個字符串則返回#t。
* `number?`?如果對象是一個數字則返回#t。
* `complex?`?如果對象是一個復數則返回#t。
* `real?`?如果對象是一個實數則返回#t。
* `rational?`?如果對象是一個有理數則返回#t。
* `integer?`?如果對象是一個整數則返回#t。
* `exact?`?如果對象不是一個浮點數的話則返回#t。
* `inexact?`?如果對象是一個浮點數的話則返回#t。
### 5.5.3?用于比較數的函數
> `=`、`>`、`<`、`<=`、`>=`
> 這些函數都有任意個數的參數。如果參數是按照這些函數的名字排序的話,函數就返回`#t`。
~~~
(= 1 1 1.0)
;Value: #t
(< 1 2 3)
;Value: #t
(< 1)
;Value: #t
(<)
;Value: #t
(= 2 2 2)
;Value: #t
(< 2 3 3.1)
;Value: #t
(> 4 1 -0.2)
;Value: #t
(<= 1 1 1.1)
;Value: #t
(>= 2 1 1.0)
;Value: #t
(< 3 4 3.9)
;Value: ()
~~~
> `odd?`、`even?`、`positive?`、`negative?`、`zero?`
> 這些函數僅有一個參數,如果這些參數滿足函數名所指示的條件話就返回`#t`。
### 5.5.4?用于比較符號的函數
在比較字符的時候可以使用`char=?`、`char<?`、`char>?`、`char<=?`以及`char>=?`函數。具體的細節請參見R5RS。
### 5.5.5?用于比較字符串的函數
比較字符串時,可以使用`string=?`和`string-ci=?`等函數。具體細節請參見R5RS。
## 5.6?總結
在這一章中,我總結了關于分支的知識點。編寫分支程序可以使用`if`表達式和`cond`表達式。
下一章我將講解局部變量。
## 5.7?習題解答
### 5.7.1?答案1
~~~
; 1
(define (my-abs n)
(* n
(if (positive? n) 1 -1)))
; 2
(define (inv n)
(if (not (zero? n))
(/ n)
#f))
; 3
(define (i2a n)
(if (<= 33 n 126)
(integer->char n)
#f))
~~~
### 5.7.2?答案2
~~~
; 1
(define (pro3and a b c)
(and (positive? a)
(positive? b)
(positive? c)
(* a b c)))
; 2
(define (pro3or a b c)
(if (or (negative? a)
(negative? b)
(negative? c))
(* a b c)))
~~~
### 5.7.3?答案3
~~~
(define (score n)
(cond
((>= n 80) 'A)
((<= 60 n 79) 'B)
((<= 40 n 59) 'C)
(else 'D)))
~~~