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,其中的變量是預設為詞法作用域的。 身為一種動態編程語言,它有助於進化和增量的軟件開發,並將其迭代編譯成高效的執行程序。這種增量開發通常是互動持續地改善,而不需中斷執行中的應用程序。它還支援在後期的分析和優化階段添加可選的型別註記與轉型,使編譯器產生更有效率的代碼。例如在硬體和實作的支援範圍內, 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年,小蓋伊·史提爾于1982年度LISP和函数式编程研讨会上首次给出了Common Lisp的概述[4]。 在1984年,首个语言文档被出版为《Common Lisp语言》,第一版(也叫做CLtL1)。在1990年,第二版(也叫做CLtL2)出版了,它结合了在ANSI Common Lisp标准化过程中对语言做的很多变更:扩展的 语法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會自動將數值轉換成適當的型別。有許多方式取捨數值,函數 例如, Common Lisp字元型別不限於ASCII字符,因為在ASCII出現前Lisp就已經存在了。大多數現代實現允許Unicode字元。 符號(Symbol)型別是Lisp語言共有的,而在其它語言中較少見。一個符號是個命名唯一的對象,它擁有幾個部份:名稱,值,函數,屬性列表(property list)和套件。其中,值單元和函數單元是最重要的。Lisp中的符號通常類似於其它語言中的標識符(identifier)用法:保存變量的值;然而還有很多種用途。一般來說,對一個符號求值時會得到以該符號為變量名稱的值,但也有例外:譬如在關鍵符套件中的符號,形如 數據結構Common Lisp的序列型別包括列表、向量、位元向量和字串。有許多函式可對應不同型別的序列進行操作。 CL如同所有Lisp方言,列表由點對(conses)組成,有時稱為cons單元、序偶或構對。一個點對是帶有兩個儲存槽的數據結構,分別稱為car和cdr。列表就是一條點對的串列,或只是空列表。每一個點對的CAR會參照列表的成員(可能是另一個列表)。而除了最後一個的CDR參照到 CL支援多維陣列,且如需要能動態地調整陣列大小。多維陣列常用於數學中的矩陣運算。向量就是一維陣列。陣列可載入任何型別(甚至於混合的型別)的成員,或只專用於特定某一型別的成員,例如由整數構成的位元向量。許多Lisp實作會根據特定型別,對陣列的操作函數進行優化。兩種特定型別的專用陣列是內建的:字串和位元向量。字串是由許多字元構成的向量,而位元向量是由許多位元構成的向量。 散列表儲存对象之間的關聯,任何物件都可以作為散列表的鍵或值。和陣列一樣,散列表可依需求自動調整其大小。 套件是一組符號的集合,主要用於將程序的個別部份區分命名空間。套件能匯出一些符號,將它們作為共用介面的某一部份,也可以匯入其它套件引用並概括承受其中的符號。 CL的結構體(Structures)類似於C語言的structs和Pascal的records,是一種任由使用者發揮的複雜数据結構定義,表示具有任意數量和任何型別的欄位(也叫做槽)。結構允許單一繼承。 類別(Class)在後期被整合進Common Lisp中,有些概念與結構體重疊,但類別提供了更多的動態特性和多重繼承(見 CLOS)。由類別創建的物件稱為實例。有個特殊情況是泛型(Generics)的雙重角色,泛型既是函數,也是類別的實例物件。 函数Common Lisp支援頭等函數(亦即函數可當成数据類型來處理)。例如編寫以其它函數當作一個函數的參數,或函數的傳回值也是函數,利用函數的結合來描述常用的操作。CL函式庫高度依賴於這樣的高階函數變換。舉例而言, ;; 使用大小於函數作為比較關係,對列表進行排序。
(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))
對函數求值的模型非常簡單。當求值器遇到一個形式如
如果 定义函数宏 (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))
匿名函数用 还有一些有关于函数定义和函数操作的运算符。如,操作符 定義泛化函數及方法
(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编程语言的一个重要不同之处。在函数名字空间定义名字的操作符包括 要用函数名把函数作为参数传给另一个函数,必须使用 Scheme编程语言的求值模型更简单些:因为只有一个名字空间,式(form)中所有位置都被求值(以任意顺序)-- 不仅是参数。所以以一种方言(dialect)写就的代码往往令熟悉其它方言程序员感到迷惑。例如,许多CL程序员喜欢使用描述性的变量名如"list"或"string",在Scheme中这将导致问题,因为它们可能局部覆盖了函数名字。 为函数提供分离的名字空间是否有益是Lisp社区不断争论的主题之一,常被称为“Lisp-1与Lisp-2辩论”。这些名称出现于Richard P. Gabriel和Kent Pitman在1998年的一篇论文,其中广泛的比较了这两种方法。[5] 多值Common Lisp支援多值的概念,任何表達式經過評估之後必定會有一個主要值,但它也可能擁有任何數量的次要值,讓感興趣的呼叫者接收和檢查。這個概念與回傳列表值不同,因為次要值是備選用的,並通過專用的側面通道來傳遞。也就是說如果不需要次要值,則呼叫者完全不需要知道它們的存在,這是偶爾需使用額外而非必要的資訊,一個方便的機制。
(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"
(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
一些標準形式支援多值,最常見的是用來存取次要值的 (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編程使用名稱來引用變量、函數和許多其它類型的實體。被命名的參照只在其作用域中有用。名稱與引用實體之間的關聯稱為綁定。作用域是指確定名稱具有特殊綁定的情況。 作用域的決定在Common Lisp中需要決定作用域的情況包括:
要理解符號參照到什麼實體,Common Lisp開發人員必須知道參照是屬於哪一種作用域,如果它是一個變量的參照,那它是處於什麼樣的(動態或詞法的)作用域中?以及在執行期的情況,參照在什麼環境中被引用,綁定是在哪裡被引入到環境等等。 環境全局Lisp中的一些環境總是存在於全局作用域之中, 例如定義了一個新型別,那麼以後在任何地方都會知道它。 動態環境在Common Lisp中有一種類型是動態環境。在這種環境中建立的綁定具有動態的作用域,這表示某些構造例如 Common Lisp支援動態作用域的變量,也稱為特殊變量。有些其它類型的綁定也必須是動態作用域的,例如重新啟動和捕獲標籤。函數綁定不能以 動態作用域非常有用,因為它將參照的清晰度和規律添加到全局變量中。計算機科學中的全局變量被認為是潛在的錯誤來源,因為它們可能導致模組之間存有特殊隱蔽的溝通渠道,從而導致令人驚訝而不在預期中的交互作用。 在Common Lisp中,只有頂層綁定的特殊變量就像其它編程語言中的全局變量一樣。它可以儲存一個新的值,該值僅替換頂層綁定中的值。造成使用全局變量的核心錯誤,是粗心的替代了全局變量值。但是,使用特殊變量的另一種方法是,在表達式中給它一個新的區域綁定。這有時被稱為“重新綁定”變量。動態作用域中對變量的綁定,會創建一個臨時的新記憶體位置給予該變量,並將該名稱與該位置相關聯。當該綁定有效,對該變量的所有參照都指向新的綁定;之前的綁定則是被隱藏起來的。當綁定表達式的執行結束時,臨時的記憶體位置消失,而舊綁定浮現出來,變量的原始值依舊完好無損。當然,同一變量的多個動態綁定可以嵌套。 在支援多緒的Common Lisp實作中,動態作用域是針對每個執行緒的。因此,特殊變量是當成執行緒區域存儲的抽象化。如果一個執行緒重新綁定了特殊變量,則此重新綁定對其它執行緒中的該變量沒有作用。儲存在綁定中的值只能由創建該綁定的執行緒取得。如果每個執行緒綁定一些特殊變量 動態變量可以用來擴展執行上下文,並附加上下文訊息,這些信息在函數之間隱含地傳遞,而不必顯示為額外的函數參數。當執行控制的轉移必須穿過不相關的代碼層時,不能藉由額外參數來擴展傳遞附加數據,所以這是非常有用的。這樣的情況通常需要一個全局變量,必須能夠被儲存和恢復,以便在遞歸時不會中斷:動態變量的重新綁定可以處理此情形。該變量必須是執行緒區域的(或必須使用大的互斥, mutex),因此這個情況不會在執行緒下斷開:動態作用域的實作也可以處理此情形。 在Common Lisp函式庫中有很多標準的特殊變量。例如,所有標準I/O流都儲存在頂層為眾所熟知的特殊變量的綁定中,即 假設有個foo函數寫入標準輸出: (defun foo ()
(format t "Hello, world"))
要擷取其輸出中的字串, (with-output-to-string (*standard-output*)
(foo))
-> "Hello, world" ; gathered output returned as a string 區域Common Lisp支援詞法環境。形式上,詞法環境中的綁定具有詞法作用域,並可能具有不定的範圍或動態的範圍,取決於命名空間的類型。詞法作用域實際上表示可見性被限制在綁定建立的區塊中。參照沒有以文本(即詞法地)嵌入在該區塊中,根本看不到該綁定。
(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
執行 (funcall *stashed*) ;; Error!
這種狀況是錯誤的。一個實作的錯誤回應該包含錯誤條件訊息,例如“GO: tagbody for tag SOME-LABEL has already been left”。該函數嘗試評估 Lisp中的區域函數綁定具有詞法作用域,預設情況下變量綁定也同樣為詞法作用域。與 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巨集的典型用途:
各種標準的Common Lisp功能也需要巨集來實現,如以下所列:
使用巨集定義控制結構的範例Lisp編程人員能夠利用巨集來創造新的語法形式。典型的用途是創建新的控制結構。 此處提供一個 (until test form*)
(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)))
上述 (until (= (random 10) 0)
(write-line "Hello"))
利用 (TAGBODY
#:START1136
(WHEN (ZEROP (RANDOM 10))
(GO #:END1137))
(PROGN (WRITE-LINE "hello"))
(GO #:START1136)
#:END1137)
在巨集展開期間,變量 符號通常會自動轉成英文大寫。這個 (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的巨集展開代碼中的符號,與調用上下文中的符號相同時,通常稱為變量捕捉。對於巨集,程序員可在其中創建具有特殊含義的各種符號。變量捕捉這個術語可能有點誤導,因為所有的命名空間都有非預期捕捉到相同符號的弱點,包括運算子和函數的命名空間、 變量捕捉情況會使軟件產生缺陷,發生原因可分為下列兩種方式:
Lisp語族的Scheme方言提供了一個巨集寫入系統,它提供了參照透明度來消除這兩種類型的捕捉問題。這樣的巨集寫入系統有時被稱為“保健的”,特別是其支持者(認為不能自動解決捕捉問題的巨集系統是不正確的)。 在Common Lisp中巨集的保健,則以兩種不同方式擔保。 一種方法是使用 另一種方法是使用套件,在自己套件中定義的巨集,在套件中的擴展可以簡單地使用內部符號。使用套件能處理類型一和類型二捕捉問題。然而,套件不能解決參照到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")))
Common Lisp禁止對標準運算子和函數的重新定義,避免它們的遮蔽來解決此類問題。因為前例重新定義了 條件系統條件系統負責Common Lisp中的異常處理。它提供條件,處理程序和重新啟動。條件是描述異常情況(例如錯誤)的物件。如果一個條件訊號被發出了,Common Lisp系統將搜索此條件類型的處理程序並調用它。處理程序現在可以搜索重新啟動(restart),並使用這些重新啟動之一來自動修復當前的問題,利用條件類型與條件物件的一部份所提供的任何相關資訊等,並調用相對的重新啟動函數。 如果沒有處理程序的代碼,這些重新啟動可以對使用者顯示選項(作為使用者介面的一部分,例如除錯器),讓使用者選擇和調用提供的重新啟動選項。由於條件處理程序在錯誤的上下文中被調用(堆疊仍未清空),在許多情況下對錯誤的完全回復處理是可行的,而不同於其它的異常處理系統可能已經終止了當前的執行程序。除錯器本身也可以使用 以下範例(使用 Symbolics Genera)中,使用者從讀取求值打印循環(REPL,即頂層)呼叫一個test函數,嘗試開啟一個檔案,而當此檔案不存在時,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標準描述了直譯器和編譯器的語義。可以使用 還有一個函數用來評估Lisp源碼: 使用 即使源碼已經完全被編譯,Common Lisp實作可以和使用者互動。因此,Common 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 的列表
(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)))
函数 (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有許多命名空間,例如 在處理布爾邏輯值時,CL也與Scheme不同。Scheme使用特殊值#t和#f來表示邏輯真與假值。而CL遵循使用符號T和NIL的傳統Lisp慣例,NIL同時也是空列表。在CL中任何非NIL值被條件處理為真,例如 最後,Scheme的標準文件要求尾部呼叫優化,而CL標準沒有。不過大多數CL實作會提供尾部呼叫優化,雖然通常只在程序員使用優化指令時。儘管如此,常見的CL編程風格並不偏好於Scheme中普遍使用的遞歸樣式- 一個Scheme程序員會使用尾部遞歸表達式,CL使用者則通常會用 实现Common Lisp是由一份技术规范定义而不是被某一种具体实现定义(前者的例子有Ada语言和C语言,后者有Perl语言)。存在很多种实现,语言标准详细阐明了可能导致合理歧义的内容。 另外,各种实现试图引入套件或函式库来提供标准没有提及的功能,可能的擴充功能如下所列:
可移植的自由软件库提供了各种特性,著名的有Common-Lisp.net[6]和Common Lisp Open Code Collection[7]项目。 Common Lisp设计为由增量编译器实现。优化编译的标准声明(例如内联函数)已进入语言规范的计划。大多数Lisp实现将函数编译成原生的机器语言。其他的编译器编译为中间码,有损速度但是容易实现二进制代码的可移植。由于Lisp提供了交互式的提示符以及函数增量式的依次编译,很多人误会为Lisp是纯解释语言。 一些基于Unix的实现,例如CLISP,可以作为脚本解释器使用;因此,系统可以像调用Perl或者Unix shell解释器一样透明地调用它。 实现的列表免费的可重发布实现包括:
商业实现在Franz, Inc.[16],Xanalys Corp.[17],Digitool, Inc.[18],Corman Technologies[19]和Scieneer Pty Ltd.[20]。 应用Common Lisp被用于很多成功的商业应用中,最著名的(毫无疑问要归功于Paul Graham的推广)要数Yahoo!商店的站点。其他值得一提的例子有:
也有很多成功的开源应用用Common Lisp写成,例如:
同样,Common Lisp也被许多政府和非盈利组织采用。NASA中的例子有:
引用
外部链接
|