Racket 的 true 與 false 不若 Java、C# 與 Python,是以完整的字來表示,從字元那節開始,我們已經開始接觸 Racket 的一些特殊表示方式,現在所談的布林值,也是如此。Racket 以 #t
當作 true,#f
當作 false。而像 C 語言那樣,非布林的值也有布林含意。但 Racket 裡,只有 #f
可以代表 false,其他非 #f
的任何值,與 #t
皆為 true。當然這是一項不太好的設計,但每個語言多少有個一兩項讓人扼腕的地方,這是很正常的事。換句話說,你可以把 '()
(空 list)帶進 if
區塊中,這在 Racket 是合理的,但在 Java 與 C# 裡,會連 compiler 都過不了。
因此,我們可以回到之前所說的型別判斷:
(string? "a") ;; #t
(string? #\a) ;; #f
(exact? 3.14) ;; #f
(inexact? 3.14) ;; #t
我們在大多數語言裡,都可以把 String 轉換成 byte array,有時候比較麻煩,像 C# 要呼叫 Encoding
,Java 直接 getBytes()
。然而在 Racket 裡頭,另有一種 String,稱為 Byte String,不若 String 是以字元 list 的形式儲存,Byte String 是以 byte list 的形式儲存,因此它可以處理更底層的 String 內容。Byte String 的表示法,就是在 String 前方加上 #
。
我們在 CJK 字集的國家裡,處理文字時長度時,通常需要先區別要處理的是 字數 還是 位元組數 這兩種不同的長度。因此 Racket 的 Byte String 提供了一個轉換機制:
(define str "我是字串")
(string-length str) ;; 4
(define bstr (string->bytes/utf-8 str))
(bytes-length bstr) ;; 12
然而,不只處理文字。在 Racket 裡,表示二進位資料也是用 bytes 的形式(對比 Scheme 則是使用 Byte Vector),後續有機會說到一些實務應用與 IO 時,會再回頭與它相遇。
至此,所談到的資料型態,都是很基本的型態,不管是數值、字串、布林、bytes。現在我們要進入的是這門語言資料型態較為抽象的一層。Racket 裡有兩種相當抽象的資料型態 — Symbol 與 Keyword。
Symbol 中文翻作符號,這像什麼呢?如果各位有 Java 或 C# 之類高階物件導向語言的經驗,它類似 Java 裡的 enum 型態。Symbol 使用單引號來表示:'a-symbol
。
根據 Racket Guide [1],Symbol 是一種 interned 的資料,什麼是 interned?我查了字典,可作實習生(當然,怎麼可能!),亦可做拘押;Racket Guide 所給的提示實在有限;或許看看規格書怎麼說,Scheme 規格書僅僅簡單地描述了 Symbol 在比較時的特性:當兩個 Symbol 的字母拼法完全一致時,這兩個可謂完全相等 [2];最後,我參考了 Scheme 最權威的教科書之一:TSPL4,其中題到了非常重要的關鍵:Symbol 在實作時,是一種查表對應的機制,名字相同的 Symbol,其實作上,就是參考到同一個記憶體物件,因此在進行比較時,可以非常迅速。相較起來, String(字串)比較,則需逐一巡訪字串裡每一個字,效率並不高。 [3]因此,我們可以知道,在 Racket 的 interned 這個字,應該就是指 Symbol 在建立時,會被 "關" 在某個表裡。
因為在 Racket 可以做以下操作,將 String 轉換為 Symbol(將 String 關起來):
(define a-symb 'a-symbol)
(define another-symb (string->symbol "a-symbol"))
(eq? a-symb another-symb)
當然,你可以把它轉回來,但 Symbol 不等於 String:
(define a-str (symbol->string a-symb))
(string? a-symb) ;; #f
(symbol? a-str) ;; #f
(eq? a-symb a-str) ;; #f
然而,Racket 有個 Scheme 沒有的特殊型態:Keyword。這個可以看一下範例:
(define fruites "apple banana graple")
(define fruite-list (string-split fruites #:trim? #t))
fruite-list
我們先定義一個長字串,然後分割它,在大多數語言裡,string splitting 是很平常的事。然而,假設 Java 有這樣的 method,string splitting 時,還要把每個字串的前後空白都 trim 掉,method 會怎麼宣告呢?
public String[] split(String sep, boolean trim){}
但在 Racket 裡頭,提供了一種為參數命名的機制,就是使用 Keyword 型態,它在記憶體裡也是以一個字串的形式來儲存,但字面表達上是在前方加上:#:
。Keyword 型態很少單獨使用,絕大多數是拿來當作函式呼叫的參數別名使用,我們便不在此深入討論。
註解在 Racket 裡頭有兩種形式,其一就是常常在我們範例程式出現的,以 ;
為首的單行註解,類似 Java、C/C++/C# 的 //
。另外還有整段文字式的:#| 註解內容 |#
,這個就可以進行多行的註解,類似 Java、C/C++/C# 的 /* */
。
因此,各位在以上、以下的範例程式裡,會看見我使用註解以表達輸出訊息或進一步的說明。