首页IT科技lisp语言能干什么(为什么Lisp语言如此先进?(译文))

lisp语言能干什么(为什么Lisp语言如此先进?(译文))

时间2025-09-19 13:37:47分类IT科技浏览6633
导读:为什么Lisp语言如此先进?(译文)...

为什么Lisp语言如此先进?(译文)

上周                ,《黑客与画家》总算翻译完成                         ,已经交给出版社了                 。

翻译完这本书        ,累得像生了一场大病                        。把书稿交出去的时候                ,心里空荡荡的                         ,也不知道自己得到了什么        ,失去了什么        。

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

下面是此书中非常棒的一篇文章                         ,原文写于八年前                 ,至今仍然具有启发性        ,作者眼光之超前令人佩服                        。由于我不懂Lisp语言                        ,所以田春同学帮忙校读了一遍                 ,纠正了一些翻译不当之处,在此表示衷心感谢                。

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

为什么Lisp语言如此先进?

作者:Paul Graham

译者:阮一峰

英文原文:Revenge of the Nerds

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

一                、

如果我们把流行的编程语言                        ,以这样的顺序排列: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语言又是一种很低层次的语言                 。但是                ,在一个高度竞争的市场中                         ,即使开发速度只相差两三倍        ,也足以使得你永远处在落后的位置                        。

附录:编程能力

为了解释我所说的语言编程能力不一样                ,请考虑下面的问题        。我们需要写一个函数                         ,它能够生成累加器        ,即这个函数接受一个参数n        ,然后返回另一个函数                         ,后者接受参数i                 ,然后返回n增加(increment)了i后的值         。

Common Lisp的写法如下:

  (defun foo (n)

    (lambda (i) (incf n i)))

Ruby的写法几乎完全相同:

  def foo (n)

    lambda {|i| n += i } end

Perl 5的写法则是:

  sub foo {

    my ($n) = @_;

    sub {$n += shift}

  }

这比Lisp和Ruby的版本        ,有更多的语法元素                        ,因为在Perl语言中                 ,你不得不手工提取参数                        。

Smalltalk的写法稍微比Lisp和Ruby的长一点:

  foo: n

    |s|

    s := n.

    ^[:i| s := s+i. ]

因为在Smalltalk中,局部变量(lexical variable)是有效的                        ,但是你无法给一个参数赋值                         ,因此不得不设置了一个新变量,接受累加后的值                。

Javascript的写法也比Lisp和Ruby稍微长一点                ,因为Javascript依然区分语句和表达式                         ,所以你需要明确指定return语句        ,来返回一个值:

  function foo (n) {

    return function (i) {

      return n += i } }

(实事求是地说                ,Perl也保留了语句和表达式的区别                         ,但是使用了典型的Perl方式处理        ,使你可以省略return         。)

如果想把Lisp/Ruby/Perl/Smalltalk/Javascript的版本改成Python        ,你会遇到一些限制                         。因为Python并不完全支持局部变量                         ,你不得不创造一种数据结构                 ,来接受n的值                。而且尽管Python确实支持函数数据类型        ,但是没有一种字面量的表示方式(literal representation)可以生成函数(除非函数体只有一个表达式)                        ,所以你需要创造一个命名函数                 ,把它返回。最后的写法如下:

  def foo (n):

    s = [n]

    def bar (i):

      s[0] += i

      return s[0]

    return bar

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

  def foo (n):

    return lambda i: return n += i

或者:

  def foo (n):

    lambda i: n += i

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

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

Python高手看来也同意                 ,这是解决这个问题的比较好的方法        ,写法如下:

  def foo (n):

    class acc:

      def _ _init_ _ (self, s):

        self.s = s

      def inc (self, i):

        self.s += i

        return self.s

    return acc (n).inc

或者

  class foo:

    def _ _init_ _ (self, n):

      self.n = n

    def _ _call_ _ (self, i):

      self.n += i

      return self.n

我添加这一段                        ,原因是想避免Python爱好者说我误解这种语言        。但是                 ,在我看来,这两种写法好像都比第一个版本更复杂                 。你实际上就是在做同样的事                        ,只不过划出了一个独立的区域                         ,保存累加器函数,区别只是保存在对象的一个属性中                ,而不是保存在列表(list)的头(head)中                        。使用这些特殊的内部属性名(尤其是__call__)                         ,看上去并不像常规的解法        ,更像是一种破解        。

在Perl和Python的较量中                ,Python黑客的观点似乎是认为Python比Perl更优雅                         ,但是这个例子表明        ,最终来说        ,编程能力决定了优雅         。Perl的写法更简单(包含更少的语法元素)                         ,尽管它的语法有一点丑陋                        。

其他语言怎么样?前文曾经提到过Fortran                         、C        、C++        、Java和Visual Basic                 ,看上去使用它们        ,根本无法解决这个问题                。Ken Anderson说                        ,Java只能写出一个近似的解法:

  public interface Inttoint {

    public int call (int i);

  }

  public static Inttoint foo (final int n) {

    return new Inttoint () {

    int s = n;

    public int call (int i) {

    s = s + i;

    return s;

    }};

  }

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

当然,我说使用其他语言无法解决这个问题                        ,这句话并不完全正确                         。所有这些语言都是图灵等价的                         ,这意味着严格地说,你能使用它们之中的任何一种语言                ,写出任何一个程序                。那么                         ,怎样才能做到这一点呢?就这个小小的例子而言        ,你可以使用这些不那么强大的语言                ,写一个Lisp解释器就行了。

这样做听上去好像开玩笑                         ,但是在大型编程项目中        ,却不同程度地广泛存在                         。因此        ,有人把它总结出来                         ,起名为"格林斯潘第十定律"(Greenspuns Tenth Rule):

"任何C或Fortran程序复杂到一定程度之后                 ,都会包含一个临时开发的                         、只有一半功能的                 、不完全符合规格的        、到处都是bug的                        、运行速度很慢的Common Lisp实现                        。"

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

这种实践不仅很普遍                ,而且已经制度化了                        。举例来说                         ,在面向对象编程的世界中        ,我们大量听到"模式"(pattern)这个词                ,我觉得那些"模式"就是现实中的因素(c)                         ,也就是人肉编译器        。 当我在自己的程序中        ,发现用到了模式        ,我觉得这就表明某个地方出错了                 。程序的形式                         ,应该仅仅反映它所要解决的问题                        。代码中其他任何外加的形式                 ,都是一个信号        ,(至少对我来说)表明我对问题的抽象还不够深                        ,也经常提醒我                 ,自己正在手工完成的事情,本应该写代码                        ,通过宏的扩展自动实现        。

(完)

创心域SEO版权声明:以上内容作者已申请原创保护,未经允许不得转载,侵权必究!授权事宜、对本内容有异议或投诉,敬请联系网站管理员,我们将尽快回复您,谢谢合作!

展开全文READ MORE
机器学习实战 pdf(机器学习实战练手项目)