如同我們在前文所見的 if
,if
的確是一個最古老的決策結構,幾乎有程式語言的時代,就有了它。而它的使用需要注意什麼呢?在 Racket 裡的 if
,先接了判斷值,這判斷值如先前所述,如果不是 #f
,就清一色都是 #t
。這點其實需要特別注意,因為從程式語言觀點來看,這樣做並不好。一個嚴謹的語言,if
與任何判斷機制,都應只接受 boolean 值。
除此之外,在這裡需要特別說明的,其實是 if
的正論與反論邏輯,if
在其他語言,常常是可以只留判斷式成立時的行為,而不寫 else
,像這樣:
private String toUpperCase(String txt){
if(txt != null){
return txt.toUpperCase();
}
}
然而,在 Racket 的 if
,卻絕對不能沒有 else
的反論邏輯。例如你不能這樣寫:
(define to-upper-case
(lambda (txt)
(if (non-empty-string? txt)
(string-upcase txt)
))) ;; 此處不能沒有第二條敘述!
因此,如果你真的不想做什麼事,可以這麼寫:
(define to-upper-case
(lambda (txt)
(if (non-empty-string? txt)
(string-upcase txt)
txt))) ;; 一定要有回傳值
(define display-upper-string
(lambda (txt)
(if (non-empty-string? txt)
(displayln (string-upcase txt))
(void)))) ;; 或直接用 (void) 結束這區段
而我們使用這些條件判斷時,常需要組合幾個可能,搭配 and
、or
、not
之類的邏輯組合方式,而 Racket 正提供了這幾個選項:
(define-values (a b c) (values 1 0 -1))
(if (and (> a 0)
(= b 0))
'positive
'negative)
(if (or (> a 0)
(> c 0))
'maybe-positive
'maybe-negative)
(if (not (> c 0))
'positive
'negative)
當你的邏輯有多重判斷時,在其他的語言,諸如 Java/Python,會使用什麼語法?以 Java 來說,就是使用 else if
的作法,在 Python,我猜應該也不出是這作法。這作法的好處,是在於使用統一的語法形式來表達多層次的判斷,而當然,在寫起來也比較繁瑣。
Racket 則使用另一個簡潔的邏輯控制機制:cond
。它可以讓你一直加上條件與回傳值,例外條件則使用 else
來表示。以下是用 cond
寫的一個費氏數列函式:
(define fib
(lambda (x)
(cond ((= x 0) 0)
((= x 1) 1)
(else (+ (fib (- n 1)) (fib (- n 2)))))))
然而,cond
是一個特殊的案例,它可以不寫 else
區段,因此若你的 if
沒有 else
,或許可以考慮使用 cond
來呼叫。
下這標題不是把大家當小朋友,而是其實 for
本質上是一種語法糖,它其實並不存在於程式語言的核心概念中,而是為了開發方便,擴充出來的語法。當然,Racket 之所以是 Racket,也在於它提供了許多親民的語法,像 for
就是:
(for ([i '(1 2 3)]
[j "abc"]
[k #(#t #f)])
(display (list i j k)))
for
有許多組合語法,詳細可以參考 Racket Reference [1],我們主要說明它的基礎概念,因為我們後續會碰到跟它很像的一個結構。
首先先說明中括號,在 Racket 與 Scheme 裡頭,小括號與中括號是可以互用的,因此在慣例上,我們在定義區域變數時,會使用中括號 []
。
for
的第一個區塊,是定義在 for
裡要使用的區域變數,這些變數只會存在在 for
迴圈中。定義的方式是 id sequence
, id 就是變數名稱,先前已經解釋過。而 sequence 的型態很特別,它只的是在 Racket 語言裡面,具有次序關係的值,例如一個 List、Vector、String。然而,如果只輸入一個數字呢?它也允許你輸入一個正整數 n,然後它會從 0 跳到 n-1。
但是輸入一個數字的方式,其實可讀性並不佳。因此,我們要說明另一個作法:
(for ([i (in-range 10)])
(display (number->string i))) ;; 0123456789
如果你要用跳數字的方式,我會建議使用 in-range
的呼叫。
如果要對這些區域變數做些即時性的判斷與處理,可以搭配 for
的 Keyword,如:
(for ([i (in-range 10)]
#:when (odd? i))
(display (number->string i))) ;; 13579
像 for
還有昨天最後說到的 cadr
、cdar
之類的特殊語法,其實都屬於 SRFI 的範圍。SRFI 全名是 Scheme Require For Implementation,是 Scheme 語言規格書之外的擴充規格。既然 Requre For Implementation,就代表有許多的實作,而 Racket 語言是把絕大多數的 SRFI 都實作在語言核心中了。