【LISP】Pascal Costanza:极端片面的Lisp介绍
1 背景知识
1.1 我为什么写这样一个介绍材料?
这份材料的初稿是在2002年八月完成的,所以其中的描述,并不完全反应我现在的情况的情况。但是为了保持最初的原味,我决定不去修改它们。
我现在的情况是这样的:过去的7年里,如果项目所需要的Java扩展已经存在,我就使用 Java。更往前算,我主要使用Wirth家族的语言(主要是Modula-2和Oberon)。所以,一开始我对Java相对其他语言而言的优点感到非常满意。
在过去的一年里,我逐渐意识到,Java依然是一种能力非常(实际上是极端)有限的语言,所以我开始寻找一个可能的替代方案。因为我参与了Feyerabend项目(http://www.dreamsongs.com/Feyerabend/Feyerabend.html), Lisp自然的成为候选之一。(Richard Gabriel启动了Feyerabend项目,而他也是1980年代初期推动Common Lisp标准化进程的那群人中的一个。)
虽 然,有很多非常漂亮的编程语言存在(比如,按照字母顺序:gbeta,Objective CAML, Python, Ruby);但我很快得到这样的印象:某种程度上来说,Lisp是所有语言的源头。这样说的主要理由,是Lisp 通过统一了代码(code)和数据(data)形成了一套完成的计算理论(比”仅仅”图林完全更加强大。更多的信息,参见下面”Lisp的技术背景 ”一节)。这意味着(理论上),Lisp没有给你任何限制:如果你用其他任何一种语言可以完成某项工作,Lisp中也可以。更进一步,Lisp在运行期 检测所有类型,所以没有静态类型系统来碍你的事。
总结这些观察,你能够得到这样的结论:Lisp的主旨就是:表达能力是语言唯一重要的属性。当你希望使用这种能力的时候,不应该有任何阻碍。编程语言不应该将自己对世界的看法强加给程序员。应当由程序员来让编程语言适应自己的需要,而不是通过其他的途径。
这 对我有非常大的吸引力,所以我决定使用Lisp。然而,当你更认真的考察Lisp的时候,你会发现Lisp有一个缺陷:整个网络上没有对这门语言好的介绍 -至少我没有找到。当然,还是有一些介绍存在,但是他们或者太浅显,只针对非常初级的程序员;或者太学术,只涉及到一些理论上令人感兴趣的问题。而我希望 看到的,是提供足够的背景材料,能够尽快帮助入门的介绍(某种可以称为”给资深程序员的Lisp介绍”的东西)。
因为我没有找到任何介绍,我不得不自己浏览所有能找到的资料。现在,我要呈现的,就是我自己的总结,希望对后来者有所帮助。
为 什么把这篇介绍称为”极端片面”呢?Lisp是一门非常复杂的语言,或者可以成为一组语言。对于Lisp来说不可能存在一种”权威指南”这样的东 西。我在这里展现的,是我自己希望在最初就看到的。其他人可能会有不同的想法。而我并不准备写一篇适合于每一个人,涉及每一个细节的文章。我只是要想一些 人,比如我自己,提供一些有用的信息。因为这些资料对我来说有价值,我想它们对其他人应该也会有用。
特别的,这不是一篇”21天精通Common Lisp”。你需要反复阅读这里或者别的地方提供的资料,直到你觉得自己”对Common Lisp有经验”了。(我不需要强调,掌握任何严肃的语言所需要的时间都远不止21天。)
1.2 一般性介绍
Lisp 拥有非常久远的历史。它最初被发明于1950年代,并在之后的岁月里不断发展进化。在不同的阶段中,Lisp产生了各种变体,所以Lisp实际上并不是某 一”个”语言,而是某一”种” 语言的总称。(把C, C, Objective C, Java以及C#都看作某一种”类C语言”的话,Lisp的情形也差不多。)
1980年代到1990年代之间,两种主要的变体被广泛 接受与使用,从而逐渐占据了主导地位: Common Lisp和Scheme。Scheme是1970年代,由Gerald Jay Sussman和Guy L. Steele发明的。他们发明Scheme的动机是试图理解面向对象,并将其引入Lisp。Scheme在概念层面上引入了lexical closures (作为对象的一种抽象)和continuations(作为对函数调用与消息传递的抽象);在技术层面上引入了对tail calls和tail recursions的”极度”优化。Scheme的优点在于,它像一颗美丽的宝石,能够吸引那些寻找美与真理(例如,最小化和正交性)的学院派。然 而,它缺少许多你在日常编程中需要的实用的东西,这些东西如果引入则会破坏概念上的完美。
如果你对以上提到的术语感兴趣,参见一下链接:
1. Lexical scope, dynamic scope, (lexical) closures: http://c2.com/cgi/wiki?ScopeAndClosures
2.Continuations: http://c2.com/cgi/wiki?CallWithCurrentContinuation
3.Tail calls, tail recursion: http://c2.com/cgi/wiki?TailCallOptimization
在1970 年代末的时候,若干种Lisp变种都在普遍使用。Common Lisp的产生是对当时分裂混乱状况的一种补救-综合当时流行的若干种Lisp的优点,避免它们的缺陷,形成一种统一的Lisp。进一步说, Common Lisp往前进了一大步,因为它采纳了从Scheme中得到的经验和教训。(Lexical Scope进入了Common Lisp。 Continuations因为实际使用太复杂而未被包括。Tail recursions的优化则被认为是一项自然纯粹的实现技术,没有必要被标准化)。
这些信息提示你,如果你今天考虑准备使用Lisp, 你应该使用什么?如果你希望作一些实际的”现实生活”中的事,你应当选择Common Lisp。如果你主要做学术研究且,比如说,希望尝试contiuations,你可以选择Scheme。两种语言现在都依然被广泛使用,你能从它们的社 区中得到支持。(此外还有一些Lisp变种存在,例如Emacs Lisp和Lush,但是它们都是针对特定领域的,能力比较有限)。
好几个理由促使我决定使用Common Lisp。其中的一些理由会在这份介绍中闪现。我不喜欢Scheme是因为它是一种”正确”的语言,非常强调事情应当如何完成。Common Lisp比起来比较自由。
然而,许多关于Scheme的书或者描述,对于Common Lisp也是有意义的。比如,描述什么是lexical closures。所以下面的章节中,偶尔也会提到一些关于Scheme的参考材料。
(我 的本意是:你选择Common Lisp或者Scheme无关紧要,这仅仅取决于你的特定的需要。让你的直觉来决定你该使用哪一种好了。事实上,两种语言都足够灵活,能够胜任任何研究或 者”现实生活”的工作。只是,我这里的介绍是针对Common Lisp的,如果你决定选用Scheme,你需要去别的地方找介绍材料。下面的”链接”一节中有一些这方面的指南。如果你觉得不喜欢Scheme或者 是Common Lisp的时候,别忘了尝试一下另一个。)
顺带说明一下人们是怎么使用Lisp和Scheme这两个名字的。广义来说, Lisp一般指称整个语言家族,包括Common Lisp, Scheme,更古老的变体(已经不再流行)比如MacLisp,InterLisp,Franz Lisp,以及更新的比如Arc和Goo。狭义来说,Lisp现在只指Common Lisp,不包含Scheme!到底是使用Lisp广义的意思,还是狭义的意思,取决于什么人在什么语境下使用。甚至对广义的理解也是不同的:有一拨人的 看法是不包括Scheme的,而另一拨人甚至包含了 Python和Dylan。(我的个人看法,如果在特定场合下它很重要的话,那就不要采用这样模糊的说法。)
关于如何阅读这份材料:我已经努力地让这份材料可以流畅得顺序读下来了。当然,你可以选择略过某一些章节,或者跳跃着读。后面的一些章节要求你至少能够了解一些Lisp的代码片断,为此文章中提供了足够了指南。
1.3 Common Lisp总纲
http://www.lisp.org/table/objects.htm 是一篇仅有一页的材料,却非常好的回顾了Common Lisp提供的特性。这篇描述着力于CLOS的面向对象特性(Common Lisp Object System是Common Lisp的一部分),同时也对Common Lisp的函数性属性与命令性属性做了不错的总结。
http://www.lispworks.com/products/lisp-overview.html 是Xanalys公司写的The Common Lisp Language Overview。
1.4 最初的障碍
Lisp的历史和其他语言,特别是Algol和C这一系的语言,不同。因此,Lisp社区在各个层次上采用的表示法和术语都和其他语言有所不同。这里先要澄清一些容易出现混乱的地方。
1.有一些Lisp的内建函数取的名字看起来非常可笑。比如”car”被用来得到列表的第一个元素,”cdr”被用来得到列表中剩余的元素。列表使 用”cons”来构造。”mapcar”被用来遍历列表,等等。特别的, Algol或者Wirth家族语言的使用者会奇怪,为什么Lisp的设计者不采用更”自然”的名字,例如”first”,”rest”, ”foreach”等等呢?至少,这就是我面对以上那些名字时的第一反应(显然,我也不是唯一一个)。
请尝试忘记你以前关于”自然”的概念-主要是历史原因,使得Lisp有完全不同的文化。就好像,你可以宣称英语比法语更加”自然”,但这只是因 为你本人只会说英语。实际上,习惯这样一套完全不同的标记法只需要一到两天的Lisp编程经历,那以后这些问题就不存在了。Common Lisp也提供了一些可选的名字,比如”first”和”rest” 用来替换”car”和”cdr”,你也不是没有选择。不过你依然会常常读到那些使用传统名称的代码。
2.看起来Lisp使用了太多的括号。只要选择一个对Lisp的缩进风格支持良好的的编辑器,你很快就可以忘掉括号了。你几乎不用担心在Lisp中写错格 式。请记住,因为格式,如果你忘记了括号或者begin/end这样的关键字,如果你用错了控制指令(例如if,while,switch/case,等 等),C风格的语言和Pascal风格的语言一样会出错。然而,如果你是一个有经验的程序员,我猜你从来没遇到过这样的问题。使用Lisp也是如此。只是 一个适应的问题。
3.有些人依然认为Lisp只提供了列表这样一种数据结构。这种观点来自于他们对某种非常古老的Lisp变体的了解,或者是因为Lisp这个名字本身就是 ”List Processing”的缩写。事实上,Common Lisp提供了所有其他语言中能找到的有用的东西,比如structs(和C风格的structs很像),数组,真正的多维数组,对象(和 Smalltalk和Java中的那样,有成员数据和成员方法),字符串,等等。
类似的,有些人依然认为Lisp是一种解释型语言。事实上,现在已经很少有哪一种Lisp实现是纯粹的解释执行系统了。所有正经的实现都包括了编译器, 能够直接产生机器代码。所以,Lisp一般区分编译器期和运行期。这种区别在某些情况下很重要,比如做宏编程的时候。
但是这依然和所谓的”编译语言”,例如C和大部分Pascal变体,不同。大部分时间里,你不需要显式编译一个Lisp程序然后执行它。你只是和一 个Lisp开发环境进行交互,开发环境会自动编译Lisp代码。所以Lisp和那一些just-in-time语言很类似。Lisp 和某些语言一样有着交互的本质,比如Smalltalk(从某种意义上说,我们可以将Smalltalk归为某一种Lisp变体)。
最重要的一点是,Lisp是非常高效的语言。Common Lisp厂商都努力来提高非常好的性能。(请注意,很多现代的 Java IDE,比如JBuilder, NetBeans, Eclipse等等,都在向Lisp和Smalltalk那样的交互环境发展-所以,这种交互属性即使在Algol风格和C风格的环境下也得到认可。)
近期有一篇比较Lisp和Java与C++性能方面的比较:Erann Gat, Lisp as an Alternative to Java,位于http://www.flownet.com/gat/papers/。(如果你对该论文中提到的Java程序员的编程经验有疑问,请看http://www.flownet.com/gat/papers/ljfaq.html)。
4.描述Lisp时常用到”form”这个术语。Form是Lisp语言格式的基础。例如:
(format t “Hello, World”)
这个form在控制台打印”Hello, World”。(参数”t”在Common Lisp中表示标准的布尔值”truth”,对于format 函数来说,指待标准输出。)所以术语form相当于其他语言中的statements(语句)或者expressions(表达式)。一个Lisp 纯粹主义者会声称Lisp中不存在statements而只有expressions,不过这个对实践没什么差别。其他编程语言中, statements和expressions的差别,也因为存在所谓”expression statements”而变得模糊,这种”expression statements” 的返回值被直接忽略了。在Lisp中你也可以这样做,Lisp甚至定义了没有值的表达式(或者被定义为”未定义值”)。所以实践中,Lisp和其他语 言一样,一样地区别两者,又一样的模糊两者的差异。
Lisp中也存在所谓的’’special forms”。要理解这一术语,你首先要理解Lisp中,函数怎么接受作为参数的 form。上面的例子中,函数”format”接受的参数是”t”和”Hello, World”。以下的例子中,函数”foo” 的参数是”bar”和”goo”。
(foo bar goo)
然而Lisp中也有一些form是不作为函数调用处理的。它们或者是宏,或者是’’special forms”。”Special forms” 指的是有限的一套内建操作。(他们被Common Lisp环境”特殊”处理了。但是要注意,内建函数和宏都不是’’special forms”)。一般都可以直接从form的第一个元素来判断,是一个函数form,一个宏form还是一个special form。这些不同form之间的区别有一些理论和实践上的意义,不过你现在用不着担心他们。当这样的区别变得重要的时候,你会注意到他们,而且很容易处 理。
5.Lisp社区中曾经有过,实际上现在依然在继续着关于”小”语言还是”大”语言的讨论。对于其他社区的人来说着听起来很让人困惑(至少是对我)。到底问题是什么?
Lisp的优点之一就是它不区分内建函数和用户定义的函数。例如:
(car mylist)
(cure mylist)
函数car是Lisp预定义的;而cure不是,所以cure应当是一个用户定义的函数。重点在于你无法从形式上区别他们之间的不同。和下面这个Java的例子做一下比较:
synchronized (lock) {
beginTransaction(…);
…
endTransaction(…);
}
Java中的synchronized语句是一个内建的特性,允许你将一个代码块保护起来,是对特定对象的访问序列化。这个例子中的事务处理函数也定义 了一个代码块,达到相同的事务语义,但你却一眼就能看出来它不是Java内建的特性。(是的,Lisp提供了面向块的语句-哦,不,是forms;而且, 你可以定义自己的面向块的form,并且保持和Lisp内建特性的相似性。)
现在,为了理解所谓小语言大语言的问题,你需要记住上述差异。
这个问题对于Java或者类似的语言的标准化来说,意味着什么?你首先必须标准化语言本身,然后你至少必须标准化一些函数库。就像前面提到的那样,内建特性和函数库之间有很明显的差别,所以这是两件不同的任务。
但是在Lisp世界中不是这样的。因为内建特性和函数库之间没有显著的差别,标准化函数库的过程是标准化语言的一部分(或者从某种程度上来说,反过来)。
所以所谓的小语言和大语言之争,归根到底就是小规模函数库和大规模函数库之间的区别。
总结起来,Scheme是一种小语言,因为它只提供了非常小的一套标准库;而Common Lisp 是一种大语言,因为它提供了很多有用且被标准化了的特性。然而,如果我们只关注语言最核心的部分,那么它们的大小实际上还是相当的。(进一步说,如果你查 看一个”严肃”的Scheme实现,你会发现它也提供了很多没有被标准化的函数库。)
虽然有概念上的差别,不过这对上述小大之争并无帮助。在Scheme和Common Lisp之间选择也不是仅仅因为语言的大小。
6.你也许听说过Scheme是”Lisp-1”的语言,而Common Lisp是”Lisp-2”的语言。另外一种说法是Scheme是one-cell的系统,而Common Lisp是two-cell的系统。人们谈论的是这样一个事实:变量的值和函数定义这两个方面,在Scheme是相同对待的,而Common Lisp中则不是。这并非出于实践上的理由,显然你用这两种语言都可以写出真正的程序。两种语言都提供了相应的方法,帮助解决作为一个”Lisp- 1”(或者”Lisp-2”)系统会面对的问题。你可以忽略这些细节,知道你真正遇到它们;而当你遇到它们的时候,你很容易发现如何正确处理。 ”Lisp-1”系统使得贯彻函数式编程的风格更加容易,而”Lisp-2”系统则使得宏编程更不容易出错。确切的细节有一点麻烦,如果你真的想 了解技术上的影响,你可以阅读以下论文。
1.Richard P. Gabriel and Kent M. Pitman, Technical Issues of Separation in Function Cells and Value Cells http://www.dreamsongs.com/Separation.html
1.5 Lisp的历史
有两篇出色的论文,提供了关于Lisp整个历史非常详细的信息,包括Common Lisp和Scheme,这是一件了不起的事情。某种角度看来,阅读这样两篇论文就像在读侦探小说,吐血推荐!
1.John McCarthy: History of Lisp (ACM SIGPLAN History of Programming Languages Conference, 1978) http://www-formal.stanford.edu/jmc/history/lisp.html
2.Guy L. Steele Jr. and Richard P. Gabriel: The Evolution of Lisp (ACM Conference on the History of Programming Languages II, 1992) http://www.dreamsongs.com/Essays.html
后一篇论文同时也提供了关于Lisp的非常全面的引用线索。
(如果读到Herbert Stoyan关于Lisp的论文,请不要被他搞糊涂了。我个人觉得他的文章很难读,而且推导出一些奇怪的结论。)
1.6 Lisp的技术背景
就 像我之前所说的那样,Lisp通过统一处理数据和代码,形成了一套完整的计算理论--这也是让Lisp如此强大的原因。我这里推荐两篇论文,它们出色的解 释了这种计算理论到底是什么。(它们涉及到”meta-circular interpreters”的概念--不过不要被术语吓倒,并不象听起来的那么难。)在我看来,你至少需要读第一篇,来真正了解Lisp的能力。
1.Paul Graham, The Roots of Lisp http://www.paulgraham.com/rootsoflisp.html
顺便说一句,这篇文章也很好的描述了Lisp中的基础(primitive)forms。
如 果你喜欢”The Roots of Lisp”,并且想知道更多关于”meta-circular interpreter”的情况,你可以阅读这一篇更深层次的讨论。你也会了解到lexical closures,dynamic binding等等的强大之处。
1.Guy L. Steele Jr. and Gerald Jay Sussman: The Art of the Interpreter or, the Modularity Complex (Parts Zero, One and Two), http://library.readscheme.org/page1.html
(这篇论文看起来像是会有续集的样子,但是实际上没有。别费劲去找了,尝试着把结尾作为一个练习吧。)
1.7 可用的实现
你可以在http://alu.cliki.net/Implementation上 找到一个 Common Lisp实现的列表。我只使用过其中的一小部分。许多Lisp程序员喜欢使用emacs或者xemacs作为它们的开发环境,不过我倾向于使用更现代化的 IDE,这很大程度上限制了我的尝试范围。(显然,这只是偏好的问题。如果你对将Emacs设置为一个Lisp IDE感兴趣,http://cl-cookbook.sourceforge.net/windows.html非常不错。)
向Apple Machintosh(Mac OS X)的用户特别推荐:LispWorks for Machintosh是一个出色的IDE,个人版可以免费下载。 Macintosh Common Lisp也是一个免费的IDE,但是和Mac OS X环境的集成不如LispWorks做的好。Digitool可以让你免费使用MCL 30天。CLISP, ECL, OpenMCL以及SBCL在Mac OS X下都可以使用,但是没有IDE。Franz Inc准备为Allegro Common Lisp在Mac OS X上推出一个 IDE,但是还没有具体计划。
我一般使用LispWorks for Machitosh和Machintosh Common Lisp。但是,这不是产品广告-请根据你自己的情况作出选择!
2 精选自学指南
这部分中,我会给一些建议和参考,以帮助你了解Common Lisp的若干方面。(主要是关于标准库的。)
2.1 参考材料
日常编程中,你一般需要一些参考材料,来查询函数定义,语法格式,等等。有的人喜欢看书,有的人则喜欢在线材料。
现在的Common Lisp标准,是1995正式作为ANSI标准发布的,是Common Lisp的权威说明。但是,因为它只给了标准而没有提供原理说明,所以它的可读性比较差。
在1990 年代初的时候,Guy L. Steele写了第二版”Common Lisp the Language”,来反应当时Common Lisp标准化的状况,那是已经非常接近最终标准了。(出版于1980年代的第一版,一般被称作CLtL,被CLtL2取代以后,没有再使用的必要。)虽 然CLtL2还是缺少一些ANSI Common Lisp中定义的特性,而且还有些特性和ANSI标准有出入,它还是被广泛推荐,作为入门材料,因为它的优势在于有非常详细的描述,而不仅仅是规范而已。 这帮助你理解材料。不过你手边还是需要一份ANSI标准,以便查得某一个定义的确切细节。进一步,ANSI文档包含了非常有用的术语表。
幸运的是,ANSI Common Lisp和CLtL2都有在线的HTML和PDF/PS格式可以下载。这里是链接:
1.ANSI Common Lisp: http://www.lispworks.com/reference/HyperSpec/
“HyperSpec”的编辑强调,这不是权威的规范,而仅仅是从标准ANSI规范的衍生物。然而,我猜测这仅仅是出于法律的原因。 HyperSpec是可以信赖的,实际上,两份文档是从同一份TeX生成的。
2.Franz, Inc.提供了一份不同风格的在线文档,http://www.franz.com/support/documentation/6.2/ansicl/ansicl.htm
3.CLtL2: http://www-2.cs.cmu.edu/afs/cs.cmu.edu/project/ai-repository/ai/html/cltl/cltl2.html 或者:http://oopweb.com/LISP/Documents/cltl/VolumeFrames.html上有一个带Frame的版本。
注意:CLtL2的附录中有关于所谓的”Series”宏和”Generators and Gatherers”,可以实现lazy iterators。这些特性并未进入ANSI Common Lisp。我没有找到相关的理由,也无从了解为什么它出现在CLtL2中,却最终没有被收入进ANSI Common Lisp,我猜测是因为它们实验性太强。”Series”可以在http://series.sourceforge.net上找到。
4.一个关于CLtL2与ANSI Common Lisp之间差别的列表,位于 http://home.comcast.net/%7ebc19191/cltl2-ansi.htm。
如果你更喜欢印刷品的化,你得花费一些时间来获得。CLtL2看起来已经绝版了,但是你还是可以尝试着从Ebay上得到一本旧的。在Amazon这样的在线书市上也可以找到。你还可以尝试一下出版商的主页:http://www.bh.com/digitalpress。
ANSI文档可以在http://global.ihs.com/上获得。不过我估计它既不方便也不有用,因为这本书超过1000页。而且价格将近$400,实在是太贵了。
Paul Graham的”ANSI Common Lisp”是一本很好的书,不过我觉得拿它做参考并不合适。我一般还是倾向于把标准规范打印出来(例如Java语言规范),不过对于Common Lisp来说,电子版的就可以了。
2.2 基础中的基础
Paul Graham写了一本非常不错的关于ANSI Common Lisp的书,叫做”ANSI Common Lisp”。非常适合阅读,而且幸运的是,作者在他的主页上公开了最初两章。我特别推荐第二章,它让你很快就能对Common Lisp编程有一个概念。
1. Paul Graham, ANSI Common Lisp, http://www.paulgraham.com/acl.html
如果你喜欢他的风格,你可以考虑购买。
那之后你需要的,只是查看CLtL2或者ANSI标准而已。下面是一些我个人认为比较难得到的信息。
为了能够彻底理解以下的章节,你需要先阅读”The Roots of Lisp”(http://www.paulgraham.com/rootsoflisp.html)或者是”ANSI Common Lisp Chapter 2”(http://www.paulgraham.com/lib/paulgraham/acl2.txt)中的一篇,或者你至少已经能够理解Lisp的代码。
2.3 基础的高级内容
以下是一些你早晚会碰到的小问题。
1.Common Lisp允许在函数定义中包含可选参数(optional arguments),剩余参数(rest arguments),关键字参数(keyword arguments),参数缺省值(arguments with default values)以及以上各种形式的组合。如何使用这些形式的确切规范可以在CLtL2的5.2.2(“Lambda Expressions”)和ANSI specs 3.4(“Ordinary Lambda Lists”)中找到。
(Lambda Expressions指未命名的函数。命名函数类似于其他编程语言中的过程(procedures),函数(functions)以及方法 (methods)。未命名函数类似于Smalltalk中的blocks,从某种意义上说,Java中的anonymous inner classes。参见CLtL2 5.2(“Functions”)以及相关章节中的说明。)
2. Common Lisp中,标准输出一般使用’format’函数。这某种程度上类似于C语言中的printf(不过,我承认我不是printf的专家!)。我还没有在 任何文章中见到过关于’format’不错的论述,所以你不得不去研读CLtL2中的相关章节。
CLtL2中,format是在22.3.3(“Formatted Ourput to Character Streams”)中描述的。对应的ANSI specs是22.3(“Formatted Output”)。
3.Common Lisp中的字符串有一点点复杂。首先是因为Common Lisp标准化在Unicode出现之前,它已经意识到ASCII或者其他一些有限的字符集是不够的。所以Common Lisp标准制定了处理字符串的一般方法,而把一些问题留给了具体的实现。
一般来说,这个问题只有在你需要和其他编程语言开发的程序进行交互的时候,才会凸现出来。Allegro Common Lisp,CLISP以及LispWorks都提供了 Unicode支持,其他的实现就我所知还没有。(当然,你可以自己实现对Unicode的支持,Lisp处理起这些事情来是足够灵活的。;-)
如果你想知道关于字符串的更多细节,参考CLtL2,ANSI specs以及你使用的Common Lisp实现自带的文档。
4.对于文件名字的处理,各个平台间是不同的。Common Lisp标准化了一个框架,以使得实现者可以根据具体操作系统的要求来填补框架中的留白。所以,又一次,你需要参考CLtL2,ANSI specs,以及你使用的Common Lisp实现自带的文档。
2.4 宏
Common Lisp的宏特性是这个语言的亮点之一-它是一种强大的方法,使得你可以书写自己的编程语言结构,而不仅仅是更多的函数而已。
宏一般被认为是高级主题,因为它非常强大,且看起来很难掌握。然而,我发现宏的概念还是非常容易理解的,而写起来也比较容易的。
你可以在Paul Graham的”On Lisp”中找到关于宏的非常好的描述,以及关于如何使用宏的说明。这本书已经绝版了,但是你可以免费从以下URL下载到电子版
Paul Graham, On Lisp, http://www.paulgraham.com/onlisp.html
以下是我认为的一些注意事项:
1.Common Lisp中的宏和C中的宏并不相同!就我所知,C语言中的宏只是对字符序列的处理,并不关心所处理的程序结构。相反的,Common Lisp中的宏直接作用于程序结构。这完全是因为Lisp中将数据和代码一致处理,既然程序就是一个forms构成的列表,那么宏就可以像处理其他列表一 样地处理forms的列表。
所以你看到术语”宏”的时候,请三思 - 特别是对于其他一些尚在发展中的编程语言。
(因为我不会再讨论C语言中的宏,从现在开始,我提到”宏”的时候,都是指Lisp的宏。)
2.宏可以被理解为一种接受参数,产生新代码的函数。在编译过程中,每发现一个宏,都会把相应的form传递给宏,然后用得到的结果替换最初的form。然后编译过程继续处理新得到的form。这个过程被称为”宏展开”。
下面这个例子定义”cure”为”car”。
(defmacro cure (argument)
(list ‘car argument))
所以,每当你看到(cure a),其结果都是编译期的(car a) - 宏的结果表示在使用宏时 真正用到的代码。
(请注意这个例子只是用于展示概念。实际上它不是一个使用宏的好例子: “cure”最好是被定义为一个函数。)
你所看到的事实是,宏是Turing-complete的。所有Common Lisp的能力在编译期都可以使用,来帮助宏产生新的代码。宏可以包含其他的宏展开和函数,甚至包括迭代、递归,condition,等等等等。而且结果 中可以进一步包含其他的宏,这使得”宏展开”不断重复,直到最终结果中只包含纯粹的函数。
举例来说,Common Lisp中的宏可以用来做和C++中template meta-programming一样的事情。特别的,它可以用来进行Generative Programming!(Generative Programming的基础是定义一种 domain-specific的语言,开发相应的编译器来将语言转化为general-purpose的编程语言。)
3. 一般介绍到宏的时候,都会提到”backquote”这种形式。我认为这种介绍方式使得事情无端变得复杂起来。事实上,当你理解前述的关于宏的 Turing-completeness特性时,你已经在概念层面上对宏有了全局认识。然而,宏和backquote的组合在实践中还是很有益处的。以下 我会介绍backquote的格式,以及它如何和宏进行配合。
如前所述,Lisp中的大部分form都是函数。所以(car mylist)从mylist对应的列表中得到第一个元素。但有时候,你不希望对form进行求值(你不希望将这个form当作函数来处理)。比如,在 (car (a b c))中, (a b c)缺省不被认为是一个三元素组成的列表,而是函数a以及参数b与c。如果你想避免对(a b c)求值,你必须使用一个特别的 form,”quote”:(car (quote (a b c)))。现在,整个表达式的求值结果为”a”(列表(a b c)中的第一个元素。) 注意(quote …)不是一个函数,而是一个特殊的form(Lisp的内建属性)。
因为quote在Lisp使用是如此的频繁,我们发明它的简化形式: (car ‘(a b c))就可以表示(car (quote (a b c)))了。顺便说一句,quote也可以用来避免对一个变量求值。变量在Lisp中只是名字而已(和在其他编程语言中一样),所以如果你写下,比如说, (car a),得到的将会是变量a所引用的列表的第一个值。为了避免对变量求值,你可以写作(quote a),或者’a,你可能已经猜到了。
有时候,你需要对一些表达式或者变量求值,然后将结果组合成一个列表。Lisp中最简单的方式是: (list a (car b) c),先后对a, (car b)以及c进行求值,并把各个结果拼接成一个列表。又有时候,你只需要对其中部分进行求值。这也难不到你:(list a (car b) ‘c),先后对 a和(car b)进行求值,这样就可以保证得到的结果中,列表的最后一个元素肯定是c。
有时候,需要quoted(不求值)的表达式数量上要远远多余需要求值的表达式。这时,backquote就可以帮上忙了。它逆转了缺省的行为,使得缺 省的所有表达式都不被求值,需要被求值反而需要标记。以下例子中`(a b c ,(car d)) 和(list ‘a ‘b ‘c (car d))完全一样,符号a,b以及c都没有被求值,而(car d)被求值了。在backquote 形式下,需要被求值的表达式需要用逗号标出。(注意,这和quote形式不同,在quote形式中,整个表达式都会被作为quoted处理,且不能使用逗 号来对其中的一部分进行求值。)
所以,本质上,backquote形式只是一个简化,便于程序员临时改变求值与否的缺省行为。
现在,我们回忆一下之前的例子。
(defmacro cure (argument) (list ‘car argument))
从之前的讨论中,你可以推断出这和一下形式是等价的。
(defmacro cure (argument) `(car ,argument))
你现在已经可以理解,backquote形式对于宏来说为什么那么重要:宏定义本身和最终要产生的代码形式上更加接近了。(还记得嘛?(cure mylist) 在编译期会变成(car mylist)。)
顺便说一下,这里有一篇关于backquote形式历史的论文,非常不错。
Alan Bawden, Quasiquotation in Lisp, http://citeseer.nj.nec.com/bawden99quasiquotation.html
4.宏也常常成为Common Lisp和Scheme两派人马之间论战的焦点。Common Lisp中的宏有可能意外的捕获宏定义周围代码中的变量名。这听起来很危险,但是实际上不是。请看Paul Graham的”On Lisp”一书中对这个问题的详细讨论 - 我在这里不准备讨论细节。
Scheme提供了一种所谓”hygienic macros”的概念(简单的说,”hygiene”),来避免捕获变量。我所看到的问题是,hygienic macros被简单的描述为对macros的发展。常见的说法是: “Common Lisp的宏可能捕获变量名; hygiene避免了这一点,所以程序更安全了。”这种论点有一些正确,因为它在某些角度上延续lexical closures的概念。
然而这并不是真相的全部。有时候,你确实需要捕获变量名,而hygiene使得这件美好的事情变得无谓的复杂。更进一步说,在Common Lisp中避免变量名捕获实际上也是非常容易的,Paul Graham在他的书中展示了这一点,所以解决它并没有实际价值。
我的观点是,hygiene在Common Lisp中不是必须的(实际上也没有提供) ,因为写出安全的宏实际上很容易。除非你在理论层次上对这个问题感兴趣,你完全可以忽略hygienic macros这个概念。
(在Scheme中,情况略有不同,因为Scheme将变量和函数定义放在同一个名字空间里--所谓的Lisp-1。这意味着意外捕获函数定义的情况在 Scheme中发生的可能性要远大于在Common Lisp中。因此,Scheme更迫切地要解决这个问题。有一篇非常好的文章描述了macro hygiene问题:
Alan Bawden and Jonathan Rees, Syntactic Closures, http://citeseer.nj.nec.com/bawden88syntactic.html
文章给出的例子,可以作为一个非常好的练习:为什么这些问题在”Lisp-2”中并不突出?在什么样的情况下你会需要捕获变量名。
也可以参看上面提到的Gabriel和Pitman所写的关于隔离function和value名字空间的论文。 )
2.5 面向对象(CLOS)
Common Lisp对象系统(CLOS)是ANSI Common Lisp的一部分。然而,因为它很晚才被加入标准,有些厂商需要时间来实现标准。现在,绝大部分实现都提供了CLOS支持。(有些厂商将这一点作为自己产 品的特性进行宣传,我觉得很荒谬。严格来说,如果没有完全支持CLOS的话,这个Common Lisp 系统甚至不能宣称自己实现了ANSI标准。)
CLOS提供了完全的面向对象支持,包括类,子类化,多继承,多方法(multi-methods),以及before-, after- 以及around-等等。这已经超出了其他面向对象编程语言所提供的特性。
更 进一步的,一个所谓”Meta-Object Protocol”(MOP)可以用来检测类的继承关系,以及运行时方法 dispatch的过程。Meta-Object Protocol不是ANSI Common Lisp的一部分,但是因为绝大多数CLOS/MOP实现都继承自同一套代码,所以它也成为了事实标准。(CLOS和MOP不是独立的两个东西,就像 Java语言和它的reflection API,但是MOP可以被认为是CLOS的一个超集。)
关于CLOS和Meta-Object Protocol的概述可以在这篇文章中找到。
Linda G. DeMichiel and Richard Gabriel, The Common Lisp Object System: An Overview, ECOOP ‘87, Springer LNCS 276, freely downloadable at the bottom of http://www.dreamsongs.com/Essays.html (thanks to Richard Gabriel for making this available on my request)
关于CLOS设计原理的描述可以在这篇文章中看到。
P. Gabriel, Jon L. White, and Daniel G. Bobrow, CLOS: integrating object-oriented and functional programming, Communications of the ACM, Volume 34, Issue 9, 9/1991, http://doi.acm.org/10.1145/114669.114671
Jeff Dalton提供了对CLOS(不包括MOP)的一个介绍,位于http://www.aiai.ed.ac.uk/jeff/clos-guide.html. 针对MOP,有类似于ANSI HyperSpec的参考资料,位于http://www.lisp.org/mop/. (这个页面上的大部分外部链接都失效了,但是这个参考材料本身倒是没出什么问题。)
Barry Margolin在comp.lang.lisp上给出了一个不错的原则:
有一些MOP相关的函数成功的打入了ANSI CL的内部,比如ADD-METHOD和ENSURE-GENERIC-FUNCTION,但数量并不多。
区 别一个函数到底是CLOS的一部分抑或是MOP的一部分,可以观察它有没有参数必须是class, method或者generic-function objects。函数MAKE-INSTANCE和CHANGE-CLASS之流不受这个规则约束;虽然它们可以接受一个class object,它们也可以一个class name。MOP函数不提供这种便利。
下面这篇文章中有一个精心打造(令人印象深刻)的例子,你可以从中了解Meta-Object Protocol的能力。
Andreas Paepcke, User-Level Language Crafting - Introducing the CLOS Metaobject Protocol, in: Object-Oriented Programming: The CLOS Perspective, MIT Press, 1993, freely downloadable somewhere at the bottom of http://www-diglib.stanford.edu/paepcke/shared-documents/bibliography.html
一些说明:
1.多继承被一些人认为是一种糟糕的设计,因为它可能会导致一些难于处理的名字冲突。绝大多数编程语言都提供了一些缺省行为来处理这种冲突,但是这些措施的 结果一般都不令人满意。 Common Lisp也提供了一种缺省行为,通过程序员给出父类的顺序来确定一种拓扑顺序,以处理名字冲突。反对者很容易找出一个例子,使得缺省行为无法给出有用的结 果。看起来Common Lisp 的方案和其他编程语言的一样糟糕。
然而,CLOS允许用户定义”method combinations”。程序员总可以根据不同情况显式指定方法如何dispatch。 (而且,这些都可以在运行时修改。)所以CLOS提供了非常高层次的灵活性来处理名字冲突。父类的拓扑排序可以理解为系统给出的建议。
理论上说起来,名字冲突不论在哪种语言中都很难处理。又一次,Common Lisp给程序员足够的自由,可以处理他可能遇到的各种问题。作为比较,如果在Java中,继承自不同interface中的名字导致冲突,你没有一点办法。
2.标准的method combinations和Meta-Object Protocol允许以aspect-oriented风格编程。这不是偶然的--Gregor Kiczales, Aspect-Oriented Programming的发明人之一,也是CLOS的设计者之一。不用再等AspectJ了,所有你需要的都已经在Common Lisp里了!
2.6 LOOP
所谓的LOOP,是Common Lisp的标准特性之一,使得程序员可以用类似Algol/Wirth的形式来写迭代。这里是loop的一个例子,它打印出十个’*'。
(loop for i from 1 to 10 do (format t “*”))
CLtL2 是非常好的信息来源。然而,你很快会发现,各种形式的组合实在是太多了。我的感觉是,学习所有细节没有什么意义,这太耗时间了。LOOP设计的目的显然是 为了支持用某种”自然”的,类似英语的方式表示迭代。某些情况下,LOOP确实使得代码容易理解了。(这也是嵌入Common Lisp中的一种domain-specific语言,专门处理迭代这个domain。)
这里是另一个,更有趣的使用LOOP的例子。(Matthew Danish提出).
(defun fibonacci (n) (loop for a = 1 then (+ a b) and b = 0 then a repeat n collect b))
看起来,当初LOOP的发明者的意图,是鼓励程序员自己去”尝试”去表达迭代的方法。如果尝试失败,你可以查看CLtL2或者ANSI specs,或者回去使用更加Lisp化的方法来表达迭代和递归(使用do, dotimes, mapcar等等)。
这只是我的猜测。我不知道当初LOOP的发明者的意图。(有人宣称其中细节很容易理解,但是我自己还没有尝试。)
2.7 Conditions
Conditions 类似于Java语言中的异常。然而,condition的能力更加强。例如, condition handlers可以命令Lisp运行环境在condition被抛出的地方继续执行执行。如果你已经习惯于异常处理,那么第一次听到这个也许有些奇怪。 异常不是意味着问题嘛? Common Lisp中的condition即使没有错误也可以触发,例如,多线程代码需要同步,或者其他代码需要在特殊事件上被触发,或者其他什么你认为有用的情 况。更进一步,有些conditions代表的问题可以在运行过程中修复。或者是以交互方式,或者用代码。
大部分Object-Oriented编程语言的模型只是CLOS的特殊形式,类似的Java的异常处理也只是Common Lisp的Condition系统的一个特殊形式。
下面这篇文章出色地阐述了在Common Lisp中的如何进行condition handling,附带也有一些历史典故。
Kent Pitman: Condition Handling in the Lisp Language Family, in: Advances in Exception Handling Techniques, 2001, Springer LNCS 2022, http://www.nhplace.com/kent/Papers/Condition-Handling-2001.html
一些说明:
1.Common Lisp提供了catch-和throw这样的结构,以支持从函数中跳出(non-local exits)。这和Java中的 try-catch-finally并不相同!对于try-catch,Common Lisp对应的是handler-case或者handler-bind这样的东西,而try-finally则对应Common Lisp中的unwind-protect结构。请参考上面的文章以获得详细说明。
2.顺便说一句,Condition也可以用来实现Aspect-Oriented Programming,这样你就不用和元数据(meta)打交道了。
2.8 高级编程技巧
我 在前面已经数次提到Paul Graham的”On Lisp”一书。这本书中还有很多其他Common Lisp 是用的高级技巧,比如创建工具函数,高阶函数,数据库存取,模仿Scheme中的continuations,多进程,非确定性计算,parsing,逻 辑计算,面向对象。(当然,这本书中的大部分内容还是关于宏的。)
我再重复一遍这个链接
Paul Graham, On Lisp, http://www.paulgraham.com/onlisp.html
2.9 Packages
Java 有一个不错的module系统,允许你将classes绑定到packages中,以避免 classes相互之间的冲突。更棒的是,packages的结构反应了相应的文件目录结构。这样,简单的调整classpaths,就可以支使Java 运行环境就可以根据需要加载classes。
Common Lisp也提供了一个package系统,也允许把定义(包括classes)绑定到package。然而,Common Lisp的package系统和文件目录结构之间不存在任何联系。 Common Lisp中的package系统只决定Lisp运行环境中的数据定义如何安排。
事实上,Common Lisp的package系统的强大之处更在于它支持把符号(symbols) 绑定到packages中去。因为Common Lisp中的定义总是通过符号访问的,所以绑定定义实际上只是绑定符号的一部分。这里有一个Kent M. Pitman给出的漂亮例子,它展示了你可以用 Common Lisp的package做什么。(参见http://makeashorterlink.com/?E3B823F91以获得完整的信息。)
“CL allows me, simply in the symbol data, to control whether I’m doing various kinds of sharing. For example, I can separate my spaces, as in:
(in-package “FLOWER”)
(setf (get ‘rose ‘color) ‘red) (setf (get ‘daisy ‘color) ‘white)
(defun colors-among (things) (loop for thing in things when (get thing ‘color) collect thing))
(in-package “PEOPLE”)
(setf (get ‘rose ‘color) ‘white)
(setf (get ‘daisy ‘color) ‘black)
(defun colors-among (things) (loop for thing in things when (get thing ‘color) collect thing))
(in-package “OTHER”)
(flower:colors-among ‘(flower:rose people:rose flower:daisy)) => (FLOWER:RED FLOWER:WHITE)
(flower:colors-among ‘(flower:rose people:daisy people:rose)) => (FLOWER:RED)
(people:colors-among ‘(flower:rose people:rose flower:daisy)) => (PEOPLE:WHITE)
(people:colors-among ‘(flower:rose people:daisy people:rose)) => (PEOPLE:BLACK PEOPLE:WHITE)
Common Lisp的Package支持对符号的绑定,而对定义进行绑定只是其中的一个部分。但是package系统并不支持搜索或者加载操作。Common Lisp术语中,后一个操作被成为”系统构建(system construction)”。
总而言之,不要被两种语言之间的区别搞混了。Java和Common Lisp只是恰好都选用了package这个术语,而他们的目的是不同的(虽然有一些重叠)。
Common Lisp对系统构建的支持很有限(函数”load”和”require” - 参见CLtL2和ANSI specs)。关于这个问题,下一节有更详细的说明。
2.10 缺点什么?
ANSI Common Lisp标准于1994年定稿,1995出版。彼时Java还在角落里成长,并没有公开。 Internet也没有做商业推广。显然,过来的这7年里,编程语言在很多方面都有更进一步的要求了。遗憾的是,Common Lisp的官方标准没有与时俱进。ANSI Common Lisp中没有涵盖的一些东西,现在已经成了其他一些时髦编程语言中的必备的基础。比如:模块(系统构建)系统,Unicode支持,平台无关的GUI 库,socket和TCP/IP,XML,WebServices,等等。
然而,Lisp世界并非停滞不前。ANSI Common Lisp标准中没有说明并不意味着客观上不存在。
两个最广泛使用的系统构建软件是ASDF(http://www.cliki.net/asdf) 和MK-DEFSYSTEM(http://www.cliki.net/mk-defsystem)。 有些Common Lisp实现也有它们自己的系统构建支持。Allegro Common Lisp, LispWorks以及 CLISP支持Unicode。CLIM是一个平台无关的GUI库,它被若干家厂商支持,包括Franz, Xanalys以及 Digitool。绝大多数正经的Common Lisp实现都支持socket和其他高级网络特性。CLISP 支持fastcgi。CL-XML是一个可以用于处理XML的库。诸如此类。
基本原则就是,如果你需要某一些功能,google一下,你也许就能找到合适的库。下面的链接也会有一些帮助。
3 Links
以下列出一些有用的站点,其中部分在前文已经提及。
3.1 个人站点
1.Bill Clementson’s homepage, http://home.comcast.net/ bc19191/
2. Richard Gabriel’s homepage, http://www.dreamsongs.com - one of the fathers of Common Lisp, various interesting essays can be found at his homepage
3.Erann Gat’s homepage, http://www.flownet.com/gat/ - he conducted the study that compared performance characteristics of Lisp, Java and C++
4.Paul Graham’s homepage, http://www.paulgraham.com - author of the books “ANSI Common Lisp” and “On Lisp”, also offers various interesting articles
5.Rainer Joswig’s homepage, http://lispm.dyndns.org/
6.David B. Lamkins’ homepage, http://psg.com/ dlamkins/ - author of the freely available online tutorial “Successful Lisp”
7.John McCarthy’s homepage, http://www-formal.stanford.edu/jmc/ - he invented Lisp in the 1950’s
8.Peter Norvig’s homepage, http://www.norvig.com
9.Andreas Paepcke’s homepage, http://www-db.stanford.edu/ paepcke/
10.Kent Pitman’s homepage, http://www.nhplace.com/kent/ - he was the project lead for the Lisp condition system
11.Kevin Rosenberg’s homepage, http://b9.com/
12.Mark Watson’s homepage, http://www.markwatson.com/ - author of the freely available book “Loving Lisp - the Savy Programmer’s Secret Weapon”
13.Edi Weitz’s hompage, http://weitz.de/
3.2 Common Lisp参考
1.ANSI Common Lisp (a hyperlinked version of the standard), http://www.franz.com/support/documentation/6.2/ansicl/ansicl.htm
2.The Common Lisp HyperSpec, http://www.lispworks.com/reference/HyperSpec/
3.Common Lisp the Language, second edition, http://www-2.cs.cmu.edu/afs/cs.cmu.edu/project/ai-repository/ai/html/cltl/cltl2.html. See also http://oopweb.com/LISP/Documents/cltl/VolumeFrames.html for a framed version.
4.Converting CLtL2 to ANSI CL, http://home.comcast.net/ bc19191/cltl2-ansi.htm
5.The Common Lisp Object System MetaObject Protocol, http://www.lisp.org/mop/ (not part of ANSI Common Lisp, but considered a de-facto standard)
3.3 背景材料
1.Common Lisp - Myths and Legends, http://www.lispworks.com/products/myths_and_legends.html
2.Continuations, http://c2.com/cgi/wiki?CallWithCurrentContinuation - an explanation on the WikiWikiWeb (http://c2.com/cgi/wiki)
3.FAQ for comp.lang.lisp, http://www-jcsu.jesus.cam.ac.uk/ csr21/lispfaq.html
4.The Feyerabend project, http://www.dreamsongs.com/Feyerabend/Feyerabend.html
5.The original ‘lambda papers’ by Guy Steele and Gerald Jay Sussman, http://library.readscheme.org/page1.html - these papers date back to the 1970’s and discuss various aspects of and around Scheme; some of them are also of interest to Common Lispers
6. LISP 1.5 Programmer’s Manual, http://green.iis.nsk.su/ vp/doc/lisp1.5/, for historically interested folks
7.Lexical scope, dynamic scope, (lexical) closures, http://c2.com/cgi/wiki?ScopeAndClosures - an explanation on the WikiWikiWeb (http://c2.com/cgi/wiki)
8.Lisp Scheme Differences, http://c2.com/cgi/wiki?LispSchemeDifferences - a discussion that compares Common Lisp to Scheme on the WikiWikiWeb (http://c2.com/cgi/wiki)
9.Common Lisp/Scheme Comparison, http://makeashorterlink.com/?E2E9254F6, an excellent comparison by Ray Dillinger
10.Meta Object Protocol, http://c2.com/cgi/wiki?MetaObjectProtocol - a discussion about Common Lisp’s MOP on the WikiWikiWeb (http://c2.com/cgi/wiki)
11.Tail calls, tail recursion, http://c2.com/cgi/wiki?TailCallOptimization - an explanation on the WikiWikiWeb (http://c2.com/cgi/wiki)
3.4 Repositories, link collections, software
1.The ALU (Association of Lisp Users) Wiki, http://alu.cliki.net/, the portal to the Common Lisp world, includes a list of Common Lisp implementations and much more
2.CLiki, http://www.cliki.net/, a collection of links to and resources for free software implemented in Common Lisp
3.Common-Lisp.net, http://common-lisp.net/, is to Common Lisp what SourceForge is to the rest of the world
4.The Common Lisp Cookbook, http://cl-cookbook.sourceforge.net/
5.Common Lisp Hypermedia Server, http://www.ai.mit.edu/projects/iiip/doc/cl-http/home-page.html, is a full-featured, open source web server implemented in Common Lisp
6.Common Lisp Open Code Collection, http://clocc.sourceforge.net
7.The Common Lisp Open Source Center, http://opensource.franz.com/
8.setf.de Lisp Tools, http://www.setf.de/, includes CL-XML, a bnf-based parser, java2lisp and several other tools
9.CLUnit, http://www.ancar.org/, a unit testing framework (similar to JUnit)
10.JACOL, http://jacol.sourceforge.net, a framework for interaction between Java and Common Lisp via sockets
11.UFFI, http://uffi.b9.com/, a package to interface Common Lisp programs with C-language compatible libraries
12.How to create dynamic websites with Lisp and Apache, http://lisp.t2100cdt.kippona.net/lispy/home
13.BioLisp.org, http://www.biolisp.org/, intelligent applications in BioComputing
14.ACL2, http://www.cs.utexas.edu/users/moore/acl2/, a semi-automatic theorem prover for an applicative subset of Common Lisp
15.Dynamic Learning Center, http://www.dynamiclearningcenter.com/, a teachers’ and students’ on-line learning resources
3.5 Scheme links
1.DrScheme, http://www.drscheme.org, is an implementation of Scheme I have checked out and liked a lot. If I hadn’t opted for Common Lisp, I would probably have stuck with this system.
2.Scheme FAQ, http://www.schemers.org/Documents/FAQ/
3.6 Copyright issues
* Copyright Quick-Start for Online Authors, by Gene Michael Stover, http://gms.freeshell.org/htdocs/copyright/copyright.html
4 Acknowledgements
Many thanks (in alphabetical order) to Tom Arbuckle (http://www.cs.uni-bonn.de/ arbuckle/), Joe Bergin (http://csis.pace.edu/ bergin/) and Richard Gabriel (http://www.dreamsongs.com/) for providing lots of useful feedback on the draft version. Thanks to Seung Mo Cho for the Korean translation.
Further thanks for even more feedback go to Paolo Amoroso, Marco Antoniotti, Tim Bradshaw, Christopher Browne, Thomas F. Burdick, Wolfhard Bu, Bill Clementson, Matthew Danish, Biep Durieux, Knut Aril Erstad, Frode Vatvedt Fjeld, John Foderaro, Paul Foley, Erann Gatt, Martti Halminen, Bruce Hoult, Arthur Lemmens, Barry Margolin, Nicolas Neuss, Duane Rettig, Dorai Sitaram, Aleksandr Skobelev, Thomas Stegen, Gene Michael Stover, Rafal Strzalinski, Raymond Toy, Sashank Varma and Espen Vestre. (Many of them are active participators in comp.lang.lisp.)
posted on 2011-10-21 22:21 Mr__BRIGHT 阅读(1840) 评论(0) 编辑 收藏 举报