实用zhlisp编程01:为什么是Lisp

  • 0

实用zhlisp编程01:为什么是Lisp

Category:中文学习 Tags : 

绪论:为什么是 Lisp?

如果你认为编程最大的乐趣在于可以用那些简单和清楚地表达你各种想法的代码来完成许多事,那么用 Common Lisp 编程可说是用计算机所能做到的最有趣的事了。相比很多其他的计算机语言,Common Lisp 可以让你更快地完成更多工作。

这是个大胆的断言。可我要怎样证明呢?当然不是用本章的只言片语了,而是这一整本书——你必须先学习一些 Lisp 知识才能体会到这一点。不过眼下,让我们先介绍一些轶事,即我本人迈向 Lisp 编程之路的经历。然后,在下一节里,我将说明你可以通过学习 Common Lisp 得到些什么收获。

我是极少数的所谓第二代 Lisp 黑客之一。我父亲的计算机生涯开始于用汇编语言给他的机器编写操作系统,从而为他的物理学博士论文搜集资料。在成功帮助了几家物理实验室用上计算机系统之后,从 20 世纪 80 年代起,他彻底抛弃了物理学,转而为一家大型制药公司工作。当时那家公司里有个软件项目,正在开发用于模拟化工生产过程的软件,比如说,增大容器的尺寸将会怎样影响其年产量。原先的团队使用 FORTRAN 进行开发,已经耗费了该项目的一半预算和几乎全部的时间,却还是没能交付任何成果。那是在 20 世纪 80 年代,人工智能(AI)蓬勃发展,Lisp 正在流行。于是,我父亲(当时还不是 Lisp 程序员)来到卡内基梅隆大学(CMU),向那些正在开发后来被称为 Common Lisp语言的人们咨询:如果用 Lisp 来开发这个项目是否合适?

CMU 的人向我父亲演示了一些他们正在做的东西,于是他被说服了。然后他又说服老板让他的团队接手那个失败的项目并用 Lisp 重新开发。一年以后,仅仅使用了原先剩余的预算,他的团队就交付了一个可用的应用程序,而且还带有已经被原先团队所放弃的一些功能。我父亲将其团队的成功原因归结为他们使用了 Lisp。

当然,仅此一例还不足以说明问题,而且也有可能我父亲对其成功原因认识有误,或者也许 Lisp 仅仅在那个年代才可以跟其他语言相媲美。如今我们有了大量精巧的新语言,它们中的许多都吸收了 Lisp 的特性。是否真的可以说,Lisp 在现今也具有跟我父亲那个年代一样的优势吗?请继续往下读。

尽管父亲作了大量努力,但我直到高中也没有学过一点儿 Lisp。在大学时代我也没怎么用任何语言来写程序,是后来出现的 Web 让我回到了计算机的怀抱。我首先使用的是Perl,先是为 Mother Jones 杂志的网站构建在线论坛,待经验丰富以后转向 Web 商店 Organic Online,在那里我为当时的大型 Web 站点工作,例如耐克公司在 1996 年奥运会期间上线的站点。后来我转向 Java,并成为 WebLogic(现在是 BEA 的一部分)的早期开发者。离开 WebLogic 以后,我又作为另一个团队的首席程序员开始用 Java 编写事务消息系统。在此期间,对编程语言的广泛兴趣使我充分研究了诸如 C、 C++ 和 Python 这些主流语言,以及 Smalltalk、Eiffel 和 Beta 这些小众语言。

所以我对 Perl 和 Java 两种语言了如指掌,同时还熟悉其他不下六种语言。不过最终我还是意识到,我对编程语言的兴趣其实来源于记忆之中的我父亲的 Lisp 经历——语言之间真的有天壤之别,尽管所有的编程语言在形式上都是图灵等价的,但一些语言真的会比其他语言更快更好地完成某些事情,并且在使用的过程中还能给你带来更多的乐趣。不过可笑的是,我从没有在 Lisp 上花太多时间。于是我开始利用业余时间研究 Lisp。无论我做什么,最令我兴奋的始终是可以很快地将思路变成可用的代码。

举个例子,在一次假期里,我差不多在 Lisp 上花了一个星期的时间,目标是实现一个基于遗传算法用来下围棋的程序系统——我以前做 Java 程序员的时候已经写过了。虽然我那时的 Common Lisp 知识极其有限,还要不时地查询基本函数的用法,但我还是能感觉到比用 Java 重写同样的程序来得顺手——尽管从编写该程序的最初版本以来,我又多了几年的 Java 经验。

类似的经历还产生了我将在第 24 章里讨论的一个库。我以前在 WebLogic 的时候曾经用 Java 写了一个用来解析 Java 类文件的库。虽然库可以正常使用,但是代码有点乱,并且难以修改或扩展。几年来我曾多次重写这个库,并期望凭借日益提高的 Java 技巧可以找到某种方式来消除其中大量的重复代码,可惜从未成功。但当我改用 Common Lisp 做这件事时,我只花了两天时间,最后不但得到了一个 Java 类文件的解析器,而且还产生了一个可用于解析任何二进制文件的通用库。第 24 章将讨论这个库的工作原理,第 25 章会用它写一个 MP3 文件的内嵌 ID3 标签的解析器。

1.1 为什么是Lisp?

很难用绪言这一章的寥寥数页来说清楚为何一门语言的用户会喜欢这门语言,更难的是找出一个理由说服你花时间来学习某种语言。现身说法只能到此为止了。也许我喜欢 Lisp 是因为它刚好符合我的思维方式,甚至可能是遗传因素,因为我父亲也喜欢它。因此在你开始学习 Lisp 之前就想知道会得到什么回报也是合理的。

对某些语言来说,回报是相对明显的。举个例子,如果打算编写 Unix 下的底层代码,那你应该去学 C。或者如果打算编写特定的跨平台应用程序,那你应该去学 Java。相当数量的公司仍在大量使用 C++,所以如果你打算在这些公司里找到工作,那就应该去学 C++。

尽管如此,对于多数语言来说,回报往往不是那么容易界定的,这里面存在包括个人感情在内的主观因素。Perl 拥趸们经常说 Perl “让简单的事情简单,让复杂的事情可行”,并且沉迷于 “做事情永远都有不止一种方法” 这一 Perl 格言所推崇的事实。另一方面,Python 爱好者们认为 Python 是简洁的,并且 Python 代码之所以易于理解,是因为正如他们的格言所说:“做每件事只有一种方法。”

那么 Common Lisp 呢?没有迹象表明采用 Common Lisp 可以立即得到诸如 C、Java 和 C++ 那样显而易见的好处(当然了,除非刚好拥有一台 Lisp 机)。使用 Lisp 所获得的好处在很大程度上取决于使用经验。本书的其余部分将向读者展示 Common Lisp 的特性以及如何使用它们,让你自己去感受它。眼下,我只想让你先对 Lisp 哲学有个大致的感受。

Common Lisp 中最接近格言的是一句类似禅语的描述:“可编程的编程语言。” 虽然隐晦了一些,但这句话却道出了 Common Lisp 至今仍然雄踞其他语言之上的最大优势。Common Lisp 比其他任何语言都更加遵循一种哲学——凡利于语言设计者的也利于语言使用者。这就意味着,当使用 Common Lisp 编程的时候,你永远不会遇到这种情况:语言里刚好缺乏某些可能令程序更容易编写的特性,因为正如你将在本书中看到的你可以为语言添加任何想要的特性

因此,Common Lisp 程序倾向于把关于程序如何工作的想法更清楚地映射到实际所写的代码上。想法永远不会被过于紧凑的代码和不断重复的模式搞得含糊不清。这将使代码更加易于维护,因为不必在每次修改之前都先复查大量的相关代码。甚至对程序行为的系统化调整也经常可以通过对实际代码作相对少量的修改来实现。这也意味着可以更快速地开发,编写更少的代码,也不必再花时间在语言的限制里寻求更简洁的方式来表达想法了。

Common Lisp 也是一门适合做探索性编程的优秀语言。如果在刚开始编写程序的 时候对整个工作机制还不甚明了,Common Lisp 提供了一些特性可以有助于实现递进的交互式开发。

对于初学者来说,将在下一章介绍的交互式 “读—求值—打印” 循环可以让你在开发过程中持续地与程序交互。编写新函数、测试它、修改它、尝试不同的实现方法。从而使思路不会因漫长的编译周期而停滞下来。

其他支持连贯的交互式编程风格的语言特性,还包括 Lisp 的动态类型机制以及 Common Lisp 状态系统(condition system)。由于前者的存在,只需花较少的时间就能让编译器把代码跑起来,然后把更多的时间放在如何实际运行和改进代码上,而后者甚至可以实现交互式地开发错误处理代码。

作为一门 “可编程的编程语言”,Common Lisp 除了支持各种小修小补以便开发人员更容易地编写某些程序之外,对于那些从根本上改变编程方式的新思想的支持也是绰绰有余的。例如,Common Lisp 强大的对象系统 CLOS(Common Lisp Object System),其最初的实现就是一个用可移植的 Common Lisp 写成的库。而在这个库正式成为语言的一部分之前,Lisp 程序员就可以体验其实际功能了。

目前来看,无论下一个流行的编程范例是什么,Common Lisp 都极有可能在无需修改其语言核心部分的情况下将其吸纳进来。例如,最近有个 Lisp 程序员写了一个叫做 AspectL 的库,它为 Common Lisp 增加了对面向方面编程(AOP)的支持。如果 AOP 将主宰编程的未来,那么 Common Lisp 将可以直接支持它而无需对基础语言作任何修改,并且也不需要额外的预处理器和编译器。

1.2 Lisp的诞生

Common Lisp 是 1956 年 John McCarthy 发明的 Lisp 语言的现代版本。Lisp 在 1956 年被设计用于 “符号数据处理”,而 Lisp 这个名字本身就来源于其最擅长的工作:列表处理(LISt Processing)。从那时起,Lisp 得到了长足的发展:Common Lisp 引人瞩目地具备一系列现代数据类型;将在第 19 章里介绍的状态系统提供了 Java、Python 和 C++ 等语言的异常系统里所没有的充分灵活性;强大的面向对象编程支持,以及其他编程语言里完全不存在的一些语言机制。这一切怎么可能呢?怎么会进化出如此装备精良的语言来呢?

原来,McCarthy 曾经是(现在也是)一名人工智能(AI)研究者,他在 Lisp 语言的最初版本里内置的很多特性使其成为了 AI 编程的绝佳语言。在 AI 繁荣昌盛的 20 世纪 80 年代,Lisp 始终是程序员们所偏爱的工具,广泛用于编写软件来求解包括自动定理证明、规划和调度以及计算机视觉在内的各种困难问题。这些问题都需 要大量难于编写的软件,为此,AI 程序员们需要一门强大的语言,而他们就将 Lisp 发展成了这样一门语言。另外,冷战也起了积极的作用——五角大楼向国防部高级研究规划局(DARPA)投入了大量资金,其中的相当一部分用于研究诸如大规模战场模拟、自动规划以及自然语言接口等问题。这些研究人员也在使用 Lisp 并且持续地对其进行改进以满足自身需要。

推动 Lisp 特性进化的动力也同样推动了其他相关领域的发展——大型的 AI 问题无论如何编码总是要耗费大量的计算资源,而如果按照摩尔定律倒推 20 年,你就可以想象 80 年代的计算资源是何等的贫乏了。Lisp 工作者们不得不想尽办法从实现中获得更多的性能。现代 Common Lisp 实现就是这些早期工作的结晶,它们通常都带有相当专业的可产生原生机器码的编译器。感谢摩尔定律,今天我们从任何纯解释型语言里也能获得可接受的性能了,性能对于 Common Lisp 来说再也不是问题了。不过,你在第 32 章可以看到,通过使用适当的(可选)变量声明,一个好的 Lisp 编译器所生成的机器码,完全可以跟 C 编译器所生成的机器码相媲美。

20 世纪 80 年代也是 Lisp 机的年代,当时好几家公司(其中最著名的是 Symbolics)都在生产可以在芯片上直接运行 Lisp 的计算机系统。Lisp 因此成了系统级编程语言,被广泛用于编写操作系统、编辑器、编译器,以及 Lisp 机上的大量其他软件。

事实上,到了 20 世纪 80 年代早期,几家 AI 实验室和 Lisp 机厂商都提供了他们自己的 Lisp 实现,众多的 Lisp 系统和方言让 DARPA 开始担心 Lisp 社区可能走向分裂。为了应对这些担忧,一个由 Lisp 黑客组成的草根组织于 1981 年成立,旨在结合既有 Lisp 方言之所长,定义一种新的称为 Common Lisp 的标准化 Lisp 语言。最后,他们的工作成果记录在了 Guy Steele 的 Common Lisp: the Language(CLtL,Digital Press,1984 年)一书里。这本书相当于 Lisp 的圣经。

到 1986 年的时候,首批 Common Lisp 实现诞生了,它们是在 Common Lisp 试图取代的那些方言的基础上写成的。1996 年,美国国家标准化组织(ANSI)发布了一个建立在 CLtL 之上并加以扩展的 Common Lisp 标准,其中增加了一些主要的新特性,包括 CLOS 和状态系统。但事情还没结束:跟此前的 CLtL 一样,ANSI 标准有意为语言实现者保留了一定空间以试验各种最佳的工作方式。一个完整的 Lisp 实现将带有丰富的运行时环境,并提供 GUI 接口、多线程控制和 TCP/IP 协议支持等。今天的 Common Lisp 则进化得更像其他的开源语言——用户可以编写他们所需要的库并开放给其他人使用。在过去的几年里,开源 Lisp 库领域尤为活跃。

所以,一方面,Lisp 是计算机科学领域的“经典语言”之一,构建在经过时间考验的各种思想之上。 另一方面,它完全是一门现代的通用语言,其设计反映了尽可能高效和可靠地求解实际问题的实用主义观点。Lisp “经典”遗产的唯一缺点是许多人仍然生活在片面的 Lisp 背景之下——他们可能只是在 McCarthy 发明 Lisp 以来的近半个世纪中的某些特定时刻接触到了这门语言的某些方面。如果有人告诉你 Lisp 只能被解释执行,因此会很慢,或者你不得不用递归来干每件事,那么一定要问问他们究竟在谈论哪种 Lisp 方言,以及他们是否是在计算机远古时代学到这些东西的。

但我曾经学过 Lisp,可是跟你所描述的不太一样

如果你以前用过 Lisp,你对 Lisp 的认识很可能对学习 Common Lisp 没什么帮助。尽管 Common Lisp 取代了大多数它所继承下来的方言,但它并非仅存的 Lisp 方言。你要清楚自己是在何时何地认识 Lisp 的,很有可能你学的是某种其他方言。

除了 Common Lisp 以外,另一种仍然有着活跃用户群的通用 Lisp 方言是 Scheme。 Common Lisp 从 Scheme 那里吸收了一些重要的特性,但从未试图取代它。

Scheme 最早在 MIT 被设计出来,然后很快用作本科计算机科学课程的教学语言,它一直被定位成一种与 Common Lisp 有所不同的语言。特别是 Scheme 的设计者们将注意力集中在使其语言核心尽可能地小而简单。这对作为教学语言来说非常有用,编程语言研究者们也很容易形式化地证明语言本身的有关命题。

这样设计的另一个好处是使得通过标准规范理解整个语言变得相对简单。但是,这样做带来的问题是缺失了许多在 Common Lisp 里已经标准化了的有用特性。个别的 Scheme 实现者可能以特定的实现方式提供了这些特性,但它们在标准中的缺失则使得编写可移植的 Scheme 代码比编写可移植的 Common Lisp 代码更加困难。

Scheme 同样强调函数式的编程风格,并且使用了比 Common Lisp 更多的递归。如果在大学里学过 Lisp 并且感觉它只是一种没有现实应用的学术语言,那你八成是学了 Scheme。当然,说 Scheme 具有这样的特征并不是很公正,只不过同样的说法用在 Common Lisp 身上更加不合适罢了,后者专门被设计成真实世界的工程语言,而不是一种理论上的“纯”语言。

如果你已经学过了 Scheme,就应该注意 Scheme 和 Common Lisp 之间的许多细微差别可能会使人犯错。这些差别也是 Common Lisp 和 Scheme 社区的狂热分子之间一些长期信仰之争的导火索。日后随着讨论的深入,我将指出其中最重要的差别。

另外两种仍然广泛使用的 Lisp 方言是 Elisp 和 Autolisp。Elisp 是 Emacs 编辑器的扩展语言,而 Autolisp 是 Autodesk 的 AutoCAD 计算机辅助设计工具的扩展语言。尽管用 Elisp 和 Autolisp 可能已经写出了超过任何其他方言的代码行数,但它们都不能用在各自的宿主应用程序之外,而且它们无论与 Scheme 还是 Common Lisp 相比都是相当过时的 Lisp 方言了。如果你曾经用过这些方言的一种,就需要做好准备,你可能要在 Lisp 时间机器里向前跳跃几十年了。

1.3 本书面向的读者

如果你对 Common Lisp 感兴趣,那么无论是否已经确定要使用它或者只是想一窥其门径,本书都挺适合你的。

如果你已经学会了一些 Lisp,但却难于跨越从学术训练到真实程序之间的鸿沟,那么本书刚好可以帮你走上正途。而另一方面,你也不必带着学以致用的目的来阅读本书。

如果你是个顽强的实用主义者,想知道 Common Lisp 相比 Perl、Python、Java、C 和 C# 这些语言都有哪些优势,那么本书应该可以提供一些思路。或者你根本就没打算使用 Lisp——可能是因为已经确信 Lisp 并不比已知的其他语言更好,但却由于不熟悉它而无法反驳那些 Lisp 程序员。如果是这样,本书将给你一个直奔 Common Lisp 主题的介绍。如果在读完本书以后,你仍然认为 Common Lisp 比不上自己当前喜爱的其他语言,那么你也有充分理由来说明自己观点了。

我不但介绍了该语言的语法和语义,还讲述了如何使用它来编写有用的软件。在本书的第一部分,我将谈及语言本身,同时穿插一些“实践性”章节,展示如何编写真实的代码。接着,在我阐述了该语言的绝大部分见容后——包括某些在其他书里往往留给你自己去考查的内容,将给出九个更实用的章节,帮助你编写一些中等大小的程序来做一些你可能认为有用的事:过滤垃圾邮件、解析二进制文件、分类 MP3、通过网络播放 MP3 流媒体,以及为MP3目录和服务器提供 Web 接口。

读完本书后,你将熟悉该语言的所有最重要的特性以及它们是如何组织在一起的,而是你已经用 Common Lisp 写出了一些非凡的程序,并且可以凭借自身力量继续探索该语言的了。尽管每个人的 Lisp 之路都有所不同,但我还是希望本书可以帮助你少走些弯路。那么,让我们就此上路吧。

1 Perl也值得学习“互联网的胶带”。

2遗憾的是,对不同语言的生产力几乎没有实际研究。一篇报告显示Lisp在程序员和程序效率的结合方面与C ++和Java相比很好地进行了讨论 http://www.norvig.com/java-lisp.html。

3心理学家已经确定了一种称为流动的心态,在这种状态下,我们能够拥有令人难以置信的专注力和生产力。流程编程的重要性已经被认识近二十年,因为在经典着作中讨论了人类软件编程中的人为因素:生产性项目和团队由Tom DeMarco和Timothy 列表er撰写(判断循环rset House,1987)。关于流动的两个关键事实是,它需要大约15分钟才能进入流动状态,即使是短暂的中断也可能会让你失控,需要再过15分钟才能重新进入。像大多数后来的作者一样,DeMarco和列表er主要关注的是流量摧毁中断,例如电话铃声和老板不合时宜的访问。对程序员来说,不太经常考虑但可能同样重要的是由我们的工具引起的中断。例如,在您尝试使用最新代码之前需要进行冗长编译的语言可能与嘈杂的电话或爱管闲事的老板一样不合时宜。因此,查看Lisp的一种方法是将语言设计为使您处于流动状态。

4至少对一些人来说,这一点必然会引起争议。静态与动态类型是编程中经典的宗教战争之一。如果您来自C ++和Java(或来自静态类型的函数语言,如Haskel和ML)并且拒绝考虑没有静态类型检查的生活,那么您现在也可以放下这本书。但是,在你这样做之前,你可能首先想看看自我描述的“静态类型偏移”Robert Martin(使用Booch方法设计面向对象的C ++应用程序的作者[Prentice Hall,1995])和C ++和Java作者Bruce Eckel (Thinking in C ++ [Prentice Hall,1995]和Thinking in Java[Prentice Hall,1998])不得不说他们的博客上的动态类型(http://www.artima.com/weblogs/viewpost.jsp?th读=4639和 http://www.最小值dview.net/WebLog/log-0025)。另一方面,来自Smalltalk,Python,Perl或Ruby的人们应该对Common Lisp的这个方面感到宾至如归。

5 AspectL是一个有趣的项目,因为AspectJ是其基于Java的前身,由Common Lisp的对象和元对象系统的设计者之一Greg或 Kiczales编写。对于许多Lispers来说,AspectJ看起来像Kiczales试图将他的想法从Common Lisp反向移植到Java中。然而,AspectL的作者Pascal Costanza认为AOP中有一些有趣的想法在Common Lisp中很有用。当然,他能够将AspectL实现为库的原因是因为Kiczales设计的Common Lisp元对象协议具有令人难以置信的灵活性。为了实现AspectJ,Kiczales必须编写本质上是一个单独的编译器,它将一种新语言编译成Java源代码。AspectL项目页面位于http://common-lisp.net/ project/aspectl/。

6或者以另一种技术上更准确的方式来看,Common Lisp附带了一个用于集成嵌入式语言编译器的内置工具。

7 Lisp 1.5程序员手册 (麻省理工学院出版社,1962年)

8观念Lisp中首次引入包括的判断 / then / else结构,递归函数调用,动态分配内存,垃圾收集,一流的功能,词汇闭包,交互式编程,增量编译和动态类型。

9关于Lisp的最常见的重复神话之一是它“死了”。虽然Common Lisp并没有像Visual Basic或Java那样被广泛使用,但是描述一种继续用于新开发并继续吸引新用户“死”的语言似乎很奇怪。最近Lisp的一些成功案例包括Paul Graham的Viaweb,当雅虎收购他的公司时,它成为雅虎商店; ITA Software的机票价格和购物系统QPX,由在线售票机构或bitz和其他人使用; Naughty dog为PlayStation 2,Jak和Daxter开发的游戏,主要是用特定领域的Lisp方言Naughty dog编写的,名为GOAL,其编译器本身是用Common Lisp编写的。和Roomba,自动机器人真空吸尘器,其软件用L编写,Common Lisp的向下兼容子集。也许更有说服力的是Common-Lisp.net网站的增长,它承载了开源Common Lisp项目,以及过去几年中涌现的本地Lisp用户组的数量。


Leave a Reply

搜索

分类目录

公 告

本网站学习论坛:

www.zhlisp.com

lisp中文学习源码:

https://github.com/zhlisp/

欢迎大家来到本站,请积极评论发言;

加QQ群学习交流。