lisp是解释型语言吗,

  lisp是解释型语言吗,

  Lisp语言为什么这么高级?——阮一峰的博客

  Lisp语言为什么这么高级?(翻译)

  上周《黑客与画家》终于翻译好了,交给出版社。

  翻译完这本书,我累得好像生了大病。交稿的时候心里空荡荡的,不知道得了什么,也不知道失了什么。

  希望这个中译本和我的努力能得到读者的认可和肯定。

  以下是这本书里的一篇很棒的文章。原文写于八年前,至今仍有启发意义。作者的远见令人钦佩。由于我不懂Lisp语言,田春帮我校对并纠正了一些翻译错误。在此表示衷心的感谢。

  ============================

  Lisp语言为什么这么高级?

  作者:保罗格拉厄姆

  译者:阮一峰

  原版英语:书呆子的复仇

  (摘自即将出版的《黑客与画家》中文译本)

  一,

  如果我们把流行的编程语言按这个顺序排列:Java,Perl,Python,Ruby。你会发现底层的语言更像Lisp。

  模仿Python Lisp,甚至模仿很多Lisp黑客认为是设计错误的函数。至于Ruby,如果你回到1975年,声称它是Lisp方言,没有人会反对。

  现在的编程语言发展刚刚赶上1958年Lisp语言的水平。

  第二,

  1958年,约翰麦卡锡设计了Lisp语言。在我看来,最新的编程语言在1958年刚刚实现了他的想法。

  这怎么可能呢?计算机技术的发展难道不是日新月异吗?1958年的技术怎么可能超过今天的水平?

  让我告诉你为什么。

  这是因为约翰麦卡锡并没有打算将Lisp设计成一种编程语言,至少在我们目前的意义上没有。他的初衷只是做一个理论上的计算,用更简洁的方式定义图灵机。

  那么,为什么50年代的编程语言仍然没有过时呢?简单来说,因为这种语言本质上不是技术,而是数学。数学没有过时。你不应该把Lisp和50年代的硬件联系在一起,而应该把它和快速排序算法相比较。这个算法是1960年提出的,至今仍是最快的通用排序方法。

  第三,

  Fortran语言也在50年代出现,一直沿用至今。它代表了完全不同的语言设计方向。Lisp是无意中从纯理论发展成编程语言的,而Fortran从一开始就是作为编程语言来设计的。然而今天,我们把Lisp当作高级语言,把Fortran当作相当低级的语言。

  1956年,Fortran诞生的时候叫做Fortran I,和今天的Fortran语言有很大的不同。Fortran实际上是一种汇编语言加数学。在某些方面,它不如今天的汇编语言强大。例如,它不支持子程序,只支持分支结构。

  Lisp和Fortran代表了编程语言发展的两个方向。前者基于数学,后者基于硬件架构。从那以后,这两个方向越来越接近。Lisp刚设计出来的时候,功能非常强大。在接下来的二十年里,它提高了运行速度。而那些所谓的主流语言都是以更快的运行速度作为设计的出发点,然后用40多年的时间一步步变强。

  直到今天,最先进的主流语言也只是刚刚接近Lisp的水平。虽然接近了,但还是没有Lisp强大。

  四,

  Lisp语言诞生的时候,包含了九个新思想。有些是我们今天习以为常的,有些是其他高级语言刚刚出现的。到目前为止,还有两个是Lisp独有的。根据被大众接受的程度,这九种思想分别是:

  1.条件结构(即“if-then-else”结构)。现在大家都觉得这是理所当然的,但是Fortran I没有这个结构。它只有一个基于底层机器指令的goto结构。

  2.函数也是一种数据类型。在Lisp语言中,函数像整数或字符串一样,属于数据类型之一。它有自己的文字表示,可以存储在变量中或作为参数传递。它具有一个数据类型应该具有的所有功能。

  3.递归。Lisp是第一个支持递归函数的高级语言。

  4.变量的动态类型。在Lisp语言中,所有的变量实际上都是指针,指向的值都被归为类型,而变量本身却不是。复制变量相当于复制指针,而不是复制指针所指向的数据。

  5.垃圾收集机制。

  6.这个程序由一个表达式组成。Lisp程序是表达式块的集合,每个表达式返回一个值。这与Fortran和大多数后来的语言大不相同,它们的程序由表达式和语句组成。

  在Fortran I中,区分表达式和语句是很自然的,因为它不支持语句嵌套。所以,如果你需要用数学公式计算一个值,你必须用一个表达式返回这个值。没有其他的语法结构可用,因为否则你不能处理这个值。

  后来新的编程语言支持块结构,这个限制当然不存在了。但是已经晚了,表情和语句的区分根深蒂固。它从Fortran传播到Algol,然后传播到它们的后继语言。

  7.符号类型。符号实际上是指向存储在哈希表中的字符串的指针。所以,比较两个符号是否相等,只要看它们的指针是否相同,而不是一个字符一个字符地比较。

  8.代码使用由符号和常量组成的树表示法。

  9.整个语言随时可用。Lisp并没有真正区分读取期、编译期和运行期。您可以在阅读期间编译或运行代码;您还可以在编译时读取或运行代码;您还可以在运行时读取或编译代码。

  在阅读期间运行代码,以便用户对LISP的语法进行重新编程;编译时运行代码是Lisp宏的基础。运行时编译代码,让Lisp充当扩展语言);在像Emacs这样的程序中;运行时读取代码使程序能够通过S表达式相互通信。最近,XML格式的出现重新发明了这个概念。

  五,

  Lisp语言刚出现的时候,它的思想和其他编程语言有很大的不同。后者的设计思路主要由50年代后期的硬件决定。随着时间的推移,流行的编程语言不断更新,语言设计思路逐渐向Lisp靠拢。

  Ideas 1到5已经被广泛接受,ideas 6已经开始出现在主流编程语言中。Ideas 7已经用Python实现了,但是好像没有什么特殊的语法。

  8思想大概是最有趣的一点。它和Idea 9只是偶然成为Lisp语言的一部分,因为它们不属于约翰麦卡锡最初的想法,而是由他的学生Steve Russell自己添加的。从那以后,它们使Lisp看起来很奇怪,但它们也成为了这种语言最独特的特征。Lisp的奇形式不是因为它的语法奇,而是因为它根本没有语法,程序直接用解析树的形式表达。在其他语言中,这种形式只在解析后在后台生成,而Lisp直接采用它作为表达形式。它由一个列表组成,列表是Lisp的基本数据结构。

  事实证明,用自己的数据结构来表达一种语言是一个非常强大的功能。思路8和思路9的意思是你可以写一个可以自己编程的程序。这听起来可能很奇怪,但这在Lisp语言中很常见。最常见的方法是使用宏。

  术语“宏”在Lisp语言中与在其他语言中有不同的含义。Lisp宏包罗万象。它可能是表达式的缩写形式,也可能是新语言的编译器。如果你想真正理解Lisp语言或者拓宽你的编程视野,那么你必须学习宏。

  据我所知,宏(Lisp语言定义的)目前还是Lisp独有的。一个原因是,为了使用宏,你可能必须让你的语言看起来像Lisp一样怪异。另一个可能的原因是,如果你想在自己的语言中加入这个终极武器,你就不能声称从此发明了一种新的语言。你只能说你发明了一种新的Lisp方言。

  我是开玩笑说的,但事实如此。如果你创建了一种新的语言,有car、cdr、cons、quote、cond、atom、eq等函数,以及一种把函数写成列表的表示方法,那么在它们的基础上,你就可以完全推导出Lisp语言的所有其他部分。事实上,Lisp语言是这样定义的,约翰麦卡锡这样设计语言就是为了让这种推导成为可能。

  六,

  即使Lisp确实代表了主流编程语言正在接近的一个方向,难道这就意味着你应该用它编程吗?

  如果用不那么厉害的语言会损失多少?有时候不采用最前沿的技术难道不是明智的选择吗?这么多人使用主流编程语言,这本身不就说明那些语言有可取之处吗?

  另一方面,对于许多项目来说,选择哪种编程语言并不重要。反正不同的语言都能完成工作。一般来说,项目要求越高,编程语言就越强大。然而,无数的项目根本不受苛刻条件的限制。大部分的编程任务,大概就是写一些小程序,然后用胶水语言把这些小程序连接起来。你可以用你熟悉的编程语言写这些小程序,也可以用某个具体项目的函数库最强大的语言写。如果只是需要在Windows应用程序之间传输数据,使用Visual Basic仍然可以达到目的。

  那么,Lisp的编程优势在哪里呢?

  七,

  语言的编程能力越强大,写出来的程序就越短(当然不是字符数,而是独立的语法单位)。

  代码的数量很重要,因为开发一个程序所需的时间主要取决于程序的长度。如果同一个软件,用一种语言写的代码比用另一种语言写的代码长三倍,就意味着你要花三倍的时间去开发它。而且即使你雇佣了更多的人,也无助于减少开发时间,因为当团队规模超过一定门槛时,增加更多的人只会带来净损失。弗雷德布鲁克斯在他的名著《人月神话》(神话中的人月)中描述了这种现象,我的所见所闻也证实了他的说法。

  如果用Lisp语言,你能把程序做多短?以Lisp和C的对比为例。我听到的大部分说法都是C代码的长度是Lisp的7到10倍。但是最近《新架构师》杂志上有一篇关于ITA软件公司的文章,说‘一行Lisp代码相当于20行C代码’。因为这篇文章引用了ITA的总裁,所以我认为这个数字来自于ITA的编程实践。如果是这样的话,那么我们可以相信这句话。ITA的软件不仅使用Lisp语言,还大量使用C和C,所以这是他们的经验。

  根据上图,如果你和ITA竞争,你用C语言开发软件,那么ITA的开发速度会比你快20倍。如果你需要一年的时间来实现一个功能,只需要不到三周。反过来说,一个新功能三个月开发出来,你要五年才能做出来。

  你知道吗?你知道吗?以上比较只是考虑最好的情况。当我们只比较代码数量的时候,言外之意就是同样的软件可以用较弱的语言开发。但实际上,程序员用某种语言能做的事情是有限度的。如果你想用一种低级语言来解决一个难题,那么你将面临各种极其复杂的情况,甚至是令人困惑的困境。

  所以,当我说假设你和ITA竞争,你五年做出来的东西,ITA借助Lisp语言只用了三个月就完成了,我指的是五年,或者说一切顺利,没有出错,没有太大麻烦的五年。事实上,按照大多数公司的实际情况,计划五年完成的项目,很可能永远也完成不了。

  我承认上面的例子太极端了。ITA好像有一群很聪明的黑客,C语言是很低级的语言。但在竞争激烈的市场中,哪怕发展速度只有两三倍的差距,也足以让你永远处于落后的位置。

  附录:编程能力

  为了解释我所讲的语言的不同编程能力,请考虑以下问题。我们需要写一个可以生成累加器的函数,就是这个函数接受一个参数n,然后返回另一个函数,这个函数接受一个参数I,然后返回n增量为I的值。

  Common Lisp编写如下:

  (defun foo (n)

  ((I)(增量n i)))

  Ruby的编写方式几乎完全相同:

  def foo (n)

  { I n=I } end

  Perl 5编写如下:

  子食物{

  我的($ n)=@ _;

  sub {$n=shift}

  }

  这比Lisp和Ruby版本有更多的语法元素,因为在Perl中,您必须手动提取参数。

  Smalltalk写的比Lisp和Ruby长一点:

  福:恩

  s

  s :=n。

  ^[:i s:=s . I .]

  因为在Smalltalk中,词法变量是有效的,但是不能给参数赋值,所以要设置一个新的变量来接受累加值。

  Javascript写的也比Lisp和Ruby长一点。因为Javascript仍然区分语句和表达式,所以需要显式指定return语句来返回值:

  函数foo (n) {

  返回函数(i) {

  return n=i } }

  (实际上,Perl也保留了语句和表达式之间的区别,但它使用典型的Perl方式来处理它,因此您可以省略return。)

  如果想把Lisp/Ruby/Perl/small talk/JavaScript的版本换成Python,会遇到一些限制。因为Python并不完全支持局部变量,所以你必须创建一个数据结构来接受n的值,而且虽然Python确实支持函数数据类型,但是没有文字表示来生成函数(除非函数体只有一个表达式),所以你需要创建一个命名函数并返回它。最终措辞如下:

  def foo (n):

  s=[n]

  定义栏(I):

  s[0]=i

  返回s[0]

  返回栏

  Python用户可以合理地质疑为什么不能写成这样:

  def foo (n):

  返回I:返回n=i

  或者:

  def foo (n):

  I:n=I

  我猜Python总有一天会支持这种写法。(如果你不想等到Python慢慢进化到更像Lisp,你总是可以.)

  在面向对象编程的语言中,您可以在有限的范围内模拟闭包(即一个函数,通过它可以引用包含该函数的代码所定义的变量)。使用方法和属性定义一个类来替换封闭范围内的所有变量。这有点类似于让程序员自己做代码分析,本来应该由支持局部作用域的编译器来做。如果有多个函数同时指向同一个变量,那么这个方法就会失败,但是在这个简单的例子中,就足够了。

  Python专家似乎同意这是解决这个问题的一个更好的方法。它是这样写的:

  def foo (n):

  等级符合:

  def _ _init_ _ (self,s):

  self.s=s

  定义公司(自我,我):

  自我=我

  回归自我

  返回acc (n)。股份有限公司

  或者

  foo类:

  def _ _init_ _ (self,n):

  self.n=n

  def _ _call_ _ (self,I):

  self.n=i

  回归自我

  我添加了这一段,因为我想避免Python爱好者说我误解了这种语言。但是,在我看来,这两种写法似乎比第一个版本更复杂。其实你也在做同样的事情,只不过你画了一个单独的区域,保存了累加器函数。不同的是,它保存在对象的一个属性中,而不是列表的头部。使用这些特殊的内部属性名(尤其是__call__)看起来不像是常规的解决方案,更像是破解。

  在Perl和Python的较量中,Python黑客似乎认为Python比Perl更优雅,但这个例子说明,最终,编程能力决定优雅。Perl写起来更简单(包含的语法元素更少),虽然它的语法有点难看。

  其他语言呢?Fortran,C,C,Java,Visual Basic之前都提过。好像用他们根本解决不了这个问题。肯安德森说Java只能写出一个近似的解:

  公共接口入口{

  公共int调用(int I);

  }

  公共静态输入foo(最终输入n) {

  return new Inttoint () {

  int s=n;

  公共int调用(int i) {

  s=s I;

  返回s;

  }};

  }

  这种写法不符合题目要求,因为只对整数有效。

  当然,我说用别的语言解决不了这个问题,也不完全正确。所有这些语言都是图灵等价的,也就是说严格来说,你可以用其中任何一种语言写任何程序。那么,我们如何做到这一点呢?就这个小例子而言,你可以使用这些不太强大的语言,只需编写一个Lisp解释器。

  这听起来像个笑话,但在大型编程项目中却不同程度地普遍存在。所以有人总结出来,命名为‘格林斯潘第十法则’:

  任何一个C或Fortran程序复杂到一定程度后,都会包含一个临时开发的Common Lisp的实现,只有一半功能,不完全符合规范,漏洞百出,运行缓慢。

  如果你想解决一个难题,关键不在于你使用的语言是否强大,而在于几个因素同时发挥作用(a)使用强大的语言,(b)为问题编写事实上的解释器,或者(c)自己成为问题的人肉编译器。在Python的情况下,这样的处理方法已经开始出现。我们实际上是自己写代码来模拟编译器实现局部变量的功能。

  这种做法不仅普遍,而且制度化。比如在面向对象编程的世界里,我们经常听到‘模式’这个词,我认为那些‘模式’就是现实中的因子(C),也就是人肉编译器。当我发现我自己的程序中使用的模式时,我认为这表明有问题。程序的形式应该只反映它想要解决的问题。代码中任何其他添加的形式都是一个信号(至少对我来说),表明我对问题的抽象还不够深入,它经常提醒我,我手动做的事情本应该用代码写出来,通过宏展开自动实现。

  (完)

郑重声明:本文由网友发布,不代表盛行IT的观点,版权归原作者所有,仅为传播更多信息之目的,如有侵权请联系,我们将第一时间修改或删除,多谢。

留言与评论(共有 条评论)
   
验证码: