LISP的历史
LISP(全名LIST Processor,即链表处理语言),由约翰·麦卡锡在1960年左右创造的一种基于λ演算的函数式编程语言。
Lisp 代表 LISt Processing,即表处理,这种编程语言用来处理由括号(即“(”和“)”)构成的列表。约翰麦卡锡于1960年发表了一篇非凡的论文,他在这篇论文中对编程的贡献有如欧几里德对几何的贡献.[1] 他向我们展示了,在只给定几个简单的操作符和一个表示函数的记号的基础上, 如何构造出一个完整的编程语言. 麦卡锡称这种语言为Lisp, 意为List Processing, 因为他的主要思想之一是用一种简单的数据结构表(list)来代表代码和数据. 值得注意的是,麦卡锡所作的发现,不仅是计算机史上划时代的大事, 而且是一种在我们这个时代编程越来越趋向的模式。可以说到目前为止只有两种真正干净利落, 始终如一的编程模式:C语言模式和Lisp语言模式。此二者就象两座高地, 在它们中间是尤如沼泽的低地.随着计算机变得越来越强大,新开发的语言一直在坚定地趋向于Lisp模式。二十年来,开发新编程语言的一个流行的秘诀是,取 C语言的计算模式,逐渐地往上加Lisp模式的特性,例如运行时类型和无用单元收集。
LISP有很多种方言,各个实现中的语言不完全一样。1980年代Guy L. Steele编写了Common Lisp试图进行标准化,这个标准被大多数解释器和编译器所接受。在Unix/Linux系统中,还有一种和Emacs一起的Emacs Lisp(而Emacs正是用Lisp编写的)非常流行,并建立了自己的标准。
LISP的祖先是1950年代Carnegie-Mellon大学的Newell、Shaw、Simon开发的IPL语言。
LISP语言的主要现代版本包括Common Lisp和Scheme。
LISP有9大创新
直到现在,越流行语言吸收的LISP元素越多,如python,ruby。LISP直到现在仍然被众多牛人推崇。当年LISP有9大创新,50年后,深刻影响了编程语言的进程。可见一个完善的理论被适当地应用,可以变得多么强大,焕发多么夺目的生机:
- 条件语句。当初的语言是没有if else的,goto统治世界。
- 函数类型。函数成了语言里的类型,可以被变量指代,可以被当成参数传来传去(的一类公民的必要条件,参考SICP第一章)。这一条可以极大简化编程,让我们写出非常漂亮的程序。所以现在的流行语言纷纷加入了这个特性(可惜Java没有)。
- 递归。这个不用说了吧。
- 动态类型。smalltalk, python, ruby。。。连C#也有一个类似的var了。
- 垃圾收集。不要以为GC是Smalltalk的发明哈,更不是Java的。
- 基于表达式的编程。任何表达式都可以成为另一个表达式的一部分。不像很多语言,把表达和陈述分开。
- 符号类型。这个在python和ruby里被采用,广受欢迎。
- 代码即解析树。这个让LISP能方便地定义新的句法,操作程序本身,编写元程序,生成真正意义上的宏。
- 语言无时不在。代码运行/解析可以在任何时候发生。这点和8.配合可以让语言的扩展和交流变得非常容易。
基本介绍
Lisp的表达式是一个原子(atom)或表(list),原子(atom)是一个字母序列,如abc;表是由零个或多个表达式组成的序列,表达式之间用空格分隔开,放入一对括号中,如:
abc () (abc xyz) (a b (c) d)
最后一个表是由四个元素构成的,其中第三个元素本身也是一个表。
正如算数表达式1+1有值2一样,Lisp中的表达式也有值,如果表达式e得出值v,我们说e返回v。如果一个表达式是一个表,那么我们把表中的第一个元素叫做操作符,其余的元素叫做自变量。
下面是一个在标准输出设备上输出Hello World的简单Common Lisp程序,这种程序通常作为开始学习编程语言时的第一个程序:(format t “Hello, world!~%”)
Lisp的7个公理(基本操作符): quote,atom,eq,car,cdr,cons,和 cond.
- (quote x)返回x,我们简记为’x
- (atom x)当x是一个原子或者空表时返回原子t,否则返回空表()。在Lisp中我们习惯用原子t表示真,而用空表()表示假。
> (atom ‘a) t > (atom ‘(a b c)) () > (atom ‘()) t
现在我们有了第一个需要求出自变量值的操作符,让我们来看看quote操作符的作用——通过引用(quote)一个表,我们避免它被求值。一个未被引用的表达式作为自变量,atom将其视为代码,例如:
> (atom (atom ‘a)) t
反之一个被引用的表仅仅被视为表
> (atom ‘(atom ‘a)) ()
引用看上去有些奇怪,因为你很难在其它语言中找到类似的概念,但正是这一特征构成了Lisp最为与众不同的特点——代码和数据使用相同的结构来表示,而我们用quote来区分它们。
- (eq x y)当x和y的值相同或者同为空表时返回t,否则返回空表()
> (eq ‘a ‘a) t > (eq ‘a ‘b) () > (eq ‘() ‘()) t
- (car x)要求x是一个表,它返回x中的第一个元素,例如:
> (car ‘(a b)) a
- (cdr x)同样要求x是一个表,它返回x中除第一个元素之外的所有元素组成的表,例如:
> (cdr ‘(a b c)) (b c)
- (cons x y)要求y是一个表,它返回一个表,这个表的第一个元素是x,其后是y中的所有元素,例如:
> (cons ‘a ‘(b c)) (a b c) > (cons ‘a (cons ‘b (cons ‘c ()))) (a b c)
- (cond (…) …(…)) 的求值规则如下. p表达式依次求值直到有一个返回t. 如果能找到这样的p表达式,相应的e表达式的值作为整个cond表达式的返回值.
> (cond ((eq ‘a ‘b) ‘first) ((atom ‘a) ‘second)) second
如果你还想了解更多更多LISP的语法,你可以查看博主原文,请点击[http://172.30.70.43/wordpress/?p=307 这里]。(编者注)
Peter Norvig:编程语言的选择并不重要
Lisp被许多资深程序员视为编程语言中的圣杯,因为学起来很难。著名程序员、最热门的技术问答网站StackOverflow创始人Joel Spolsky曾经在“Java语言学校的危险性”一文中说,自己当年 在大学里学习用Lisp的导论课程也是苦不堪言,而Lisp这样的函数式语言实际开发中并不常用,但是一旦到了体现优势的时候,你如果不懂,将失之千里。他还认为,Google的核心技术之一MapReduce就来自函数式语言,而且使Google领先微软多年。请注意,Spolsky曾经1990年代微软的Excel项目经理,VBA的主要创造者。
《Unix编程艺术》的作者Eric Raymond也在“如何成为黑客”(英文版)中说过,Lisp是对黑客特别重要的语言,“掌握了之后,你会得到丰富的启迪和经验。 即使实际上很少使用Lisp,这些经验也会使你在以后的日子里成为更好的程序员。”
而Paul Graham更是Lisp的头号吹鼓手,他曾与蠕虫病毒发明者Robert Morris一起,成功地用Lisp开发历史上第一个Web应用——ViaWeb,并高价卖给Yahoo致富。他在2001年写了一篇宣传Lisp的名文“Beating the Averages”(英文),其中说到:“Lisp之所以极为优秀,并不是因为只有铁杆粉丝才知道的某些魔术般的性质,而在于它确实是最强大的编程语言。大家不用它的原因,在于编程语言不只是技术,而且也是思维习惯,这是改变起来最慢的东西。”
有意思的是,美国时间10月18日,著名技术新闻网站Hacker News上,名为kung-fu-master的用户发了一篇名为“问PG: Lisp与Python (2010)”的帖子,内容很简单:“好像许多Lisp老枪都开始转而用Python的(比如Peter Norvig)。今天你怎么看Lisp与Python?”
这里的PG就是指Hacker News的创始人、著名Lisp程序员与吹鼓手Paul Graham。而其中提到的Peter Norvig则是另一个著名Lisp程序员,现任Google研发总监。他有一篇经典的文章“十年学会编程”(英文版),相信大家都已经看过,他的首选推荐语言当然也是Lisp(更准确的说是Lisp的方言Scheme)。
面对这样直接的问题,专家们是怎么回答的呢?
Paul Graham说:
这问题好像来自2005年而不是2010年。Lisp现在由于Clojure(Lisp的现代方言,可以运行在JVM和.NET上——CSDN编者注)的出现,已经又变得时髦了。当然Python也有很好的库,但是我觉得用缺乏宏的语言编程很受局限。
许多网友对Python没有宏的问题进行了讨论。有人说不支持宏是出于代码可读性的考虑。而更多的人则认同宏是一种抽象工具,任何工具都会被误用、滥用,但并不表示工具本身有问题。
另一Lisp专家,《Coder at Work》和《Practical Common Lisp》作者>Peter Seibel也加入了讨论(他最近忙于编辑一种杂志Code Quarterly)。他说:自己与Peter Norvig有类似的体验。虽然自己Lisp非常熟练,但是在表达算法方面Python是最佳选择。不过,相比Lisp实现,CPython的运行速度慢得惊人,甚至差5-10倍。
而Peter Norvig也罕见地现出真身,给出了自己的回答,内容一如既往地经典:
我是Peter Norvig。我转向Python不是因为它比Lisp更好、更令人满意、更实用,而是因为它用作伪代码更合适。许多学生说,在《人工智能》(Norvig与Russell合著的经典教材)一书中的伪代码与我们在网上提供的Lisp代码之间进行转换太难了。于是我要寻找一种更近似伪代码的语言,发现Python是最合适的。然后我就自学了Python,熟练到足以实现教材中示例的地步。我发现Python对于一些类型的小问题非常合适,有些库也是我与其他的一些代码(包括在Google内和网上其他地方的代码)集成非常需要的。 我认为Lisp对更大的项目和编译速度很重要的应用而言仍然具有优势。但是在主要目的是交流而非编程的时候(比如针对数量众多的学生),Python更有优势。
就更一般意义上的编程而言,在Google和其他地方,我认为语言的选择并不如其他方面的选择那么重要:如果你有了正确的总体架构、正确的程序员团队、正确的开发过程(能够快速开发、持续改善),那么很多语言都能胜任;但如果以上的东西你没有,那无论选择什么语言,你都会陷入一团糟。
还有人找出今年2月Norvig在一次采访(无法直接访问)中类似的话,透露了Google内部的一些信息:
(1) Google最开始似乎核心程序员都是用C++的,效率很高。这部分形成了公司的文化。
(2) Google早期的Lisp程序员(Erann Gat) 注意到其他程序员和自己的效率差不多,甚至更好。关键还是在人,与20年前相比,现在语言的选择不那么重要了。
(3) Lisp是为单个程序员或者一小组程序员进行探索性工作而专门设计的……如果我想自己在周末修改代码,会更愿意用Lisp,但是如果有几百个程序员一起改代码,那就不是编程语言的问题,而是社会问题了。
(4) 库。
其他人举出的材料中,最有意思的应该来自Lisp之父、计算机科学巨人约翰麦卡锡。这篇博客(无法直接访问)里生动讲述了这位图灵奖得主在某次听Peter Norvig改而鼓吹Python的演讲后的情景:
Norvig演讲后,进入提问环节。出乎我的意料,他点了一位皱巴巴的老头。老头的胡子和头发都花白了,而且乱糟糟的,看上去像是来此参观但是迷路了,到这里来休息一下,好奇地听听我们在说什么。我第一个念头是,估计他已经被这么艰深的话题弄晕了。但是马上想到,不对啊,这里离斯坦福很近,这老头的年纪也对,难道是……
只听Norvig说:“是,John,你有什么问题?”我记不清Lisp之父当时怎么问的了,但不超过十个词,就是问 Python是否能如Lisp那样优雅地像数据一样处理代码。“不,John,不行。”Norvig回答,然后静等麦卡锡继续发问。但是,老人什么也没有再说。此时真是无言胜千语啊……
看来,在大师眼中,数据与代码等同处理是最重要的语言特性之一。他还曾经在访谈中这样评价Ruby(提问者说Ruby从Lisp中借鉴了很多):“Ruby能像数据那样使用列表结构吗?那每次算加和减的时候,都得进行解析啰?这方面Ruby还不如1960年的Lisp。”
为什么LISP语言如此先进
此文节选自《黑客与画家》中译版
一、
如果我们把流行的编程语言,以这样的顺序排列:Java、Perl、Python、Ruby。你会发现,排在越后面的语言,越像Lisp。
Python模仿Lisp,甚至把许多Lisp黑客认为属于设计错误的功能,也一起模仿了。至于Ruby,如果回到1975年,你声称它是一种Lisp方言,没有人会反对。
编程语言现在的发展,不过刚刚赶上1958年Lisp语言的水平。
二、
1958年,John McCarthy设计了Lisp语言。我认为,当前最新潮的编程语言,只是实现了他在1958年的设想而已。
这怎么可能呢?计算机技术的发展,不是日新月异吗?1958年的技术,怎么可能超过今天的水平呢?
让我告诉你原因。
这是因为John McCarthy本来没打算把Lisp设计成编程语言,至少不是我们现在意义上的编程语言。他的原意只是想做一种理论演算,用更简洁的方式定义图灵机。
所以,为什么上个世纪50年代的编程语言,到现在还没有过时?简单说,因为这种语言本质上不是一种技术,而是数学。数学是不会过时的。你不应该把Lisp语言与50年代的硬件联系在一起,而是应该把它与快速排序(Quicksort)算法进行类比。这种算法是1960年提出的,至今仍然是最快的通用排序方法。
三、
Fortran语言也是上个世纪50年代出现的,并且一直使用至今。它代表了语言设计的一种完全不同的方向。Lisp是无意中从纯理论发展为编程语言,而Fortran从一开始就是作为编程语言设计出来的。但是,今天我们把Lisp看成高级语言,而把Fortran看成一种相当低层次的语言。
1956年,Fortran刚诞生的时候,叫做Fortran I,与今天的Fortran语言差别极大。Fortran I实际上是汇编语言加上数学,在某些方面,还不如今天的汇编语言强大。比如,它不支持子程序,只有分支跳转结构(branch)。
Lisp和Fortran代表了编程语言发展的两大方向。前者的基础是数学,后者的基础是硬件架构。从那时起,这两大方向一直在互相靠拢。Lisp刚设计出来的时候,就很强大,接下来的二十年,它提高了自己的运行速度。而那些所谓的主流语言,把更快的运行速度作为设计的出发点,然后再用超过四十年的时间,一步步变得更强大。
直到今天,最高级的主流语言,也只是刚刚接近Lisp的水平。虽然已经很接近了,但还是没有Lisp那样强大。
四、
Lisp语言诞生的时候,就包含了9种新思想。其中一些我们今天已经习以为常,另一些则刚刚在其他高级语言中出现,至今还有2种是Lisp独有的。按照被大众接受的程度,这9种思想依次是:
1. 条件结构(即”if-then-else”结构)。现在大家都觉得这是理所当然的,但是Fortran I就没有这个结构,它只有基于底层机器指令的goto结构。
2. 函数也是一种数据类型。在Lisp语言中,函数与整数或字符串一样,也属于数据类型的一种。它有自己的字面表示形式(literal representation),能够储存在变量中,也能当作参数传递。一种数据类型应该有的功能,它都有。
3. 递归。Lisp是第一种支持递归函数的高级语言。
4. 变量的动态类型。在Lisp语言中,所有变量实际上都是指针,所指向的值有类型之分,而变量本身没有。复制变量就相当于复制指针,而不是复制它们指向的数据。
5. 垃圾回收机制。
6. 程序由表达式(expression)组成。Lisp程序是一些表达式区块的集合,每个表达式都返回一个值。这与Fortran和大多数后来的语言都截然不同,它们的程序由表达式和语句(statement)组成。
区分表达式和语句,在Fortran I中是很自然的,因为它不支持语句嵌套。所以,如果你需要用数学式子计算一个值,那就只有用表达式返回这个值,没有其他语法结构可用,因为否则就无法处理这个值。
后来,新的编程语言支持区块结构(block),这种限制当然也就不存在了。但是为时已晚,表达式和语句的区分已经根深蒂固。它从Fortran扩散到Algol语言,接着又扩散到它们两者的后继语言。
7. 符号(symbol)类型。符号实际上是一种指针,指向储存在哈希表中的字符串。所以,比较两个符号是否相等,只要看它们的指针是否一样就行了,不用逐个字符地比较。
8. 代码使用符号和常量组成的树形表示法(notation)。
9. 无论什么时候,整个语言都是可用的。Lisp并不真正区分读取期、编译期和运行期。你可以在读取期编译或运行代码;也可以在编译期读取或运行代码;还可以在运行期读取或者编译代码。
在读取期运行代码,使得用户可以重新调整(reprogram)Lisp的语法;在编译期运行代码,则是Lisp宏的工作基础;在运行期编译代码,使得Lisp可以在Emacs这样的程序中,充当扩展语言(extension language);在运行期读取代码,使得程序之间可以用S-表达式(S-expression)通信,近来XML格式的出现使得这个概念被重新”发明”出来了。
五、
Lisp语言刚出现的时候,它的思想与其他编程语言大相径庭。后者的设计思想主要由50年代后期的硬件决定。随着时间流逝,流行的编程语言不断更新换代,语言设计思想逐渐向Lisp靠拢。
思想1到思想5已经被广泛接受,思想6开始在主流编程语言中出现,思想7在Python语言中有所实现,不过似乎没有专用的语法。
思想8可能是最有意思的一点。它与思想9只是由于偶然原因,才成为Lisp语言的一部分,因为它们不属于John McCarthy的原始构想,是由他的学生Steve Russell自行添加的。它们从此使得Lisp看上去很古怪,但也成为了这种语言最独一无二的特点。Lisp古怪的形式,倒不是因为它的语法很古怪,而是因为它根本没有语法,程序直接以解析树(parse tree)的形式表达出来。在其他语言中,这种形式只是经过解析在后台产生,但是Lisp直接采用它作为表达形式。它由列表构成,而列表则是Lisp的基本数据结构。
用一门语言自己的数据结构来表达该语言,这被证明是非常强大的功能。思想8和思想9,意味着你可以写出一种能够自己编程的程序。这可能听起来很怪异,但是对于Lisp语言却是再普通不过。最常用的做法就是使用宏。
术语”宏”在Lisp语言中,与其他语言中的意思不一样。Lisp宏无所不包,它既可能是某样表达式的缩略形式,也可能是一种新语言的编译器。如果你想真正地理解Lisp语言,或者想拓宽你的编程视野,那么你必须学习宏。
就我所知,宏(采用Lisp语言的定义)目前仍然是Lisp独有的。一个原因是为了使用宏,你大概不得不让你的语言看上去像Lisp一样古怪。另一个可能的原因是,如果你想为自己的语言添上这种终极武器,你从此就不能声称自己发明了新语言,只能说发明了一种Lisp的新方言。
我把这件事当作笑话说出来,但是事实就是如此。如果你创造了一种新语言,其中有car、cdr、cons、quote、cond、atom、eq这样的功能,还有一种把函数写成列表的表示方法,那么在它们的基础上,你完全可以推导出Lisp语言的所有其他部分。事实上,Lisp语言就是这样定义的,John McCarthy把语言设计成这个样子,就是为了让这种推导成为可能。
六、
就算Lisp确实代表了目前主流编程语言不断靠近的一个方向,这是否意味着你就应该用它编程呢?
如果使用一种不那么强大的语言,你又会有多少损失呢?有时不采用最尖端的技术,不也是一种明智的选择吗?这么多人使用主流编程语言,这本身不也说明那些语言有可取之处吗?
另一方面,选择哪一种编程语言,许多项目是无所谓的,反正不同的语言都能完成工作。一般来说,条件越苛刻的项目,强大的编程语言就越能发挥作用。但是,无数的项目根本没有苛刻条件的限制。大多数的编程任务,可能只要写一些很小的程序,然后用胶水语言把这些小程序连起来就行了。你可以用自己熟悉的编程语言,或者用对于特定项目来说有着最强大函数库的语言,来写这些小程序。如果你只是需要在Windows应用程序之间传递数据,使用Visual Basic照样能达到目的。
那么,Lisp的编程优势体现在哪里呢?
七、
语言的编程能力越强大,写出来的程序就越短(当然不是指字符数量,而是指独立的语法单位)。
代码的数量很重要,因为开发一个程序耗费的时间,主要取决于程序的长度。如果同一个软件,一种语言写出来的代码比另一种语言长三倍,这意味着你开发它耗费的时间也会多三倍。而且即使你多雇佣人手,也无助于减少开发时间,因为当团队规模超过某个门槛时,再增加人手只会带来净损失。Fred Brooks在他的名著《人月神话》(The Mythical Man-Month)中,描述了这种现象,我的所见所闻印证了他的说法。
如果使用Lisp语言,能让程序变得多短?以Lisp和C的比较为例,我听到的大多数说法是C代码的长度是Lisp的7倍到10倍。但是最近,New Architect杂志上有一篇介绍ITA软件公司的文章,里面说”一行Lisp代码相当于20行C代码”,因为此文都是引用ITA总裁的话,所以我想这个数字来自ITA的编程实践。 如果真是这样,那么我们可以相信这句话。ITA的软件,不仅使用Lisp语言,还同时大量使用C和C++,所以这是他们的经验谈。
根据上面的这个数字,如果你与ITA竞争,而且你使用C语言开发软件,那么ITA的开发速度将比你快20倍。如果你需要一年时间实现某个功能,它只需要不到三星期。反过来说,如果某个新功能,它开发了三个月,那么你需要五年才能做出来。
你知道吗?上面的对比,还只是考虑到最好的情况。当我们只比较代码数量的时候,言下之意就是假设使用功能较弱的语言,也能开发出同样的软件。但是事实上,程序员使用某种语言能做到的事情,是有极限的。如果你想用一种低层次的语言,解决一个很难的问题,那么你将会面临各种情况极其复杂、乃至想不清楚的窘境。
所以,当我说假定你与ITA竞争,你用五年时间做出的东西,ITA在Lisp语言的帮助下只用三个月就完成了,我指的五年还是一切顺利、没有犯错误、也没有遇到太大麻烦的五年。事实上,按照大多数公司的实际情况,计划中五年完成的项目,很可能永远都不会完成。
我承认,上面的例子太极端。ITA似乎有一批非常聪明的黑客,而C语言又是一种很低层次的语言。但是,在一个高度竞争的市场中,即使开发速度只相差两三倍,也足以使得你永远处在落后的位置。