Common Lisp
編程範型 | 多范型:過程式, 函數式, 面向對象, 元編程, 反射式, 泛型 |
---|---|
設計者 | Scott Fahlman, Richard P. Gabriel, David A. Moon, Kent Pitman, Guy Steele, Dan Weinreb |
實作者 | ANSI X3J13委員會 |
面市時間 | 1984年 | , ANSI Common Lisp:1994年
當前版本 | |
型態系統 | 動態類型、強類型 |
操作系統 | 跨平台 |
許可證 | GNU通用公共許可證、Artistic License |
文件擴展名 | .lisp, .lsp, .l, .cl, .fasl |
網站 | common-lisp |
啟發語言 | |
Lisp, Lisp Machine Lisp, Maclisp, Scheme, InterLisp | |
影響語言 | |
Clojure, Dylan, Emacs Lisp, EuLisp, ISLISP, R, SKILL, SubL, Julia |
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方言[1]。大多數最初的語言設計是通過電子郵件完成的[2][3]。在1982年,Guy L. Steele Jr.於1982年度LISP和函數式編程研討會上首次給出了Common Lisp的概述[4]。
在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-表達式來表示源碼和資料結構。函數調用、宏形式和基本形式都以列表來編寫,列表的第一項是函數名稱,如以下範例:
(+ 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
將參數四捨六入為最接近的整數,逢五則取偶整數。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
函數可將關係運算子作為參數,並選用如何取鍵的函數作為參數。如此一來不但能對任何型別的數據排序,還能根據取用的鍵值對數據結構作排序。
;; 使用大小於函數作為比較關係,對列表進行排序。
(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編程語言的一個重要不同之處。在函數名字空間定義名字的操作符包括defun
、flet
和labels
。
要用函數名把函數作為參數傳給另一個函數,必須使用function
特殊操作符,通常簡略為#'
。上文第一個sort
的例子中,為了引用在函數名字空間名為>
的函數,使用了代碼#'>
。
Scheme編程語言的求值模型更簡單些:因為只有一個名字空間,式(form)中所有位置都被求值(以任意順序)-- 不僅是參數。所以以一種方言(dialect)寫就的代碼往往令熟悉其它方言程序員感到迷惑。例如,許多CL程序員喜歡使用描述性的變量名如"list"或"string",在Scheme中這將導致問題,因為它們可能局部覆蓋了函數名字。
為函數提供分離的名字空間是否有益是Lisp社區不斷爭論的主題之一,常被稱為「Lisp-1與Lisp-2辯論」。這些名稱出現於Richard P. Gabriel和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提供的用於存儲「鍵值對」的數據類別。在散列表中任何物件都可以作為鍵或者值。雜湊在必要時候會自動調整大小。
- 輸入流和輸出流(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函數寫入標準輸出:
(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對於變量的預設模式是詞法綁定。對於個別符號可用區域聲明,或全局的聲明,來切換成動態作用域。而後者可能隱含地透過如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 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源碼: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。
;; 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. 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[6]和Common Lisp Open Code Collection[7]項目。
Common Lisp設計為由增量編譯器實現。優化編譯的標準聲明(例如內聯函數)已進入語言規範的計劃。大多數Lisp實現將函數編譯成原生的機器語言。其他的編譯器編譯為中間碼,有損速度但是容易實現二進制代碼的可移植。由於Lisp提供了交互式的提示符以及函數增量式的依次編譯,很多人誤會為Lisp是純解釋語言。
一些基於Unix的實現,例如CLISP,可以作為腳本解釋器使用;因此,系統可以像調用Perl或者Unix shell解釋器一樣透明地調用它。
實現的列表
免費的可重發布實現包括:
- CMUCL,最初來自卡內基梅隆大學,現在作為自由軟件由一個志願者團隊維護。CMUCL使用一個快速的原生代碼編譯器。它運行於x86上的Linux和BSD;Alpha上的Linux;以及Solaris、IRIX和HP-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上的Linux,SPARC,MIPS,和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中的例子有:
- SPIKE[30],the Hubble Space Telescope planning and scheduling system.
- Remote Agent[31],winner of the 1999 NASA Software of the Year Award.
引用
- ^ Roots of "Yu-Shiang Lisp", Mail from Jon L White, 1982. cmu.edu. [2021-10-25]. (原始內容存檔於2022-03-31).
- ^ Mail Index. cl-su-ai.lisp.se. [2021-10-25]. (原始內容存檔於2016-04-11).
- ^ 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,.
- ^ 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.
- ^ 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).
- ^ Common-Lisp.net(頁面存檔備份,存於網際網路檔案館)
- ^ Common Lisp Open Code Collection(頁面存檔備份,存於網際網路檔案館)
- ^ [1]
- ^ GNU CLISP(頁面存檔備份,存於網際網路檔案館)
- ^ Steel Bank Common Lisp(頁面存檔備份,存於網際網路檔案館)
- ^ GNU Common Lisp(頁面存檔備份,存於網際網路檔案館)
- ^ Armed Bear Common Lisp
- ^ Armed Bear J Editor
- ^ Jatha(頁面存檔備份,存於網際網路檔案館)
- ^ Clozure CL(頁面存檔備份,存於網際網路檔案館)
- ^ Franz, Inc.(頁面存檔備份,存於網際網路檔案館)
- ^ Xanalys Corp.(頁面存檔備份,存於網際網路檔案館)
- ^ Digitool, Inc.
- ^ Corman Technologies(頁面存檔備份,存於網際網路檔案館)
- ^ Scieneer Pty Ltd.
- ^ Orbitz(頁面存檔備份,存於網際網路檔案館)
- ^ Mirai(頁面存檔備份,存於網際網路檔案館)
- ^ Izware LLC(頁面存檔備份,存於網際網路檔案館)
- ^ Piano(頁面存檔備份,存於網際網路檔案館)
- ^ Xanalys Corp.(頁面存檔備份,存於網際網路檔案館)
- ^ Genworks International(頁面存檔備份,存於網際網路檔案館)
- ^ Applicative Common Lisp(頁面存檔備份,存於網際網路檔案館)
- ^ Compo
- ^ Lisa(頁面存檔備份,存於網際網路檔案館)
- ^ SPIKE
- ^ Remote Agent(頁面存檔備份,存於網際網路檔案館)
外部連結
- 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寫成的程序。