跳转到内容

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

    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 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页面存档备份,存于互联网档案馆

    外部链接