Common_Lisp

**Common Lisp**，縮寫為 **CL**（不是組合邏輯的縮寫）是 Lisp 程式語言的一種方言，由 ANSI INCITS 226-1994 (R2004)（前身為 ANSI X3.226-1994 (R1999)），所定義的語言規範標準。Common Lisp HyperSpec 是源自於 ANSI Common Lisp 標準的網頁超連結版本。

CL 語言是為標準化和改良 Maclisp 而開發的後繼者。到 20 世紀 80 年代初，幾個工作群組已經在設計 MacLisp 各種後繼者，例如：Lisp Machine Lisp（又名 ZetaLisp），Spice Lisp，NIL 和 S-1 Lisp。CL 是為了標準化和擴展此前眾多的 MacLisp 分支而開發，它本身並非具體的實作，而是對語言設立標準的規範。有數個實作符合 Common Lisp 規範，其中包括自由和開源軟體，以及商業化產品。CL 支援了結構化、函數式和物件導向編程等範式。相對於各種嵌入在特定產品中的語言，如 Emacs Lisp 和 AutoLISP，Common Lisp 是一種用途廣泛的程式語言。不同於很多早期 Lisp，Common Lisp 如同 Scheme，其中的變數是預設為詞法作用域的。

身為一種動態程式語言，它有助於進化和增量的軟體開發，並將其迭代編譯成高效的執行程式。這種增量開發通常是互動持續地改善，而不需中斷執行中的應用程式。它還支援在後期的分析和優化階段添加可選的型別註記與轉型，使編譯器產生更有效率的代碼。例如在硬體和實作的支援範圍內，`fixnum` 能儲存一個未封裝整數，允許比大整數或任意精度類型更高效率的運算。同樣地，在每個模組或函式的基礎上可聲明優化，指示編譯器要編譯成哪一類型的安全級別。

CL 包含了支援多分派和方法組合的物件系統，縮寫為 CLOS，它通常以元物件（Metaobject）協定來實現。

CL 藉由標準功能進行擴展，例如 Lisp 宏（編譯時期程式自身完成的代碼重排（compile-time code rearrangement accomplished by the program itself））和閱讀器宏（賦予使用者自訂的語法以擴充具特殊意義的符號（extension of syntax to give special meaning to characters reserved for users for this purpose））。

CL 為 Maclisp 和約翰・麥卡錫的原創 Lisp 提供了一些向下相容性。這允許較舊的 Lisp 軟體移植到 Common Lisp 之上。

## 歷史

在 1981 年，ARPA 管理者 Bob Engelmore 最初發起了關於 Common Lisp 的工作，開發一個單一的社群標準 Lisp 方言。大多數最初的語言設計是通過電子郵件完成的。在 1982 年，Guy L. Steele Jr. 於 1982 年度 LISP 和函式語言程式設計研討會上首次給出了 Common Lisp 的概述。

在 1984 年，首個語言文件被出版為《Common Lisp 語言》，第一版（也叫做 CLtL1）。在 1990 年，第二版（也叫做 CLtL2）出版了，它結合了在 ANSI Common Lisp 標準化過程中對語言做的很多變更：擴充的 `LOOP` 語法、Common Lisp 物件系統、用於錯誤處理的條件系統、到精美列印的介面等等。但是 CLtL2 不描述最終的 ANSI Common Lisp 標準，因而不是 ANSI Common Lisp 的文件。在 1994 年，最終的 ANSI Common Lisp 標準出版了。自從出版之後標準就沒有更新。對 Common Lisp 的各種擴充和改進（例如 Unicode、並行、基於 CLOS 的 IO）都由實現和函式庫來提供。

## 語法

Common Lisp 是 Lisp 編程語族的一種方言；它使用 S - 表達式來表示原始碼和資料結構。函式呼叫、宏形式和基本形式都以列表來編寫，列表的第一項是函式名稱，如以下範例：

## 數據類別

Common Lisp 擁有豐富的資料類別。

### 純量型別

數值型別包括整數，分數，浮點數和複數。Common Lisp 使用大數（bignums）來表示任意大小和精度的數值。
分數型別確切地代表分數，很多語言並不具備這種能力。Common Lisp 會自動將數值轉換成適當的型別。有許多方式取捨數值，函式 `round` 將參數四捨六入為最接近的整數，逢五則取偶整數。`truncate`，`floor` 和 `ceiling` 分別朝向零，向下或向上取整數。所有這些函式將捨去的小數當作次要值返回。

例如，`(floor -2.5)` 產生 -3, 0.5；`(ceiling -2.5)` 產生 -2，-0.5；`(round 2.5)` 得到 2,0.5；和 `(round 3.5)` 得到 4，-0.5。

Common Lisp 字元型別不限於 ASCII 字元，因為在 ASCII 出現前 Lisp 就已經存在了。大多數現代實現允許 Unicode 字元。

符號（Symbol）型別是 Lisp 語言共有的，而在其它語言中較少見。一個符號是個命名唯一的物件，它擁有幾個部份：名稱，值，函式，屬性列表（property list）和套件。其中，值單元和函式單元是最重要的。Lisp 中的符號通常類似於其它語言中的標識符（identifier）用法：儲存變數的值；然而還有很多種用途。一般來說，對一個符號求值時會得到以該符號為變數名稱的值，但也有例外：譬如在關鍵符套件中的符號，形如 `:foo` 的符號值就是它本身（自我評估的，self-evaluating），而符號 `T` 和 `NIL` 則用於表示布爾邏輯值真與假。Common Lisp 可設計容納符號的命名空間，稱為「套件」（package）。

### 數據結構

Common Lisp 的序列型別包括列表、向量、位元向量和字串。有許多函式可對應不同型別的序列進行操作。

CL 如同所有 Lisp 方言，列表由點對（conses）組成，有時稱為 cons 單元、序偶或構對。一個點對是帶有兩個儲存槽的數據結構，分別稱為 car 和 cdr。列表就是一條點對的串列，或只是空串列。每一個點對的 CAR 會參照列表的成員（可能是另一個列表）。而除了最後一個的 CDR 參照到 `nil` 值之外，其餘的 CDR 都會參照下一個點對。Conses 也能輕易地實現樹和其它複雜的數據結構；儘管一般建議以結構體或是類別的實例來代替。利用點對能夠建立循環形的資料結構。

CL 支援多維陣列，且如需要能動態地調整陣列大小。多維陣列常用於數學中的矩陣運算。向量就是一維陣列。陣列可載入任何型別（甚至於混合的型別）的成員，或只專用於特定某一型別的成員，例如由整數構成的位元向量。許多 Lisp 實作會根據特定型別，對陣列的操作函式進行優化。兩種特定型別的專用陣列是內建的：字串和位元向量。字串是由許多字元構成的向量，而位元向量是由許多位元構成的向量。

雜湊表儲存物件之間的關聯，任何物件都可以作為雜湊表的鍵或值。和陣列一樣，雜湊表可依需求自動調整其大小。

套件是一組符號的集合，主要用於將程式的個別部份區分命名空間。套件能匯出一些符號，將它們作為共享介面的某一部份，也可以匯入其它套件參照並概括承受其中的符號。

CL 的結構體（Structures）類似於 C 語言的 structs 和 Pascal 的 records，是一種任由使用者發揮的複雜資料結構定義，表示具有任意數量和任何型別的欄位（也叫做槽）。結構允許單一繼承。

類別（Class）在後期被整合進 Common Lisp 中，有些概念與結構體重疊，但類別提供了更多的動態特性和多重繼承（見 CLOS）。由類別建立的物件稱為實例。有個特殊情況是泛型（Generics）的雙重角色，泛型既是函式，也是類別的實例物件。

### 函式

Common Lisp 支援頭等函式（亦即函式可當成資料類型來處理）。例如編寫以其它函式當作一個函式的參數，或函式的傳回值也是函式，利用函式的結合來描述常用的操作。CL 函式庫高度依賴於這樣的高階函式變換。舉例而言，`sort` 函式可將關係運算子作為參數，並選用如何取鍵的函式作為參數。如此一來不但能對任何型別的資料排序，還能根據取用的鍵值對資料結構作排序。

對函式求值的模型非常簡單。當求值器遇到一個形式如 `（F a1 a2 ...）` 時，那麼名稱為 `F` 的符號會被假定是以下三種狀況之一：

* 是否為基本運算子？（在固定列表中檢查，ANSI LISP 標準有 25 個特殊運算子號）
* 是否為宏運算子？（必須先前已經存有定義）
* 是否為函式名稱？（預設，可以是符號，也可能是以 `lambda` 符號開頭的子形式。）

如果 `F` 符號是三者其中之一，則求值器判定它是個函式，找到此函式的定義內容，然後以從左到右的次序來評估參數 `a1，a2，...，an` 的值，並且使用這些值進行運算，以函式定義中最後一個評估的結果作為傳回值。

 

#### 定義函式

宏 `defun` 用來定義函式。函式定義給出了函式名，參數名和函式體：

函式定義中可以包括「聲明」，它可以指示編譯器最佳化設定或參數的資料類型等。還可以在函式定義中包括「文件字串」（docstring），Lisp 系統用它們形成互動式文件：

匿名函式用 `lambda` 表達式定義。Lisp 編程頻繁使用高階函式，以匿名函式作為其參數的作法十分有效。

還有一些有關於函式定義和函式操作的運算子。如，運算子 `compile` 可以用來重新編譯函式。（一些 Lisp 系統預設下在直譯器里執行函式，除非指示編譯它；其他 Lisp 系統在函式輸入時即被編譯。）

 

#### 定義泛化函式及方法

`defgeneric` 宏用來定義泛化函式，而 `defmethod` 宏則用來定義方法。泛化函式是一些方法的集合。方法可依照 CLOS 標準類別、系統類別、結構類別或物件，以特定方式處理它們所使用的參數。許多型別都有相對應的系統類別。當呼叫泛化函式數時，多樣派發（multiple-dispatch）將會依型別確定要應用的有效方法。如下列範例展示了對不同型別的參數如數值、向量或字串，設計對應的 `add` 方法將兩個物件相加的動作。

泛化函式也是第一類資料類別。除了上面陳述之外，泛化函式和方法還有更多的特性。

#### 函式命名空間

函式的命名空間與資料變數的命名空間是分離的。這是 Common Lisp 和 Scheme 程式語言的一個重要不同之處。在函式命名空間定義名字的運算子包括 `defun`、`flet` 和 `labels`。

要用函式名把函式作為參數傳給另一個函式，必須使用 `function` 特殊運算子，通常簡略為 `#'`。上文第一個 `sort` 的例子中，為了參照在函式命名空間名為 `>` 的函式，使用了代碼 `#'>`。

Scheme 程式語言的求值模型更簡單些：因為只有一個命名空間，式（form）中所有位置都被求值（以任意順序）-- 不僅是參數。所以以一種方言（dialect）寫就的代碼往往令熟悉其它方言程式設計師感到迷惑。例如，許多 CL 程式設計師喜歡使用描述性的變數名如 "list" 或 "string"，在 Scheme 中這將導致問題，因為它們可能局部覆蓋了函式名字。

為函式提供分離的命名空間是否有益是 Lisp 社群不斷爭論的主題之一，常被稱為「Lisp-1 與 Lisp-2 辯論」。這些名稱出現於 Richard P. Gabriel 和 Kent Pitman 在 1998 年的一篇論文，其中廣泛的比較了這兩種方法。

#### 多值

Common Lisp 支援多值的概念，任何表達式經過評估之後必定會有一個主要值，但它也可能擁有任何數量的次要值，讓感興趣的呼叫者接收和檢查。這個概念與回傳列表值不同，因為次要值是備選用的，並通過專用的側面通道來傳遞。也就是說如果不需要次要值，則呼叫者完全不需要知道它們的存在，這是偶爾需使用額外而非必要的資訊，一個方便的機制。

* 例如 `TRUNCATE` 函式對給定數值取最接近的整數。然而，它也會返回一個餘數作為次要值，使呼叫者確定有多少數值被捨棄了。它還支援可選用的除數參數，可顯明地表達帶餘除法：

* `GETHASH` 回傳雜湊表中依鍵作搜尋的值，否則返回預設值，還有一個指出是否找到該值的布爾輔助值。因此不論搜尋結果 (找到鍵的對應值或預設值) 是否成功，原始碼都可以直接使用它，但如果要求能區別搜尋結果的情況時，它可以檢查輔助的布爾值並做出適當反應。相同的函式調用支援兩種使用情境，不會受到另一個的負擔或約束影響。

一些標準形式支援多值，最常見的是用來存取次要值的 `MULTIPLE-VALUE-BIND` 基本運算子和用於返回多值的 `VALUES`：

### 其它類別

Common Lisp 中的其他資料類別包括：

* 雜湊表是 Common Lisp 提供的用於儲存「鍵值對」的資料類別。在雜湊表中任何物件都可以作為鍵或者值。雜湊在必要時候會自動調整大小。
* 路徑名稱（Pathnames）表示檔案系統中的檔案和目錄。Common Lisp 的路徑名稱型別比大多數作業系統的檔名慣例更為通用，提高 Lisp 編程在不同系統存取檔案能力的可攜性。
* 輸入流和輸出流（Input/output streams）表示二進位或文字資料的源頭和出口，例如顯示終端或開啟的檔案內容。
* Common Lisp 有內建的偽亂數產生器（PRNG）。隨機狀態物件代表可重複使用的偽亂數起源，允許使用者設定 PRNG 種子或使其重設序列。
* 條件（Conditions）是用於表示程式回應的錯誤、異常和其它「有趣」事件的型別。
* 類別是第一類物件，它們自身是被稱為元類別（metaclass）的實例。
* 讀取字表（Readtables）是控制 Common Lisp 直譯器（`read` 函式）如何解析原始碼文字的物件型別。開發人員可操控 Lisp 編程在讀取原始碼時要使用哪一個讀取字表，改變或擴展 Lisp 的語法。

## 作用域

與許多其它程式語言中的程式一樣，Common Lisp 編程使用名稱來參照變數、函式和許多其它類型的實體。被命名的參照只在其作用域中有用。名稱與參照實體之間的關聯稱為綁定。作用域是指確定名稱具有特殊綁定的情況。

### 作用域的決定

在 Common Lisp 中需要決定作用域的情況包括：

* 參照在表達式中的位置，如果它位於複合表達式的最左側，它指的是一個基本運算子、一個宏或是函式的綁定，否則是一個變數綁定或其它的東西。
* 依參照如何出現在表達式中，例如 `(go x)` 表示將控制跳轉到 `x` 標籤的位置，而 `(print x)` 表示 `x` 變數。這兩個 `x` 的作用域可以在程式的相同區域處於活動狀態，因為 `tagbody` 標籤的 `x` 與 `x` 變數名稱位於分開的命名空間中。基本運算子或宏形式可完全控制其語法中所有符號的含義。例如在 `(defclass x (a b) ())` 表達式中，類別定義 `(a b)` 是基本類別的列表，因此會在類別的命名空間中搜尋這些名稱；`x` 並非參照到現有的綁定，而是源自於 `a` 和 `b` 的新類別名稱。這些事實純粹由 `defclass` 的語義表示得出。這表達式的唯一事實是 `defclass` 參照一個宏綁定；其中的一切都由 `defclass` 決定作用域。
* 參照在程式中的位置。比如若對 `x` 變數的參照被含括在一個綁定結構中，例如以 `let` 綁定對 `x` 的定義，則該參照的效用發生在該綁定建立的作用域內。
* 對於變數的參照，如果變數符號已被聲明為 `special`，無論這聲明是在原生的或在全域中。這將使參照依據其位於詞法或動態的環境中來參照它。
* 參照解決的環境的具體實例。一個環境是在執行期將符號與綁定對應起來的字典。每種參照會使用自己的環境。詞彙的變數會在詞彙的環境中被參照，可將同一個參照與多個環境相關聯起來。例如由遞迴或使用多執行緒，一個函式的多次觸發可以同時存在。這些觸發會共享相同的程式文字，但每個都有自己的詞彙環境實例。

要理解符號參照到什麼實體，Common Lisp 開發人員必須知道參照是屬於哪一種作用域，如果它是一個變數的參照，那它是處於什麼樣的（動態或詞法的）作用域中？以及在執行期的情況，參照在什麼環境中被參照，綁定是在哪裡被引入到環境等等。

### 環境

#### 全域

Lisp 中的一些環境總是存在於全域作用域之中， 例如定義了一個新型別，那麼以後在任何地方都會知道它。 
該類型別的參照會從全域作用域中的環境去尋找。

#### 動態

環境在 Common Lisp 中有一種類型是動態環境。在這種環境中建立的綁定具有動態的作用域，這表示某些構造例如 `let`，會在執行的起點就先建立綁定，而在該構造完成執行時消失：它的生命週期依附著這區塊動態地觸發和停用。然而動態綁定不僅在該區塊中可見；對於從該區塊中調用的所有函式也是可見的。這樣的可見性被稱為不定的作用域。具有動態（依附區塊的觸發和停用相關的生命週期）和不定作用域（從該區塊調用的所有函式可見）的綁定，被稱為具有動態作用域。

Common Lisp 支援動態作用域的變數，也稱為特殊變數。有些其它類型的綁定也必須是動態作用域的，例如重新啟動和捕獲標籤。函式綁定不能以 `flet`（僅提供詞法範圍的函式綁定）進行動態作用域，但可以將函式物件（Common Lisp 中的第一類物件）分配給動態作用域的變數，在動態作用域內使用 `let` 綁定，然後再以 `funcall` 或 `APPLY` 調用。

動態作用域非常有用，因為它將參照的解析度和規律添加到全域變數中。計算機科學中的全域變數被認為是潛在的錯誤來源，因為它們可能導致模組之間存有特殊隱蔽的溝通管道，從而導致令人驚訝而不在預期中的互動作用。

在 Common Lisp 中，只有頂層綁定的特殊變數就像其它程式語言中的全域變數一樣。它可以儲存一個新的值，該值僅替換頂層綁定中的值。造成使用全域變數的核心錯誤，是粗心的替代了全域變數值。但是，使用特殊變數的另一種方法是，在表達式中給它一個新的區域綁定。這有時被稱為「重新綁定」變數。動態作用域中對變數的綁定，會建立一個臨時的新記憶體位置給予該變數，並將該名稱與該位置相關聯。當該綁定有效，對該變數的所有參照都指向新的綁定；之前的綁定則是被隱藏起來的。當綁定表達式的執行結束時，臨時的記憶體位置消失，而舊綁定浮現出來，變數的原始值依舊完好無損。當然，同一變數的多個動態綁定可以巢狀。

在支援多緒的 Common Lisp 實作中，動態作用域是針對每個執行緒的。因此，特殊變數是當成執行緒區域儲存的抽象化。如果一個執行緒重新綁定了特殊變數，則此重新綁定對其它執行緒中的該變數沒有作用。儲存在綁定中的值只能由建立該綁定的執行緒取得。如果每個執行緒綁定一些特殊變數 `*x*`，則 `*x*` 的行為就像執行緒在本地中儲存一樣。在沒有重新綁定 `*x*` 的執行緒中，它的行為就像一個普通的全域變數：所有這些執行緒的參照都會指向 `*x*` 的頂層綁定。

動態變數可以用來擴展執行上下文，並附加上下文訊息，這些資訊在函式之間隱含地傳遞，而不必顯示為額外的函式參數。當執行控制的轉移必須穿過不相關的代碼層時，不能藉由額外參數來擴展傳遞附加數據，所以這是非常有用的。這樣的情況通常需要一個全域變數，必須能夠被儲存和恢復，以便在遞迴時不會中斷：動態變數的重新綁定可以處理此情形。該變數必須是執行緒區域的（或必須使用大的互斥，mutex），因此這個情況不會在執行緒下斷開：動態作用域的實作也可以處理此情形。

在 Common Lisp 函式庫中有很多標準的特殊變數。例如，所有標準 I/O 流都儲存在頂層為眾所熟知的特殊變數的綁定中，即 `*standard-output*`。

假設有個 foo 函式寫入標準輸出：

要擷取其輸出中的字串，`*standard-output*` 可以被綁定到一個字串流，並調用它：

```
 -> "Hello, world" ; gathered output returned as a string
```

#### 區域

Common Lisp 支援詞法環境。形式上，詞法環境中的綁定具有詞法作用域，並可能具有不定的範圍或動態的範圍，取決於命名空間的類型。詞法作用域實際上表示可見性被限制在綁定建立的區塊中。參照沒有以文字（即詞法地）嵌入在該區塊中，根本看不到該綁定。

`TAGBODY` 中的標籤會具有詞法作用域。如果 `(GO X)` 表達式實際上沒有嵌入到其中，則它會發生錯誤。`TAGBODY` 包含標籤 `X`。但是當 `TAGBODY` 執行終了時，標籤的綁定就會消失，因為它們具有動態作用域。如果以調用一個詞法閉包重新進入該代碼區塊，那麼這個閉包的內文無法藉由 `GO` 將控制轉移到標籤中：

執行 `TAGBODY` 時，它首先評估以 `setf` 形式指向函式的特殊變數 `*stashed*`，然後 `(go end-label)` 將控制項轉移到終了標籤，跳過代碼 `(print "Hello")`。由於終了標籤位於 `TAGBODY` 的末端，於是終止並返回 `NIL` 值。假設現在調用先前指向的函式：

這種狀況是錯誤的。一個實作的錯誤回應該包含錯誤條件訊息，例如「GO: tagbody for tag SOME-LABEL has already been left」。該函式嘗試評估 `(go some-label)`，它是詞法地嵌入到 `TAGBODY` 中並解析為標籤。然而 `TAGBODY` 被跳過了而沒有執行（其作用域已經結束），故無法再轉移控制。

Lisp 中的區域函式綁定具有詞法作用域，預設情況下變數綁定也同樣為詞法作用域。與 `GO` 標籤對比，它們的作用域是範圍不定的。當一個詞法的函式或變數綁定時，既然可以對其參照參照，該綁定就會持續存在，即使在建立該綁定的結構已經終止後。參照到詞法變數和函式，在其建立結構終止後，可以藉由詞法的閉包來實現。

Common Lisp 對於變數的預設模式是詞法綁定。對於個別符號可用區域聲明，或全域的聲明，來切換成動態作用域。而後者可能隱含地透過如 `DEFVAR` 或 `DEFPARAMETER`，這樣的構造使符號成為全域可見的。Common Lisp 編程中慣例以開頭和結尾星號 `*`，將特殊變數（即處於動態作用域的）包括起來，這稱為「耳罩慣例」。遵循此慣例的效果，即為特殊變數建立了一個單獨的命名空間，則應該處於詞法作用域的變數不會被意外地特殊化。

幾個原因使得詞法作用域有用。

首先，變數和函式的參照可以被編譯成高效的機器碼，因為執行期環境的結構相對簡單。在許多情況下它可以優化堆疊儲存，因此開啟和關閉的詞法作用域前置開銷最小。即使在必定要產生完整閉包的情況下，存取閉包的環境仍然是有效率的；每個變數通常會轉成一個綁定向量之中的偏移量，因此變數的參照就成為簡單的載入，或是以基底 - 加 - 偏移尋址模式表示的儲存指令。

其次詞法作用域（與不定範圍結合）可以創造出詞彙閉包，從而產生了中心以函式作為第一類物件的編程範式，這是函數式編程的根本。

第三，也許最重要的是，即使沒有用到詞法的閉包，詞法作用域的運用，會將程式模組與不需要的互動影響隔離開來。由於可見性受到限制，詞法變數是私有的。如果一個模組 A 綁定一個詞法變數 X，並呼叫另一個模組 B，則參照 B 其中的變數 X，不會被意外地解析成在 A 中綁定的 X。B 根本無法存取 X。對於需使用變數進行有規則的互動作用情況，Common Lisp 提供了特殊變數。特殊變數允許一個模組 A 設定變數 X 的綁定，使另一模組 B 能看見並從 A 調用其中的 X。能夠做到這一點是個優勢，能夠防止它發生也是個優勢；因此 Common Lisp 同時支援詞法和動態作用域兩者。

## 巨集

Common Lisp 中的巨集是獨一無二的，和 C 語言中的巨集的機制相同，但是在巨集擴充的過程中由於可以使用所有現有的 Common Lisp 功能，因此巨集的功能就不再僅限於 C 語言中簡單的文字替換，而是更進階的代碼生成功能。巨集的使用形式和函式一致，但是巨集的參數在傳遞時不進行求值，而是以_字面形式_傳遞給巨集的參數。巨集的參數一旦傳遞完畢，就進行_展開_。展開巨集的過程將一直進行到這段代碼中的所有巨集都展開完畢為止。巨集完全展開完畢後，就和當初直接手寫在此處的代碼沒有區別，也就是嵌入了這段代碼上下文中，然後 Lisp 系統就對完整的代碼上下文進行求值。

Lisp 巨集表面上類似於函式的使用，但並不是會直接被求值的表達式，它代表程式原始碼的字面轉換。巨集將包含的代碼內容當作參數，將它們綁定到巨集自身的參數，並轉換為新的原始碼形式。這個新的原始碼形式也能夠使用一個巨集，然後重複擴展，直到新的原始碼形式沒有再用到巨集。最終形式即運行時所執行的原始碼。

Lisp 巨集的典型用途：

* 新的控制結構（例如：循環結構，分支結構）
* 作用域和綁定結構
* 簡化複雜和重複原始碼的語法
* 以編譯時期副作用定義的頂層形式
* 資料驅動的編程
* 內嵌式的特定領域語言（例如：SQL，HTML，Prolog）
* 隱式的結束形式

各種標準的 Common Lisp 功能也需要巨集來實現，如以下所列：

* 標準的 `setf` 抽象化，允許客製化編譯時賦值 / 存取運算子的擴展形式
* `with-accessors, with-slots, with-open-file`，與其它相似的 WITH 巨集
* 依實作的，`cond` 是建立在基本運算子 `if` 之上的巨集；條件分支 `when` 和 `unless` 也是由巨集所構成
* 強大的 `loop` 迭代巨集語法

 
巨集是以 `defmacro` 來定義。基本運算子 `macrolet` 允許定義區域性的（詞法作用域）巨集。也可以使用 `define-symbol-macro` 和 `symbol-macrolet`，為符號定義巨集。Paul Graham 的《On Lisp》書籍詳細介紹了 Common Lisp 中巨集的用途。Doug Hoyte 的《Let Over Lambda》書籍擴展了關於巨集的討論，聲稱「巨集是 lisp 編程最獨特的優勢，和任何程式語言的最大優點」。Hoyte 提供了迭代開發的幾個巨集範例。

 

### 使用巨集定義控制結構的範例

Lisp 編程人員能夠利用巨集來創造新的語法形式。典型的用途是建立新的控制結構。 
此處提供一個 `until` 循環結構的巨集範例，其語法如下：

`until` 巨集的定義：

`tagbody` 是一個基本的 Common Lisp 運算子，它提供了命名標籤的能力，並使用 `go` 形式跳轉到這些標籤。 
反引號 `` ` `` 的用途類似單引號 `'`（相當於 quote 函式，參照形式當成資料而不求值），它還是一個可作代碼模板 
的符號，其中需要求值的形式參數以逗號 `,` 開頭填入模板；而以 `,@` 符號為開頭的形式參數，其中巢狀的內容會 
再被拆解評估。`tagbody` 形式測試結束條件。如果條件為真，則跳轉到結束標籤；否則執行主體的代碼， 
然後跳轉到起始標記。

上述 `until` 巨集的使用範例：

利用 `macroexpand-1` 函式可以展開巨集的代碼。上例經過展開後的代碼如下所示：

在巨集展開期間，變數 `test` 的值為 `(= (random (10) 0)`，變數 `body` 的值為 `((write "Hello"))`，是一個列表形式。

符號通常會自動轉成英文大寫。這個 `TAGBODY` 擴展中帶有兩個標籤符號，由 `GENSYM` 自動產生，並且不會被拘束到任何套件中（為待綁定的暫時自由變數）。兩個 `go` 形式會跳轉到這些標籤，因為 `tagbody` 是 Common Lisp 中的基本運算子（並不是巨集），因此它沒有其它內容會再展開。展開形式中用到的 `when` 巨集也會再展開。將一個巨集完全展開為原始碼的形式，被稱為代碼走開（code walking）。在已被完全展開的形式中，`when` 巨集會被基本運算子 `if` 代換：

原始碼中所有包含的巨集必須在展開之後，才能正常地評估或編譯。巨集可以理解為接受和返回抽象語法樹（Lisp S - 表達式）的函式。
這些函式會在求值器或編譯器調用之前，將巨集內容轉換為完整的原始碼，Common Lisp 中所提供的任何運算子都可用於編寫巨集。

### 變數捕捉和覆蓋

因為 Common Lisp 的巨集在展開完畢後就完全嵌入了所處的代碼上下文中，相當於以字面形式書寫同樣的代碼，因此在巨集展開代碼中與上下文代碼中相同的符號就會覆蓋上面的參照，稱為_變數捕捉_。如果 Common Lisp 的巨集展開代碼中的符號，與調用上下文中的符號相同時，通常稱為變數捕捉。對於巨集，程式員可在其中建立具有特殊含義的各種符號。變數捕捉這個術語可能有點誤導，因為所有的命名空間都有非預期捕捉到相同符號的弱點，包括運算子和函式的命名空間、`tagbody` 標籤的命名空間、`catch` 標記，條件處理程式和重新啟動的命名空間。

變數捕捉情況會使軟體產生缺陷，發生原因可分為下列兩種方式：

* 第一種方式是，巨集擴展可能無意中產生一個符號參照，這個巨集的作者設想符號是在全域命名空間中被解析，但是巨集的展開代碼恰好提供了一個會遮蔽的區域定義，而取用區域定義的參照；此情況稱為類型一捕捉。
* 第二種方式，類型二捕捉正好相反：巨集的某些參數來自於巨集調用者提供的代碼片段，這些代碼片段被寫入，而且參照周圍的綁定。然而，巨集將這些代碼片段插入到一個展開中，而該展開有自己的綁定定義，這些綁定意外捕捉了這些參照其中的一部份。

Lisp 語族的 Scheme 方言提供了一個巨集寫入系統，它提供了參照透明度來消除這兩種類型的捕捉問題。這樣的巨集寫入系統有時被稱為「保健的」，特別是其支持者（認為不能自動解決捕捉問題的巨集系統是不正確的）。

在 Common Lisp 中巨集的保健，則以兩種不同方式擔保。

一種方法是使用 `gensym`：保證只產生唯一的符號在巨集擴展中使用，而不受到捕捉問題的威脅。在巨集定義中使用 `gensym` 是件零瑣的雜務，但利用巨集可簡便 `gensym` 的實例化和使用。`gensym` 很容易解決類型二的捕捉問題，但它們不能以相同方式來處理類型一的捕捉問題，因為巨集展開不能重新命名，周圍代碼中參照所捕捉到的介入符號（被區域定義遮蔽的全域符號）。`Gensym` 可以為巨集擴展所需要的全域符號，提供穩定的別名。巨集擴展使用這些秘密別名而非眾所熟知的名稱，因此重新定義熟知的名稱對巨集並沒有不利影響。

另一種方法是使用套件，在自己套件中定義的巨集，在套件中的擴展可以簡單地使用內部符號。使用套件能處理類型一和類型二捕捉問題。然而，套件不能解決參照到 Common Lisp 標準函式和運算子的類型一捕捉，因為用套件來解決捕捉問題，只能解析其私有符號（套件中的符號不是匯入的，或能被其它套件看見的）；而 Common Lisp 函式庫的符號都是外部共享的，並經常匯入到使用者定義套件中，或在使用者定義套件中是可見的。

以下範例是在巨集展開時，運算子命名空間中發生的不預期捕捉：

`until` 巨集將展開為一個調用 `do` 功能的形式，該形式旨在參照 Common Lisp 標準的 `do` 巨集。但在這種情況下，`do` 可能有完全不同的含義，所以 `until` 可能無法正常工作。

Common Lisp 禁止對標準運算子和函式的重新定義，避免它們的遮蔽來解決此類問題。因為前例重新定義了 `do` 標準運算子，實際上是一個不合格的代碼片段，Common Lisp 實作應當對前例進行診斷並拒絕其重新定義。

## 條件系統

條件系統負責 Common Lisp 中的異常處理。它提供條件，處理程式和重新啟動。條件是描述異常情況（例如錯誤）的物件。如果一個條件訊號被發出了，Common Lisp 系統將搜尋此條件類型的處理程式並調用它。處理程式現在可以搜尋重新啟動（restart），並使用這些重新啟動之一來自動修復當前的問題，利用條件類型與條件物件的一部份所提供的任何相關資訊等，並調用相對的重新啟動函式。

如果沒有處理程式的代碼，這些重新啟動可以對使用者顯示選項（作為使用者介面的一部分，例如除錯器），讓使用者選擇和調用提供的重新啟動選項。由於條件處理程式在錯誤的上下文中被調用（堆疊仍未清空），在許多情況下對錯誤的完全回復處理是可行的，而不同於其它的異常處理系統可能已經終止了當前的執行程式。除錯器本身也可以使用 `*debugger-hook*` 這個動態變數來客製或替換。在 `unwind-protect` 中寫明的代碼，譬如作為終結，也會適當地被執行例外。

以下範例（使用 Symbolics Genera）中，使用者從讀取求值列印循環（REPL，即頂層）呼叫一個 test 函式，嘗試開啟一個檔案，而當此檔案不存在時，Lisp 系統則呈現四個重新啟動的選項。使用者選擇了 `s-B:` 這個重新啟動選項，並輸入不同的路徑名稱（以 lispm-init.lisp 取代了 lispm-int.lisp）。使用者執行的原始碼中並沒有包含任何錯誤處理。整個錯誤處理和重新啟動代碼是由 Lisp 系統本身所提供，它可以處理和修復錯誤，而不終止使用者執行中的程式碼。

## Common Lisp 物件系統 (CLOS)

Common Lisp 包含了物件導向編程的工具包，Common Lisp 物件系統或簡稱為 CLOS，它是最強大的物件系統之一。Peter Norvig 解釋了在具備 CLOS 的動態語言中，如何使用其功能（多重繼承，混合，多方法，元類，方法組合等），以達成設計模式更簡單的實現。曾經有幾個擴展被提出來作為 Common Lisp ANSI 標準的物件導向編程應用，而最終採用了 CLOS 作為 Common Lisp 的標準物件系統。

CLOS 是個具有多分派和多重繼承的動態物件系統，並且與靜態語言（如 C++ 或 Java）中的 OOP 設施截然不同。作為動態物件系統，CLOS 允許在執行時期對泛化函式和類別進行更改。方法可以添加和刪除，類別可以添加和重新定義，物件可依照類別的變動更新，而物件所屬的類別也可以更改。CLOS 已經整合到 ANSI Common Lisp 中。通過函式可以像普通函式一樣使用，並且是第一類資料類型。每個 CLOS 類別都已被整合到 Common Lisp 類別系統中。

Common Lisp 中許多型別都有一個相對應的類別。規範中沒有說明 CLOS 實作的條件，CLOS 進階用法的可能性並不是 Common Lisp 的 ANSI 標準，CLOS 的用處有更多的潛能。一般 Common Lisp 實作將 CLOS 用於路徑名稱、流、輸入 / 輸出、條件，CLOS 本身等等。

## 編譯器和直譯器

早期 Lisp 方言的幾個實現提供了直譯器和編譯器，不幸的是兩者之間語義是不同的。這些早期的 Lisps 在編譯器中實作了詞法作用域，在直譯器中實作了動態作用域。Common Lisp 要求直譯器和編譯器兩者皆預設使用詞法作用域。Common Lisp 標準描述了直譯器和編譯器的語義。可以使用 `compile` 函式呼叫編譯器，來編譯各個函式，並使用 `compile-file` 函式編譯原始碼檔案。Common Lisp 允許類型別聲明，並提供產生編譯器代碼的選擇。後者有優化參數可選擇 0（不重要）和 3（最重要）之間的值：會影響到_執行速度_，_空間_，_安全性_，_除錯_和_編譯速度_。

還有一個函式用來評估 Lisp 原始碼：`eval`。`eval` 將原始碼視為預先解析的 S - 表達式，而不像其它語言只當成字串處理。這樣可以用常見的 Lisp 函式來建構代碼，用來構造列表和符號，然後以 `eval` 函式來評估該代碼。幾個 Common Lisp 實作（如 Clozure CL 和 SBCL）以它們的編譯器來實現 `eval`。這樣子即使用 `eval` 函式進行評估時，原始碼也是會被編譯。

使用 `compile-file` 函式呼叫檔案編譯器，產生的編譯檔稱為 fasl（快速載入，fast load）檔案。這些 fasl 檔案和原始碼檔案都能以 `load` 功能，載入到運行的 Common Lisp 系統中。根據實作，檔案編譯器會產生位元組碼（例如 Java 虛擬機），C 語言代碼（然後以 C 編譯器編譯）或直接使用原生機器碼。

即使原始碼已經完全被編譯，Common Lisp 實作可以和使用者互動。因此，Common Lisp 的互動介面並非類比於直譯指令碼的設想。

這個語言區隔了讀取時期、編譯時期、載入時期和執行時期，並讓使用者編程在需求的步驟中，也依照這些區別來執行所需的處理種類。

有些特殊的運算子特別適合互動式開發；譬如，若 `defvar` 還沒有任何綁定時，則只對提供給它的變數進行賦值；而 `defparameter` 總是會執行賦值。在實時映像中互動地評估，編譯和載入代碼時，這種區別是有用的。還有一些功能也幫助撰寫編譯器和直譯器。符號由第一類物件所組成，可由使用者的代碼直接操縱。`progv` 基本運算子允許以編程方式創造詞法綁定，也可以運用套件。Lisp 編譯器本身在運行時可用來編譯檔案或單一函式，這使得 Lisp 成為其它程式語言的中途編譯器或直譯器變得容易。

## 編程原始碼範例

### 生日悖論

以下程式計算一個房間內最小數量的人，其完全獨特生日的概率小於 50％（生日悖論，1 人的概率明顯為 100％，2 為 364/365 等）。答案是 23。

使用 REPL 呼叫函式用例：

### 排序列表

我們定義一個人員類別和一個顯示姓名和年齡的方法。接下來，我們將一組人定義為人物物件列表。然後我們遍歷排序列表。

它以降序列印三個名字。

### 平方指數

使用 LOOP 宏：

使用範例：

與內建的求冪函式比較：

### 尋找可用 shell 的列表

`WITH-OPEN-FILE` 是打開一個檔案並提供一個串流的宏。在這個形式返回的時候，這個檔案自動關閉。`FUNCALL` 呼叫一個函式物件。`LOOP` 收集匹配謂詞的所有行：

函式 `AVAILABLE-SHELLS` 呼叫上述 `LIST-MATCHING-LINES` 函式，並以一個路徑名和作為謂詞的一個匿名函式作為參數。這個謂詞返回一個 shell 的路徑名或 `NIL`（如果這個字串不是一個 shell 的路徑名）：

例子結果（在 Mac OS X 10.6 之上）：

## Common Lisp 與 Scheme 的比較

Common Lisp 經常和 Scheme 互相比較，因為它們是最受歡迎的兩種 Lisp 方言。Scheme 早於 CL，不僅來自同一個 Lisp 傳統，而且是 Guy L. Steele 與 Gerald Jay Sussman 設計的，Guy L. Steele 也擔任過 Common Lisp 標準委員會的主席。

Common Lisp 是一種普遍用途的的程式語言；相反的如 Emacs Lisp 和 AutoLISP 這兩種 Lisp 的變體，則是嵌入特定產品作為擴展用的語言。與許多早期的 Lisps 不同，Common Lisp（Scheme 同樣）對原始碼直譯和編譯時，預設為詞法變數作用域。

大部份 Lisp 系統（如 ZetaLisp 和 Franz Lisp）的設計，促成了 Common Lisp 在直譯器中使用動態作用域的變數，並在編譯器中使用了詞法作用域的變數。由於 ALGOL 68 的啟發，Scheme 引入了 Lisp 對詞法作用域變數的單一使用；這被廣泛認同是好主意。CL 也支援動態作用域的變數，但必須將其顯式聲明為「特殊」。ANSI CL 直譯器和編譯器之間的作用域界定是沒有差別的。

Common Lisp 有時被稱為 Lisp-2，而 Scheme 被稱為 Lisp-1。它指的是 CL 對函式和變數使用個別的命名空間（實際上 CL 有許多命名空間，例如 `go` 標籤，`block` 名稱和 `loop` 關鍵字）。在涉及多個命名空間的權衡之間，CL 與 Scheme 倡導者之間存在著長期的爭議。在 Scheme 中（廣義地）必須避免與函式名稱互相衝突的變數名稱；Scheme 函式通常擁有名稱為 `lis`，`lst` 或 `lyst` 的參數，以免與系統內建的 `list` 函式衝突。然而在 CL 中，在傳遞函式作為參數時一定要顯式地參照函式的名稱空間，這也是一個常見的事件，如前面小節中的排序編程範例。

在處理布爾邏輯值時，CL 也與 Scheme 不同。Scheme 使用特殊值 #t 和 #f 來表示邏輯真與假值。而 CL 遵循使用符號 T 和 NIL 的傳統 Lisp 慣例，NIL 同時也是空串列。在 CL 中任何非 NIL 值被條件處理為真，例如 `if`；而在 Scheme 當中，所有非 #f 值被視為真。這些慣例約定允許這兩種語言的一些運算子同時作為謂詞（回應邏輯上的是非問題），並返回一個作用值進行進一步的計算，但在 Scheme 的布爾表達式中，等同於 Common Lisp 空串列的 NIL 值或 '()，會被評估為真。

最後，Scheme 的標準檔案要求尾部呼叫優化，而 CL 標準沒有。不過大多數 CL 實作會提供尾部呼叫優化，雖然通常只在程式員使用優化指令時。儘管如此，常見的 CL 編程風格並不偏好於 Scheme 中普遍使用的遞迴樣式 - 一個 Scheme 程式員會使用尾部遞迴表達式，CL 使用者則通常會用 `do`，`dolist`，`loop` 等迭代表達式，或使用 `iterate` 套件來表達。

## 實現

Common Lisp 是由一份技術規範定義而不是被某一種具體實現定義（前者的例子有 Ada 語言和 C 語言，後者有 Perl 語言）。存在很多種實現，語言標準詳細闡明了可能導致合理歧義的內容。

另外，各種實現試圖引入套件或函式庫來提供標準沒有提及的功能，可能的擴充功能如下所列：

* 互動式頂層（REPL）
* 垃圾收集
* 除錯器，步進器和檢查器
* 弱資料結構（雜湊表）
* 可擴展的序列
* 可擴展的 LOOP
* 環境存取
* CLOS 元物件協定（meta-object protocol）
* 基於 CLOS 的可擴展流
* 基於 CLOS 的條件系統
* 網路流
* 固定性 CLOS（persistent）
* Unicode 支援
* 外語編程介面（經常到 C）
* 作業系統介面
* Java 介面
* 多緒和多重處理
* 應用交付（應用程式，動態函式庫）
* 儲存映像檔

可移植的自由軟體庫提供了各種特性，著名的有 Common-Lisp.net 和 Common Lisp Open Code Collection 專案。

Common Lisp 設計為由增量編譯器實現。最佳化編譯的標準聲明（例如行內函式）已進入語言規範的計劃。大多數 Lisp 實現將函式編譯成原生的機器語言。其他的編譯器編譯為中間碼，有損速度但是容易實現二進位代碼的可移植。由於 Lisp 提供了互動式的提示符以及函式增量式的依次編譯，很多人誤會為 Lisp 是純解釋語言。

一些基於 Unix 的實現，例如 CLISP，可以作為指令碼直譯器使用；因此，系統可以像呼叫 Perl 或者 Unix shell 直譯器一樣透明地呼叫它。

### 實現的列表

免費的可重釋出實現包括：

* CMUCL，最初來自卡內基梅隆大學，現在作為自由軟體由一個志願者團隊維護。CMUCL 使用一個快速的原生代碼編譯器。它執行於 x86 上的 Linux 和 BSD；Alpha 上的 Linux；以及 Solaris、IRIX 和 HP-UX。
* GNU CLISP，是一個 bytecode 編譯的實現。它可移植並執行在很多 Unix 和 Unix 風格的系統上（包括 Mac OS X），以及 Microsoft Windows 和一些其他系統。
* Steel Bank Common Lisp（SBCL），是 CMUCL 的一個分支。"寬泛的說，SBCL 是 CMUCL 的可維護性加強版本。" SBCL 執行的平台和 CMUCL 一樣，除了 HP/UX，它執行於 Windows，PowerPC 上的 Linux，SPARC，MIPS，和 Mac OS X 之上。SBCL 不使用直譯器；所有的語句編譯為原生機器碼。
* GNU Common Lisp（GCL），GNU 專案的 Lisp 編譯器。GCL 還不是完全相容 ANSI，但它仍然是一些大型專案所選擇的實現，包括數學工具 Maxima，AXIOM 和 ACL2。GCL 執行在十一種架構的 GNU/Linux 下，以及 Windows，Solaris，和 FreeBSD。
* Embeddable Common Lisp（ECL），設計為可嵌入 C 語言應用中；
* Movitz 實現了 x86 上的 Lisp 環境而不依賴任何 OS。
* Armed Bear Common Lisp 是一個執行在 Java 虛擬機器上的 Common Lisp 實現。它包括了一個編譯器可以編譯 Javabytecode，並允許 Common Lisp 呼叫 Java 庫。Armed Bear Common Lisp 是 Armed Bear J Editor 的一個組件，但它也能獨立使用。
* Jatha 是一個 Java 庫，實現了 Common Lisp 的大部分子集。
* Clozure CL（CCL），可以執行在 Mac OS,Linux,FreeBSD,Solaris, 以及 Windows XP 等作業系統上，並且支援 x86,PowerPC,ARM 等硬體平台。其原名為 OpenMCL。

商業實現在 Franz, Inc.，Xanalys Corp.，Digitool, Inc.，Corman Technologies 和 Scieneer Pty Ltd.。

## 應用

Common Lisp 被用於很多成功的商業應用中，最著名的（毫無疑問要歸功於 Paul Graham 的推廣）要數 Yahoo! 商店的站點。其他值得一提的例子有：

* Orbitz，以飛機票預訂為主的站點
* Mirai，Izware LLC's fully integrated 2d/3d computer graphics content creation suite that features what is almost universally regarded as the best polygonal modeler in the industry, an advanced IK/FK and non-linear animation system (later popularized by such products as Sega's Animanium and Softimage XSI, respectively), and advanced 2d and 3d painting. It is used in major motion pictures（most famously in New Line Cinema's Lord of the Rings）, video games and military simulations.
* Piano，一個用 Lisp 寫的商業的航空期前期設計包以及與它的競爭對手的比較
* Xanalys Corp. 的調查軟體，被全球的警察，安全部門和防止詐騙服務部門採用
* Genworks International 的多用途說明語言（GDL），是一個基於 CL 的開發工具，用來建立基於 web 的工程，設計和商業應用

也有很多成功的開源應用用 Common Lisp 寫成，例如：

* Applicative Common Lisp，a full-featured theorem prover for a subset of Common Lisp.
* Maxima，一個精緻的電腦代數系統。
* Compo，a language allowing complex musical structures to be described in a natural way.
* Lisa，a production-rule system to build "intelligent" software agents.

同樣，Common Lisp 也被許多政府和非盈利組織採用。NASA 中的例子有：

* SPIKE，the Hubble Space Telescope planning and scheduling system.
* Remote Agent，winner of the 1999 NASA Software of the Year Award.

## 參照

## 外部連結

* ANSI Common Lisp 中文翻譯版（頁面存檔備份，存於網際網路檔案館），Paul Graham 寫的 Lisp 入門教科書
* The Common Lisp HyperSpec （頁面存檔備份，存於網際網路檔案館），Common Lisp 標準的電子版本
* The CLiki，關於 Unix 風格系統下的 Common Lisp 的 Wiki
* Lisp 使用者協會
* 豆瓣 Lisp 使用者群組（頁面存檔備份，存於網際網路檔案館）
* Common Lisp 快速入門
* Common Lisp the Language, 2nd Edition，也被稱為 "CLtL2". Guy Steele's book on Common Lisp，介紹了 ANSI Common Lisp 標準的基礎知識
* The Common Lisp Cookbook（頁面存檔備份，存於網際網路檔案館），有用的編程方法收集。
* Paul Graham 的頁面（頁面存檔備份，存於網際網路檔案館）關於 Lisp。如果你跟隨連結，你會找到他的電子書 _On Lisp_，這本書專注於 Common Lisp 中的宏設計。
* Common Lisp：符號計算的漸進入門（頁面存檔備份，存於網際網路檔案館）by David S. Touretzky，有電子版本並為新手而寫。
* Peter Norvig's page（頁面存檔備份，存於網際網路檔案館）包含了很多有趣的 Common Lisp 資源。
* Practical Common Lisp（頁面存檔備份，存於網際網路檔案館）同名的電子書。已進入了 2006 Jolt 大獎的 Finalist。
* Casting Spells in Lisp（頁面存檔備份，存於網際網路檔案館） Common Lisp 的卡通化介紹。
* Igor Engraver（頁面存檔備份，存於網際網路檔案館）：一流的音樂標記法和用 Common Lisp 寫成的程式。