Common Lisp

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

Common Lisp
編程範型多範式程序式, 函數式, 物件導向, 元程式設計, 反射式, 泛型
設計者Scott Fahlman英語Scott Fahlman, Richard P. Gabriel英語Richard P. Gabriel, David A. Moon英語David A. Moon, Kent Pitman英語Kent Pitman, Guy Steele, Dan Weinreb英語Dan Weinreb
實作者ANSI X3J13英語X3J13委員會
面市時間1984年​(40年前)​(1984, ANSI Common Lisp:1994年​(30年前)​(1994
目前版本
    編輯維基數據鏈結
    型態系統動態型別強型別
    作業系統跨平台
    授權條款GNU通用公眾授權條款Artistic License
    副檔名.lisp, .lsp, .l, .cl, .fasl
    網站common-lisp.net
    啟發語言
    Lisp, Lisp Machine Lisp英語Lisp Machine Lisp, Maclisp, Scheme, InterLisp英語InterLisp
    影響語言
    Clojure, Dylan, Emacs Lisp, EuLisp英語EuLisp, ISLISP, R, SKILL英語SKILL, SubL英語SubL, Julia

    CL語言是為標準化和改良Maclisp而開發的後繼者。到20世紀80年代初,幾個工作群組已經在設計MacLisp各種後繼者,例如:Lisp Machine Lisp(又名 ZetaLisp),Spice Lisp,NIL和S-1 Lisp。CL是為了標準化和擴展此前眾多的MacLisp分支而開發,它本身並非具體的實作,而是對語言設立標準的規範。有數個實作符合Common Lisp規範,其中包括自由和開源軟體,以及商業化產品。CL支援了結構化函數式物件導向編程等範式。相對於各種嵌入在特定產品中的語言,如Emacs LispAutoLISP,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方言[1]。大多數最初的語言設計是通過電子郵件完成的[2][3]。在1982年,Guy L. Steele Jr.於1982年度LISP和函數式程式設計研討會上首次給出了Common Lisp的概述[4]

    在1984年,首個語言文件被出版為《Common Lisp語言英語Common Lisp the Language》,第一版(也叫做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 擁有豐富的資料類別。

    純量型別

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

    例如,(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),而符號TNIL則用於表示布爾邏輯值真與假。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函式可將關係運算子作為參數,並選用如何取鍵的函式作為參數。如此一來不但能對任何型別的資料排序,還能根據取用的鍵值對資料結構作排序。

     ;; 使用大小於函數作為比較關係,對列表進行排序。
     (sort (list 5 2 6 3 1 4) #'>)   ; 大於比較排序結果 (6 5 4 3 2 1)
     (sort (list 5 2 6 3 1 4) #'<)   ; 小於比較排序結果 (1 2 3 4 5 6)
    
     ;; 對每個子列表中,根據其第一個元素作為鍵值,以小於比較關係來排序。
     (sort (list '(9 A) '(3 B) '(4 C)) #'< :key #'first)   ; 結果為 ((3 B) (4 C) (9 A))
    

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

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

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


    定義函式

    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系統在函式輸入時即被編譯。)


    定義泛化函式及方法

    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"
    

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

    函式名字空間

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

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

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

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

    多值

    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
    

    其它類別

    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標籤的xx變數名稱位於分開的命名空間中。基本運算子或宏形式可完全控制其語法中所有符號的含義。例如在(defclass x (a b) ())表達式中,類別定義(a b)是基本類別的列表,因此會在類別的命名空間中搜尋這些名稱;x並非參照到現有的綁定,而是源自於ab的新類別名稱。這些事實純粹由defclass的語義表示得出。這表達式的唯一事實是defclass參照一個宏綁定;其中的一切都由defclass決定作用域。
    • 參照在程式中的位置。比如若對x變數的參照被含括在一個綁定結構中,例如以let綁定對x的定義,則該參照的效用發生在該綁定建立的作用域內。
    • 對於變數的參照,如果變數符號已被聲明為special,無論這聲明是在本地的或在全域中。這將使參照依據其位於詞法或動態的環境中來參照它。
    • 參照解決的環境的具體實例。一個環境是在執行期將符號與綁定對應起來的字典。每種參照會使用自己的環境。詞彙的變數會在詞彙的環境中被參照,可將同一個參照與多個環境相關聯起來。例如由遞迴或使用多執行緒,一個函式的多次觸發可以同時存在。這些觸發會共享相同的程式文字,但每個都有自己的詞彙環境實例。

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

    環境

    全域

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

    動態

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

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

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

    在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
    

    區域

    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對於變數的預設模式是詞法綁定。對於個別符號可用區域聲明,或全域的聲明,來切換成動態作用域。而後者可能隱含地透過如DEFVARDEFPARAMETER,這樣的構造使符號成為全域可見的。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之上的巨集;條件分支whenunless也是由巨集所構成
    • 強大的loop迭代巨集語法


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


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

    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中所提供的任何運算子都可用於編寫巨集。

    變數捕捉和覆蓋

    因為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, <Resume>: Retry OPEN of lispm:>zippy>lispm-int.lisp.newest
    s-B:           Retry OPEN using a different pathname
    s-C, <Abort>:  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原始碼:evaleval將原始碼視為預先解析的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。

    ;;  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 (< new-probability 0.5)
            (1+ number-of-people)
            (birthday-paradox new-probability (1+ number-of-people)))))
    

    使用REPL呼叫函式用例:

    CL-USER > (birthday-paradox 1.0 1)
    23
    

    排序列表

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

    (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)
    

    平方指數

    使用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
    

    尋找可用 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. SteeleGerald 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函式通常擁有名稱為lislstlyst的參數,以免與系統內建的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使用者則通常會用dodolistloop等迭代表達式,或使用iterate套件來表達。

    實現

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

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

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

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

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

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

    實現的列表

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

    • CMUCL,最初來自卡內基梅隆大學,現在作為自由軟體由一個志願者團隊維護。CMUCL使用一個快速的原生代碼編譯器。它執行於x86上的LinuxBSD;Alpha上的Linux;以及SolarisIRIXHP-UX[8]
    • GNU CLISP[9],是一個bytecode編譯的實現。它可移植並執行在很多Unix和Unix風格的系統上(包括Mac OS X),以及Microsoft Windows和一些其他系統。
    • Steel Bank Common Lisp[10](SBCL),是CMUCL的一個分支。"寬泛的說,SBCL是CMUCL的可維護性加強版本。" SBCL執行的平台和CMUCL一樣,除了HP/UX,它執行於Windows,PowerPC上的LinuxSPARCMIPS,和Mac OS X之上。SBCL不使用直譯器;所有的語句編譯為原生機器碼。
    • GNU Common Lisp[11](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[12]是一個執行在Java虛擬機器上的Common Lisp實現。它包括了一個編譯器可以編譯Javabytecode,並允許Common Lisp呼叫Java庫。Armed Bear Common Lisp是Armed Bear J Editor[13]的一個組件,但它也能獨立使用。
    • Jatha[14]是一個Java庫,實現了Common Lisp的大部分子集。
    • Clozure CL[15](CCL),可以執行在Mac OS,Linux,FreeBSD,Solaris,以及Windows XP等作業系統上,並且支援x86,PowerPC,ARM等硬體平台。其原名為OpenMCL。

    商業實現在Franz, Inc.[16],Xanalys Corp.[17],Digitool, Inc.[18],Corman Technologies[19]和Scieneer Pty Ltd.[20]

    應用

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

    • Orbitz[21],以飛機票預訂為主的站點
    • Mirai[22],Izware LLC[23]'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[24],一個用Lisp寫的商業的航空期前期設計包以及與它的競爭對手的比較
    • Xanalys Corp.[25]的調查軟體,被全球的警察,安全部門和防止詐騙服務部門採用
    • Genworks International[26]的多用途說明語言(GDL),是一個基於CL的開發工具,用來建立基於web的工程,設計和商業應用

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

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

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

    參照

    1. ^ Roots of "Yu-Shiang Lisp", Mail from Jon L White, 1982. cmu.edu. [2021-10-25]. (原始內容存檔於2022-03-31). 
    2. ^ Mail Index. cl-su-ai.lisp.se. [2021-10-25]. (原始內容存檔於2016-04-11). 
    3. ^ Knee-jerk Anti-LOOPism and other E-mail Phenomena: Oral, Written, and Electronic Patterns in Computer-Mediated Communication, JoAnne Yates and Wanda J. Orlikowski., 1993 網際網路檔案館存檔,存檔日期August 8, 2012,.
    4. ^ Jr, Steele; L, Guy. An overview of COMMON LISP. Lfp '82. ACM. August 15, 1982: 98–107. ISBN 9780897910828. S2CID 14517358. doi:10.1145/800068.802140. 
    5. ^ Richard P. Gabriel; Kent M. Pitman. Technical Issues of Separation in Function Cells and Value Cells. Lisp and Symbolic Computation. June 1988, 1 (1): 81–101 [2006-11-06]. S2CID 26716515. doi:10.1007/bf01806178. (原始內容存檔於2006-11-13). 
    6. ^ Common-Lisp.net頁面存檔備份,存於網際網路檔案館
    7. ^ Common Lisp Open Code Collection頁面存檔備份,存於網際網路檔案館
    8. ^ [1]
    9. ^ GNU CLISP頁面存檔備份,存於網際網路檔案館
    10. ^ Steel Bank Common Lisp頁面存檔備份,存於網際網路檔案館
    11. ^ GNU Common Lisp頁面存檔備份,存於網際網路檔案館
    12. ^ Armed Bear Common Lisp
    13. ^ Armed Bear J Editor
    14. ^ Jatha頁面存檔備份,存於網際網路檔案館
    15. ^ Clozure CL頁面存檔備份,存於網際網路檔案館
    16. ^ Franz, Inc.頁面存檔備份,存於網際網路檔案館
    17. ^ Xanalys Corp.頁面存檔備份,存於網際網路檔案館
    18. ^ Digitool, Inc.
    19. ^ Corman Technologies頁面存檔備份,存於網際網路檔案館
    20. ^ Scieneer Pty Ltd.
    21. ^ Orbitz頁面存檔備份,存於網際網路檔案館
    22. ^ Mirai頁面存檔備份,存於網際網路檔案館
    23. ^ Izware LLC頁面存檔備份,存於網際網路檔案館
    24. ^ Piano頁面存檔備份,存於網際網路檔案館
    25. ^ Xanalys Corp.頁面存檔備份,存於網際網路檔案館
    26. ^ Genworks International頁面存檔備份,存於網際網路檔案館
    27. ^ Applicative Common Lisp頁面存檔備份,存於網際網路檔案館
    28. ^ Compo
    29. ^ Lisa頁面存檔備份,存於網際網路檔案館
    30. ^ SPIKE
    31. ^ Remote Agent頁面存檔備份,存於網際網路檔案館

    外部連結