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 年，小蓋伊・史提爾於 1982 年度 LISP 和函數語言程式設計研討會上首次給出了 Common Lisp 的概述。

在 1984 年，首個語言文件被出版為《》，第一版（也叫做 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 - 表示式來表示原始碼和資料結構。函式呼叫、宏形式和基本形式都以列表來編寫，列表的第一項是函式名稱，如以下範例：

(+ 2 2) ; 將 2 加上 2 得 4。函式名稱為 '+'，在 Lisp 語法中是唯一的 (只能作用於數值)。

(defvar x) ; 先確保 x 變數存在，尚未賦值給它。星號也是變數名稱的一部份，

; 依慣例約定表示一個特殊（全域性）變數。符號 x 與生俱有的屬性是

; 對於它後續的繫結是動態可變的，而非詞法靜止不變的。

(setf x 42.1) ; 對 x 變數賦予浮點數值 42.1。

;; 定義計算一個數的平方函式：

(defun square (x)

(* x x))

;; 執行這個函式：

(square 3) ; 返回平方值 9

;; 'let' 構造為區域變數建立一個作用域。這裡變數 'a' 被繫結到 6，變數 'b' 被繫結到 4。

;; 'let' 的內部是一個函式體，對它求值後會返回最後一個計算值。這個 'let' 表示式將

;; a 和 b 相加的結果返回。變數 a 和 b 只存在於詞法作用域中，除非它們已先被標記

;; 成特殊變數（例如上述的 DEFVAR）。

(let ((a 6)

(b 4))

(+ a b)) ; 返回數值 10

資料類別

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

3.1 純量型別

數值型別包括整數，分數，浮點數和複數。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）。

3.2 資料結構

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）的雙重角色，泛型既是函式，也是類別的例項物件。

3.3 函式

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

;; 使用大小於函式作為比較關係，對列表進行排序。

(sort (list 5 2 6 3 1 4) #'>) ; 大於比較排序結果 (6 5 4 3 2 1)

(sort (list 5 2 6 3 1 4) #'

;; 對每個子列表中，根據其第一個元素作為鍵值，以小於比較關係來排序。

(sort (list '(9 A) '(3 B) '(4 C)) #'

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

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

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

3.3.1 定義函式

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

(defun square (x)

(* x x))

函式定義中可以包括 “宣告”，它可以指示編譯器最佳化設定或引數的資料型別等。還可以在函式定義中包括 “文件字串”（docstring），Lisp 系統用它們形成互動式文件：

(defun square (x)

(declare (number x) (optimize (speed 3) (debug 0) (safety 1)))

"Calculates the square of the number x."

(* x x))

匿名函式用 lambda 表示式定義。Lisp 程式設計頻繁使用高階函式，以匿名函式作為其引數的作法十分有效。

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

3.3.2 定義泛化函式及方法

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

(defgeneric add (a b))

(defmethod add ((a number) (b number))

(+ a b))

(defmethod add ((a vector) (b number))

(map 'vector (lambda (n) (+ n b)) a))

(defmethod add ((a vector) (b vector))

(map 'vector #'+ a b))

(defmethod add ((a string) (b string))

(concatenate'string a b) )

(add 2 3) ; returns 5

(add #(1 2 3 4) 7) ; returns #(8 9 10 11)

(add #(1 2 3 4) #(4 3 2 1)) ; returns #(5 5 5 5)

(add "COMMON" "LISP") ; returns "COMMON LISP"

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

3.3.3 函式名字空間

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

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

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

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

3.3.4 多值

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

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

(let ((x 1266778)

(y 458))

(multiple-value-bind (quotient remainder)

(truncate x y)

(format nil "~A divided by ~A is ~A remainder ~A" x y quotient remainder)))

;;; => "1266778 divided by 458 is 2765 remainder 408"

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

(defun get-answer (library)

(gethash 'answer library 42))

(defun the-answer-1 (library)

(format nil "The answer is ~A" (get-answer library)))

;;; Returns "The answer is 42" if ANSWER not present in LIBRARY

(defun the-answer-2 (library)

(multiple-value-bind (answer sure-p)

(get-answer library)

(if (not sure-p)

"I don't know"

(format nil "The answer is ~A" answer))))

;;; Returns "I don't know" if ANSWER not present in LIBRARY

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

(defun magic-eight-ball

"Return an outlook prediction, with the probability as a secondary value"

(values "Outlook good" (random 1.0)))

;;; => "Outlook good"

;;; => 0.3187

3.4 其它類別

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 程式設計使用名稱來引用變數、函式和許多其它型別的實體。被命名的參照只在其作用域中有用。名稱與引用實體之間的關聯稱為繫結。作用域是指確定名稱具有特殊繫結的情況。

4.1 作用域的決定

在 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 開發人員必須知道參照是屬於哪一種作用域，如果它是一個變數的參照，那它是處於什麼樣的（動態或詞法的）作用域中？以及在執行期的情況，參照在什麼環境中被引用，繫結是在哪裡被引入到環境等等。

4.2 環境

4.2.1 全域性

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

該型別別的參照會從全域性作用域中的環境去尋找。

4.2.2 動態

環境在 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 函式寫入標準輸出：

(defun foo

(format t "Hello, world"))

要擷取其輸出中的字串，standard-output 可以被繫結到一個字串流，並呼叫它：

(with-output-to-string (standard-output)

(foo))

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

4.2.3 區域

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

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

(defvar stashed) ;; will hold a function

(tagbody

(setf stashed (lambda (go some-label)))

(go end-label) ;; skip the (print "Hello")

some-label

(print "Hello")

end-label)

-> NIL

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

(funcall stashed) ;; Error!

這種狀況是錯誤的。一個實作的錯誤回應該包含錯誤條件訊息，例如 “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 提供了迭代開發的幾個巨集範例。

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

Lisp 程式設計人員能夠利用巨集來創造新的語法形式。典型的用途是建立新的控制結構。

此處提供一個 until 迴圈結構的巨集範例，其語法如下：

(until test form*)

until 巨集的定義：

(defmacro until (test &body body)

(let ((start-tag (gensym "START"))

(end-tag (gensym "END")))

`(tagbody ,start-tag

(when ,test (go ,end-tag))

(progn ,@body)

(go ,start-tag)

,end-tag)))

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

上述 until 巨集的使用範例：

(until (= (random 10) 0)

(write-line "Hello"))

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

(TAGBODY

:START1136

(WHEN (ZEROP (RANDOM 10))

(GO #:END1137))

(PROGN (WRITE-LINE "hello"))

(GO #:START1136)

:END1137)

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

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

(TAGBODY

:START1136

(IF (ZEROP (RANDOM 10))

(PROGN (GO #:END1137))

NIL)

(PROGN (WRITE-LINE "hello"))

(GO #:START1136))

:END1137)

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

5.2 變數捕捉和覆蓋

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

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

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

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

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

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

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

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

;; expansion of UNTIL makes liberal use of DO

(defmacro until (expression &body body)

`(do (,expression) ,@body))

;; macrolet establishes lexical operator binding for DO

(macrolet ((do (...) ... something else ...))

(until (= (random 10) 0) (write-line "Hello")))

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 系統本身所提供，它可以處理和修復錯誤，而不終止使用者執行中的程式碼。

Command: (test ">zippy>lispm-int.lisp")

Error: The file was not found.

For lispm:>zippy>lispm-int.lisp.newest

LMFS:OPEN-LOCAL-LMFS-1

Arg 0: #P"lispm:>zippy>lispm-int.lisp.newest"

s-A, : Retry OPEN of lispm:>zippy>lispm-int.lisp.newest

s-B: Retry OPEN using a different pathname

s-C, : Return to Lisp Top Level in a TELNET server

s-D: Restart process TELNET terminal

-> Retry OPEN using a different pathname

Use what pathname instead default lispm:>zippy>lispm-int.lisp.newest:

lispm:>zippy>lispm-init.lisp.newest

...the program continues

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 成為其它程式語言的中途編譯器或直譯器變得容易。

程式設計原始碼範例

9.1 生日悖論

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

; By convention, constants in Common Lisp are enclosed with + characters.

(defconstant +year-size+ 365)

(defun birthday-paradox (probability number-of-people)

(let ((new-probability (* (/ (- +year-size+ number-of-people)

+year-size+)

probability)))

(if (

使用 REPL 呼叫函式用例：

CL-USER > (birthday-paradox 1.0 1)

23

9.2 排序列表

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

(defclass person

((name :initarg :name :accessor person-name)

(age :initarg :age :accessor person-age))

(:documentation "The class PERSON with slots NAME and AGE."))

(defmethod display ((object person) stream)

"Displaying a PERSON object to an output stream."

(with-slots (name age) object

(format stream "~a (~a)" name age)))

(defparameter *group (list (make-instance 'person :name "Bob" :age 33)

(make-instance 'person :name "Chris" :age 16)

(make-instance 'person :name "Ash" :age 23))

"A list of PERSON objects.")

(dolist (person (sort (copy-list group)

'>

:key #'person-age))

(display person standard-output)

(terpri))

它以降序列印三個名字。

Bob (33)

Ash (23)

Chris (16)

9.3 平方指數

使用 LOOP 宏：

(defun power (x n)

(loop with result = 1

while (plusp n)

when (oddp n) do (setf result (* result x))

do (setf x (* x x)

n (truncate n 2))

finally (return result)))

使用示例：

CL-USER > (power 2 200)

1606938044258990275541962092341162602522202993782792835301376

與內建的求冪函式比較：

CL-USER > (= (expt 2 200) (power 2 200))

T

9.4 查詢可用 shell 的列表

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

(defun list-matching-lines (file predicate)

"Returns a list of lines in file, for which the predicate applied to

the line returns T."

(with-open-file (stream file)

(loop for line = (read-line stream nil nil)

while line

when (funcall predicate line)

collect it)))

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

(defun available-shells (&optional (file #p"/etc/shells"))

(list-matching-lines

file

(lambda (line)

(and (plusp (length line))

(char= (char line 0) #\/)

(pathname

(string-right-trim '(#\space #\tab) line))))))

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

CL-USER > (available-shells)

(#P"/bin/bash" #P"/bin/csh" #P"/bin/ksh" #P"/bin/sh" #P"/bin/tcsh" #P"/bin/zsh")

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 直譯器一樣透明地呼叫它。

11.1 實現的列表

免費的可重發布實現包括：

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.