Common Lisp 有一個稱為 The Printer 的概念,其中包含了所有用來撰寫 Lisp 資料文字表示法的函式。其中一個這樣的函式是 format,它讓程式設計師能依照自己的需求格式化資料。
format 以及廣義上 Lisp Printer 的能力是個很大的主題;這裡只會介紹 format 用法的基礎。
函式format至少接受兩個引數:要輸出到的串流,以及定義要輸出什麼、如何輸出的控制字串。
串流本身是個獨立的概念,會在別處介紹。但目前你只需要知道,輸出是寫入串流,而這個引數有兩個特殊值:t和nil。t表示輸出到標準輸出(實際上是變數*standard-output*所綁定的串流),而nil表示建立一個字串並回傳它。
請注意,format一律會求值為nil,只有在後者、也就是把nil指定為串流的情況下例外。這是進行單純字串內插或字串建構的常見做法。
(format nil "hello world") ; => "hello world"
(format t "hello world") ; => NIL (but "hello world" is printed to standard output)
控制字串可以包含「格式指示詞」,用來指示format要輸出什麼,以及如何解讀傳給format的其他參數。某種程度上,控制字串就是一支程式,format會解讀並執行它。所有指示詞都以~開頭,後面接著一個字元(例如~A、~D、~&)。字元的大小寫沒有差別。
大多數格式指示詞只是把傳給format的引數內插到輸出中。這稱為「消耗」該引數。有些指示詞不會消耗引數,有些則消耗超過一個。甚至有一個指示詞能讓你在引數列表中跳來跳去,或略過某些引數。
一個用途非常廣泛的格式指示詞是~A。它會輸出資料的「人類可讀」版本。
(format nil "Value = ~a" 10) ; => "10"
(format nil "hello ~a" "world") ; => "hello world"
(format nil "The list is: ~A" (list 1 2 3)) ; => "The list is: (1 2 3)"
另一個實用的格式指示詞是~S。它會輸出資料的「機器可讀」版本。如果你想讓產生的輸出能被當作 Lisp 資料解讀,這會非常有用。對許多東西而言,它的結果看起來和~A一樣,但要注意,以字串為例,輸出會包含引號(被包含在另一個字串中時會加上跳脫),這樣這個值之後就能被讀回成字串參數。
(format nil "~s" 10) ; => "10"
(format nil "~s" "hello world") ; => "\"hello world\""
(format nil "~s" (list 1 2 3)) ; => "(1 2 3)"
另外兩個非常實用的指示詞是~%和~&。前者輸出一個換行字元,後者則輸出一個「新行」。差別在於~%一律輸出換行,而~&只有在目前不在行首時才會輸出一個換行。這兩者都不會消耗引數。
(format nil "~%new~%lines") ; => "
new
lines"
(format nil "~&new~&lines" ; => "new
lines"
最後,~~是一個簡單的指示詞,會輸出一個字面上的~。
就像幾乎所有有工作的人一樣,Lisp 外星人 Layla 有時也需要製作報表,讓其他人知道一些資訊。他問你能不能幫他把他手上這些報表要用的資料格式化。
Layla 首先需要的是一個函式:format-quarter-value,它接受兩個參數:季度和值,並將它格式化,以便放進報表。它的求值結果應該是一個字串。
(format-quarter-value "last" 3.14) ; => "The value last quarter: 3.14"
(format-quarter-value "this" 0) ; => "The value this quarter: 0"
Layla 謝謝你寫了 format-quarter-value 這個函式,並說在下一個需要的函式裡,它會非常有用。
下一份報表需要印出 2 個季度的季度與值,分成兩行。
此外,輸出必須送到作為第一個引數提供的那個串流。
例如:(format-two-quarters t "last" 3.14 "this" 0) 會在標準輸出上產生下列內容:
The value last quarter: 3.14
The value this quarter: 0
注意它前後的空行。
Layla 忘了告訴你,Lisp 外星人喜歡用可以直接當成 Lisp 資料讀取的方式來看值。所以他們需要一個新函式,它的輸出要能被讀成 Lisp 資料(一樣要送到指定的串流):
(format-two-quarters-for-reading nil "last" 3.14 "this" 0) ; =>
"(\"The value last quarter: 3.14\" \"The value this quarter: 0\")"