Common Lisp 允許程式設計師撰寫會被電腦忽略的「註解」。
單行註解以一個或多個分號(;)開頭,偶爾你也會看到下面這種寫法:
(code...) ; => value
這種註解用來表示 Common Lisp 執行該行程式碼後回傳什麼值。
慣例上,行末的簡短註解用一個分號,一段程式碼上方的較長註解用兩個分號,描述函式這類事物的長註解用三個,而原始檔最上方的標頭之類註解則用四個。
所有 Common Lisp 物件不是「原子」(單一且不可分割的值),就是「cons」(由建構函式 cons 建立)。
一個 cons 由兩部分組成:第一個元素與其餘的元素。
基於歷史因素,這兩部分分別稱為 car 和 cdr。
當這些 cons 被當作程式碼求值時,第一個元素(car)代表被呼叫的函式,而其餘的元素(cdr)則代表傳給該函式的引數:
(<function> <arg1> <arg2> ... <argN>)
; ^ car ^ | ^ cdr ^
所有 Common Lisp 程式碼都是由 S-Expressions(符號運算式)組成。它們簡稱 sexprs。每個 sexpr 不是原子就是 cons。S-Expressions 被求值時,會自動回傳某個值,取代原本的運算式。當你撰寫自己的函式(使用 defun)時,defun 主體中的最後一個值會自動被回傳:
;; Defining a new function
(defun gimme-foo () 'foo)
;; Calling the function as an S-Expression
(gimme-foo) ; => FOO
Common Lisp 中的符號是特殊的值,可以指向其他值,或者,就_關鍵字_而言,指向它們自己。 當 Lisp 對符號求值時,會將它們替換成所指向的值:
foo ; => <whatever-foo-points-to>
:foo ; => :FOO
請注意,關鍵字以開頭的冒號(:)表示。
引用是指在 S-expression 前面加上 ',它會告訴 Lisp 不要對該運算式求值。
在我們的 defun 範例中,透過引用 'foo,我們避免了 Lisp 試圖尋找(卻找不到)FOO 應該指向的對象,反而直接回傳 FOO 這個值本身。
如果程式中任何地方都沒有定義 FOO:
foo ; => <ERROR! Lisp doesn't know what foo points to!>
'foo ; => FOO
目前你可以把它想成一種從函式回傳符號的方法,但我們之後會再回來看引用,並在未來的概念練習中進一步探討它的影響。
你!沒錯,就是你,勇敢的 lisper!Lenny 這位 lisp 外星人 需要你的幫忙!Lenny 想和其他 lisp 外星人一起出去玩,但他們的父母說了,房間沒打掃乾淨之前,哪裡都不准去。
問題是,Lenny 幾乎看不到地板!Lenny 分不清哪些是原子、哪些是 cons,哪些是符號、哪些是關鍵字,也分不清 car 和 cdr!
你可以幫 Lenny 收拾這團混亂嗎?別忘了你的括號,你會需要它們的!
在 Lenny 開始動手之前,得先複習一下符號和關鍵字長什麼樣子。畢竟他們上次整理已經是好一陣子以前的事了…… 幫幫 Lenny,定義兩個函式:lennys-favorite-food 會求值成某個符號,lennys-secret-keyword 則會求值成某個關鍵字。例如:
(lennys-favorite-food) ; => LASAGNA
(lennys-secret-keyword) ; => :ALIENS-ARE-REAL
請注意,你可以回傳任何你喜歡的符號或關鍵字(如果你不愛千層麵,或是不相信外星人,那也不要緊)。
等 Lenny 複習完符號和關鍵字,就該開始打掃了!要整理散落在他們房間裡的 S-Expressions,Lenny 需要有人幫忙判斷哪些是 cons、哪些是原子。你可以實作兩個函式來幫忙:is-an-atom-p 和 is-a-cons-p:
(is-an-atom-p 'one-thing) ; => T
(is-an-atom-p '(two things)) ; => NIL
(is-a-cons-p 'one-thing) ; => NIL
(is-a-cons-p '(two things)) ; => T
這一段裡有幾個內建的 lisp 函式可能會派上用場。另外,目前你可以先把 T 視為 true,把 NIL 視為 false。
最後,Lenny 的 cons 太龐大了,沒辦法整齊收好,所以得把它們拆成更小的部分。你能再定義兩個函式來拆解它們的 cons 嗎(first-thing 和 rest-of-it)?
(first-thing '(first second third)) ; => FIRST
(rest-of-it '(first second third)) ; => (SECOND THIRD)
同樣地,Common Lisp 的某些內建函式在這裡也許能幫上忙。