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

作者: 阮一峰

日期: 2010年10月14日

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

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

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

下面是此书中非常棒的一篇文章,原文写于八年前,至今仍然具有启发性,作者眼光之超前令人佩服。由于我不懂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解释器就行了。

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

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

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

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

(完)

留言(169条)

赞 :)

Lisp MS经常用来AI设计的

外洐越小,内涵越大!
简单到极致时,自然可以千变万化,
就象老子当年模糊的啍了声: 道可道,非常道...
就道尽未来一切变化;
幸好 Lisp 从来也本来没有想变成通用编程语言,才保持住了原始的那份天真设想,
Haskell 为了主流化,已经开始妥協了 ...
期望中国大学至少增个 Sheme 选修课吧

引用Zoom.Quiet的发言:

幸好 Lisp 从来也本来没有想变成通用编程语言,才保持住了原始的那份天真设想,
Haskell 为了主流化,已经开始妥協了 ...
期望中国大学至少增个 Sheme 选修课吧

中山大学开的“函数程序设计”选修课介绍的是Haskell。
本人数学专业,对Lisp缺少了解,不知阁下对两者异同有何高论?

学习LISP有20来年了,但一直没写过大的应用,呵呵,属于在内心中向往LISP的那批人吧,所以见LISP文章必顶,这篇文章由楼主来翻译还是大有好处的——毕竟看英文的人还是太少了。

Where is LISP winning? -- Where there are LISP programmers!

另,认为LISP是AI专用的是常见的误解,LISP是数学符号,通用编程语言,而且AI的实现方法也经过几次大的转向,现在有LISP开发AI不一定有优势了。

另外补充一个弹药,这个是科学家Peter Norvig写的。在国内,Peter Norvig以《十年编程无师自通》一文而知名,他现在是Google Director of Research。

地址: http://norvig.com/design-patterns/

John Vlissides在《关于模式的十大误解》中有一条为:误解之七:“ 模式是针对( 面向对象) 设计或实现的”(Kenny注:不知道有多少人同意这个?)

Peter Norvig的文章( slide)会明确地告诉你,如果把Design Patterns扔到LISP里,表现会是如何?这里我卖个关子,不给出任何结论,感兴趣的请自己去看,或者去请求楼主翻译,哈哈

(话说许多中国人都太想从别人那里得到现成儿的结论了,这些人都不爱思考,特别是critical thinking)

ruby比python确实更优雅一点

书啥时候出来啊

lexical variable 翻译成“局部变量”不合适

我当初看这个文章一直不明白这个观点:一个语言比另一个语言优秀?

作者谈数学,我觉得恰恰他自己没搞清楚数学。

计算理论上这些语言都是图灵完备(Turing complete)的,所以没有一个语言做的了,而另一个语言做不了的。

实际层面所有语言都要转化成汇编(assembly)和机器语言(binary)运行。虚拟机和解释器算不算?我觉得也差不多。只不过这个转化没有编译那么直接。

Lisp有一些好的程序语言设计的思想。如果要我简单表述可能是:这个语言的一些特性使其描述问题解法的一些方式更接近人对问题/算法的理解。

但这篇文章的一些论据不太站的住脚。简单的说作者(在写这篇文章的时候)是一个Lisp fanboy。

我是第一次听到Lisp语言,文中说的很多先进性的理由我也基本不懂.

理论上,语言应该是往更先进的方向发展的,越来越接近Lisp,说明Lisp是更先进的,但如此先进的语言一直以来并没有成为主流.

相对于一个太先进的Lisp,我认为主流语言更重要.即使完成任务花费的时间是Lisp10倍,但是能使用主流语言的人远远超过Lisp的10倍,他们完成的任务更多,贡献也更大.

我会诧异于超前的技术,但我更赞赏当代技术,因为这些是正在被用来最大化创造价值的,并且它们在可预期地不断演绎发展.

所以,我更关心的是:如何把极具先进性的技术(比如Lisp)更快的应用起来?

lisp的优秀最主要的来自于代码=数据,而且都是同一个简单得不能再简单的统一格式。

在上面玩一些东西就非常方便,如果你想用什么特性,大多可以在不长的时间内用lisp写一个出来,由于lisp的历史悠久,这样的东西就非常的多,现在语言的大多数新特性,只要你肯翻lisp的代码库,总能找到一样的,有可能还会更好。

lisp的问题起初是因为效率,而且发展过程中的方言化太严重了,本来可以造一座巴比伦塔的。

现在的发展恐怕还跟商业发展中的路径依赖有关。按我的想法,如果网页一开始用lisp格式,那恐怕什么html,xml,javascript,包括后台的各种脚本语言,估计都没法发展起来,前端后端一直到表现层,lisp足够了,比现在四分五裂的场面要强N倍。

可WEB刚发展起来的时候,大家只想到用一点点简单的格式化文本,哪里知道WEB将来要干这么多事情。接下来就是路径依赖的过程了,lisp的选择早就被远远的抛在一边了。

……我觉得python的大君明显是不喜欢lisp的,比如关于reduce的看法:http://www.artima.com/weblogs/viewpost.jsp?thread=98196

python模仿的似乎主要是haskell...

PS: 推一个华丽的东西,30分钟用ruby实现lisp:http://gist.github.com/562017

Perl老菜鸟,前来观看。

我坚信对Lisp的卖点鼓吹现在也仅仅在语法变换上是合理的;至于那些函数式语言的特性,很遗憾,这个领域目前几乎就属于Haskell和ML家族。
另一方面也很容易看到,Lisp家族中最流行的Common Lisp方言写的代码越来越倾向于鼓励命令式而非函数式的编程风格。
曾经我也很沉醉于动态类型检查赋予的高度灵活性,但是静态类型检查+类型推理显然给了我们两个世界最好的东西,尽管些许方面有些妥协。
如果语法变换对你很重要,在Perl 6正式发布之前恐怕你不会有太多选择;如果你更在乎自信、可靠地构建程序,希望在编译期间就排除绝大多数的类型错误,如果不是全部的话,而缓解你debug的痛苦,显然ML家族、Haskell、Scala会是更好的选择。

common lisp也有type inference的库,呵呵,估计也是很早的事情了吧。

我的感觉是,什么杂七杂八的东西都能从里面翻出来。

Python 2.6 版本,

def inc(n):
return lambda i : i + n

就可以完成这个功能吧?

上面的同学,这是2002年的文章了。

AMB也是Big John McCarthy发明的……

呵呵!虽然看不懂,但是还是感谢为IT业付出一份努力!值得感激!希望以后多发表一些此类型的文章!将感激不尽!

引用daiyugoal的发言:

Python 2.6 版本,

def inc(n):
return lambda i : i + n

就可以完成这个功能吧?

错了,Paul Graham要的是累加器,不是加法函数,即调用一次累加一次

想了半天,发现python有种 极其别扭 但也能在两行内 实现这个功能的方法……
def f(n):
return lambda x, y = [n]: (y.append(x), sum(y))[1]
不过估计极不实用……

Lisp无论从表达工具角度来说,还是从开发工具角度来说,都是一坨sh1t。这就好比2进制和10进制能表现一样的东西,但是人类还是会选择10进制。这里,Lisp就是2进制。这种洋洋自得的文章看多了,就觉得恶心了。

细说的话,首先Lisp缺乏上层组织工具这决定了它只适合处理求解的问题却不适合无科技含量的组装;其次,Lisp缺乏足够好的表达元数据的形式,导致干什么事都很痛苦。

至于code is data,generating on the fly这些理念,根本是任何一个老派程序员都玩的非常习惯的东西,怎么就成了Lisp的绝活了。其它编程语言也有很多重大缺陷,不过Lisp永远是个完美的渣。

引用young的发言:

想了半天,发现python有种 极其别扭 但也能在两行内 实现这个功能的方法……
def f(n):
return lambda x, y = [n]: (y.append(x), sum(y))[1]
不过估计极不实用……

用yield似乎更好...

Lisp 确实不错 —— 以60年前的标准来说。
Lisp 是一个历史,不是工具。
你可以说轮子的发明很伟大,很有创意。但是,你别说他比汽车跑得还快。 后代都是在前代的基础上发展起来的。

我觉得LISP可能是最丑陋的函数式语言。能力跟haskell也差很多

有的人喜欢数学式的编程,有的人喜欢文学性的编程,还有高手告诉我喜欢VB呢。不过就单从数理层面上来说,同意阮兄文中的观点。

我已经把自己的主要工作开发语言往Lisp转了。写了几个统计和抓取网页类的工具,感觉不错。虽然还没有Python那么顺手,但是已经体会到:Lisp用的越多越好用。

不太明白這個累加器的例子。
foo(n)(i)和n += i有何區別?

打个比方,怎么说呢。

lisp就像一个头发蓬乱的老教授的车库,什么乱七八糟的东西都有,当然看起来也是挺乱的,新手都不知道东西在哪放着,也不知道什么跟什么往一块组合就能用了。当然也不是鼓吹现在什么都要换lisp,lisp的问题就是太乱了,无组织无纪律。

另外多说一句,code=data不那么简单,lisp是唯一严格的做到这一点的。要是不严格的说,c也可以是code=data嘛。

感觉到一件事情很奇怪。上面所有的人你们都用过lisp吗?

感觉要么就没有仔细看这个文章,要么就没有用过lisp,要么用过lisp就完全按照命令行的方式来!
作者明明是非常重要的提到了lisp的终极武器:宏MACRO。上面竟然没有一个提到此的。

你们真是个假lisp粉丝!!!

多说句:ON LISP这本书非常不错

引用hesen的发言:

感觉到一件事情很奇怪。上面所有的人你们都用过lisp吗?

莫激动。谁用谁知道,自己心里爽就是了。

真是一竿子打翻一船人。上面明明有好几个人都是用过lisp的。

没提到macro就是不懂lisp?

lisp并不等于函数式好吧,用命令式的形式写lisp算不得大罪过,有些过程本来就是命令式的,强行用函数式方式来写既不好看也不好用。

这边书什么时候 上市 啊!我很期待!

请问阮先生一个出版相关的问题,通常情况下,译本的封面是由谁决定的呢?

引用hesen的发言:
作者明明是非常重要的提到了lisp的终极武器:宏MACRO。上面竟然没有一个提到此的。 你们真是个假lisp粉丝!!!
你真不好好看帖,我明明提到了“语法变换(syntax transformation)”,这个术语在Lisp论域内就是指macro,实际上R. Kent Dybvig的The Scheme Programming Language和Nils M Holm的Sketchy LISP: An Introduction to Functional Programming in Scheme里也是这么指称的。

引用Tyn的发言:

通常情况下,译本的封面是由谁决定的呢?

译文的所有事项,都由出版社说了算。

引用frouds的发言:

这边书什么时候 上市 啊!

明年上半年。快一点的话,就是春节前。

引用abc的发言:

common lisp也有type inference的库,呵呵,估计也是很早的事情了吧。

但CL始终是动态类型的,可能依靠类型推理把它实现为完全静态类型化的语言吗?

我的直觉是:不可能。联想到人们乐呵呵地把命令式特性赋予ML时,忽然发现它产生的多态引用竟然能让一个静态类型化的语言产生运行时类型错误,不得不引入类型约束(type constraints);Lisp除了命令式特性,还有macro这个大玩意儿呢,它会合类型推理的静态类型如何交互呢?

当然,这还得仰仗学术派的牛们来证或否该直觉;我们仅仅是,哪边凉快哪边呆着的观察者而已。

这个嘛,顺着你说的东西往下可深了去了,type theory这玩意的确绕人,牛们估计也晕着那。

跑个题,我在这方面的观点是往祖坟上刨,回到一阶逻辑和语言逻辑,不失为一个路子。比如给类型下了这么个定义:符号的类型,就是关于该符号命题的总和。按照这个定义,3和5两个自然数的类型都不一样。

如何理解这个定义,举例来说,如果有个大小为4的数组a[],如果写a[3]就是对的,写a[5]就要产生类型错误。

往这个方向探索的结果是发现,类型推导不仅要依赖于语法,最困难的是,它不得不依赖于语义。

语义最操蛋的事情就是,很多语义只会在运行时浮现出来,这就是joe armstrong一篇文章里面提到的,计算机语言不擅长描述协议:

“你需要告诉它们。你可以在状态机中添加这样的信息,说“当文件处于打开的状态时,你就可以对其进行读取”,或者“当你关闭了文件,就会将状态变为‘关闭’,当文件处于‘关闭’状态的时候,你就无法读取它。” ----joe armstrong

如果类型推理不纳入运行时语义,那么错误还是无法避免。
如果类型推理纳入了运行时语义,那么,该怎么办呢?

引用abc的发言:

上面的同学,这是2002年的文章了。

呵呵,我刚开始用Python不到半年,对Python历史了解不多,lambda是从哪个版本引入python的呢?

文中观点我基本都赞同,只要你读过SICP(Structure and Interpretation of Computer Programs,Second Edition),你就会了解的,该书中例子全部使用LISP编写。

SICP对面向对象和过程化的思维方式的分析,非常透彻,要知道,这书可是1984年出的第一版。

计算机语言发展太快了,我上大学大会,最流行的还是C语言,现在貌似Java都快过时了。


foo = lambda n: lambda i: i+n

不符合要求么?

引用Albert Lee的发言:

莫激动。谁用谁知道,自己心里爽就是了。

确实很爽,感觉Lisp确实很强大,有一天不看看她的话都会不舒服,之前用C/C++/Python/PHP/Erlang都没有过这么强烈的感觉

可能文章实在是很老了吧,今天的Python可以用两种方法来实现都很简洁:

其一:

def foo(n):
return lambda i:i+n

其二:

foo = lambda n: lambda i: i+n

工程界有句名言"less is more",Lisp只不过把计算机的数学模型做了最简易的包装。看看"(+ a b)"与"add a b"何其相似。就像不理解操作系统的人用不好C语言一样,不理解编译原理的人也用不好Lisp,譬如本人。

引用Zoom.Quiet的发言:

Haskell 为了主流化,已经开始妥協了 ...

信口开河,haskell哪里妥协了

虽然麻省理工学院人工智能实验室一直在维护 MIT/GNU Scheme,但累积一代又一代聪明黑客的心力,这太过于诡异神妙精心设计的产品到最后连自家人都不能完全理解掌握...

哦,我还仅仅处于学习C++的时代呢~瞻仰一下~

SICP(Structure and Interpretation of Computer Programs, 2nd Edition)
http://mitpress.mit.edu/sicp/full-text/book/book.html

文不对题啊。

明明题目是“为什么Lisp语言如此先进”,可是通读了全文只看到说Lisp如何如何先进,这也先进,那也先进,哪里在讲“为什么”呢?

如果文章的内容只是这样,那不如把题目改为“Lisp语言如此先进”。

说了这么多,看了这么多,不如实际行动起来,拿它写个东西,实际体会体会。

唉,翻得真是好。看的真让人有些感动,能拥这么大的诚意和热情看待翻译,难得的人品,难得的才能!

说它好把它用起来为行业所接受才是正道,不然就只是个玩具。

http://goo.gl/WT6BS

Python在closure上的一个限制,详解。

hello,很希望跟你认识,我也是自由软件的支持者


“所以,当我说假定你与ITA竞争,你用五年时间做出的东西,ITA在Lisp语言的帮助下只用三个月就完成了。”这是作者的逻辑错误。关于效率的比较,可以参考一下Weinberg的著作,有完整的论述。作者的思路也是一种典型的“银弹”观点。如果真的成立那就同微软去竞争一下好了。微软不过风光了二十多年,ITA一年多就可以打垮他了。

想起一个笑话:“你是站在人民那边还是党那边?”

同样,很多时候无法回答“你是站在数学那边还是机器那边?”

我觉得文中似乎已经讲出了答案:“大多数的编程任务,可能只要写一些很小的程序,然后用胶水语言把这些小程序连起来就行了。你可以用自己熟悉的编程语言,或者用对于特定项目来说有着最强大函数库的语言,来写这些小程序。“

引用shaung的发言:

不太明白這個累加器的例子。
foo(n)(i)和n += i有何區別?

累加器里面有保存状态,加法函数没有。

大学时开了Lisp与Fortran两门语言课, 记得Lisp还考了90多分,但最后毕业论文还是用的fortran! 虽然工作后从没用lisp做个项目, 但其思想对偶编程影响还是比较大!尤其对快速掌握haskell/erlang帮助甚大。

不过,个人觉得Lisp代码的可读性的确比较差 .......

这本书貌似买不到呢。当当和amazon上都没有,淘宝也没……

(c)你自己变成这个难题的人肉编译器。在Python的例子中,这样的处理方法已经开始出现了,我们实际上就是自己写代码,模拟出编译器实现局部变量的功能。

lexical variable 在这里仅仅翻译成“局部变量”是不是有些不妥?
对没接触过相关概念的人来说,局部变量是指"local variable",
然而在这里其实是指完全不同的概念。
如果我没看过原文的话,会觉得很费解。

PS:不知道作者有没有兴趣翻译"Let Over Lambda"这本书,至少对我这种
新接触lisp的C++土著来说,这本书的前三章带给我的是醍醐灌顶的震撼。

最近两周拿Lisp 写了一个小的内部工具,做分布编译和产品发布。差不多7,8百行代码。

最早的程序是用Python写的,只在一台机器上作,后来随着项目越来越复杂,打包编译花的时间越来越多。于是决定重写,那谁说重写代码是创业公司自杀行为来着?简直扯淡。

我决定使用 Common Lisp 来重写它。经过一周多时间,基本完成。

这一千行代码作的事情却一点也不少,它包含了:

1. 一个web界面,大概6、7个控制器,两个主要的view (没有html模板文件,都是通过lisp代码生成)

2. 一大堆的编译配置选项

3. 操作数据库的部分

4. 解析遗留系统 json 文件的部分

5. 通过ssh 执行命令,并将命令输出结果显示到web页面的部分

6. 编译任务的部分 : 创建、运行状态跟踪、命令输出

再整理下代码,估计可以缩减到 700行以内。

通过这个项目实践,对自己使用lisp的信心更足了。越复杂的项目,它的优势体现的越明显。

Common Lisp开发相比其他语言的一个核心优势是它的开发流程!

通过emacs + slime 这类工具,可以实现程序边运行边修改。程序运行中,可以修改函数代码,也可以修改程序中的变量的值,而不需要重新启动程序。这一点是相比其他开发语言来说非常显著的优势。

用common lisp开发的一个典型工作状态是: 打开emacs , 通过slime 开始执行程序,然后在源代码的各处敲敲打打,边修改边看执行的状态,还可以立刻在 repl 环境下修改变量的值。

这种边运行边开发的模式与PHP那种还不同,PHP程序虽然修改完可以在网页上立刻看到效果,但是是重新启动了一个执行的进程,之前的运行环境都要重新来。

Python也一样,虽然python有类似Lisp REPL那样的交互环境,但是却难以修改源代码,并非不可能,实在是没有趁手的工具。

再往下说 Java C++一类编译型语言,写程序与编译调试运行,整个流程分的太开,流程太长。

这种快速增量开发的模式,带来的就是更高的开发效率,和更复杂问题的处理能力。

http://blog.csdn.net/albert_lee/archive/2011/02/17/6192208.aspx

长见识了,看来学无止境呀~

正在学AutoLISP做CAD开发,这里貌似都是高手啊,膜拜!

lisp在大学不仅没有课程,就边书本都很少。再加上复杂难懂的演算。
普通人还是难以接受的。有谁能忍受那么多的括号。
所以想要了解lisp的特性,很多人都会转向ruby之类的语言---我觉得!

书我买了,翻译的不错,文笔很流畅,我用五一节一天的时间一气看完的。

确实可以这么写, 是我对您的文章理解有误吗? 或者是版本问题?
Python 2.6.6
>>> def s(n):
... return lambda x: x+n
...
>>> a = s(10)
>>> a(10)
20
>>> a(11)
21

已经买了楼主翻译的书,感觉不错。

看下来居然没有一个提到Prolog!

虽说是LISP的后辈,但个人认为Prolog完全可以和LISP分庭抗礼

十分想听听Paul或者阮一峰谈谈这两个语言的对比

我回复这个只是为了表达对你的尊重,因为你翻译了这本书《黑客与画家》,这是一本非常棒的书,原作者写的内容本已足够给国内的从业者带来思想冲击,而您的翻译更是让文本内容锦上添花,从您的博客中看到翻译本书并没有为您带来较好的经济利益,但我会鼓动周围的人都买这本书的,希望您不要太为金钱上的问题而苦恼,大环境的改变需要每一个人的力量,您的译著无疑会留下浓厚的一笔,感谢。

引用real的发言:

确实可以这么写, 是我对您的文章理解有误吗? 或者是版本问题?
Python 2.6.6
>>> def s(n):
... return lambda x: x+n
...
>>> a = s(10)
>>> a(10)
20
>>> a(11)
21

你确实理解有误。

文中要求的是累加器,执行结果应该呈现出这样:
>>> a = s(10)
>>> a(10)
20
>>> a(11)
31

认真看完正文和每一位前辈的留言,以我目前的能力,很多问题理解起来很有难度,学无止境,还待潜心学习。

书买了..看完了正跟着HTDP学SCHEME.以前看过长铗的一篇小说.看完之后我幼小的心灵就留下了LISP很强大的印象...
看完了楼上各位强人的留言.真心佩服...或许未来某一天可以达到他们一样的高度.加油吧少年.....
阮哥..读你的博客读了很久了.真的是非常丰富的精神食粮.谢谢你.

Paul Graham展示的是:
1,各个语言返回函数的能力;
2,各个语言定义的函数能具有状态的能力。
第二点C++实际上可以做到,通过仿函数,当然没那么优雅。
template
class Foo {
public:
Foo(T n): x_(x) {}
T operator()(T x) {
return x + x_;
}
private:
T x_;
};

Foo foo(10);
cout 就能得到30。
我认为他想说的是:函数也能有状态。Lisp和现在的Python都有闭包,所以轻易地实现了这一点。

求教,静态类型是不是会比动态类型的快很多?比如ocaml比clisp快很多?

现在python真的已经可以支持了:
def foo(n)
return lambda i:n+i

看来python在不断的演进啊

把贴子看了一遍发现理解问题有误。刚才的代码不对。

看来python的表达式的描述能力还是不足啊。

呵呵,打算以后有空学习lisp,学无止境!

这是用 Python 2.7 实现的一个累加器:

def my_foo(n):
return lambda i, lst=[n]: (lst.insert(0, lst.pop()+i), lst[0])[1]


Python 3.x 增加了一个新的关键字 —— nonlocal,实现起来会更优雅一些~

最近一段时间有幸读了这本书,中文翻译做的非常好,看的出来翻译的很用心。
作者做为一个前辈为有态度的程序员们提了很多建议和指导,不愧是硅谷创业之父。
讲了很多大公司的弊病和Lisp语言的优势,让我这个菜鸟Coder对这门编程语言产生了极大兴趣。
最后,真心感谢LZ翻译了这么好的一本书。

累加器的例子用 coffee:

(x) -> (y) -> x += y

这个算是一个累加器么(python)?
def foo(a,b=[]):
b.append(a)
return sum(b)


貌似很傻的实现,运行时间长了就不行了

(defun foo (n)
    (lambda (i) (incf n i)))
没学过LISP,只会C++,这代码看起来很诡异。

我刚学python,看到了这个问题的解法,是函数嵌套:
def outf(op):
def inf(ip):
return op+ip
return inf
=========
>>> x=outf(1)(2)
>>> x
3

非常感谢!十一看了这本书,对我很有帮助,启发了思考,开阔了视野!

C++中使用function adaptor也可以实现这种功能

引用wayhome的发言:

可能文章实在是很老了吧,今天的Python可以用两种方法来实现都很简洁:

其一:

def foo(n):
return lambda i:i+n

其二:

foo = lambda n: lambda i: i+n

这还是没有实现累加器,只不过算个闭包

这段时间有空时都在看《黑客与画家》,今天晚上下班回来时,在地铁上刚好看到这一章,感觉作者讲的真是太精彩了!!!有种冲动想继承学习LISP(以前由于学Emacs,学习过一点Emacs Lisp)。

引用c的发言:

计算理论上这些语言都是图灵完备(Turing complete)的,所以没有一个语言做的了,而另一个语言做不了的。

Lisp有一些好的程序语言设计的思想。如果要我简单表述可能是:这个语言的一些特性使其描述问题解法的一些方式更接近人对问题/算法的理解。

但这篇文章的一些论据不太站的住脚。简单的说作者(在写这篇文章的时候)是一个Lisp fanboy。

同意该观点,即使是lisp也要通过解释器变成二进制代码的吧,只能说某些语言更加适合一些特定程序或算法设计,每种语言都有自身的优势。我觉得就像说汉语和英语哪种语言更先进、更有优势,这是个仁者见仁智者见智的问题吧。个人觉得关键的不是语言,而是思想,语言只是思想的一种表现形式。

带累加和重置的累加器
def icounter():
count=[0]
def inner(c):
if c==0:
count[0]=0
return count[0]
else:
count[0]+=1
return count[0]
return inner
counter=icounter()

>>> counter(1)
1
>>> counter(1)
2
>>> counter(1)
3
>>> counter(0)
0
>>> counter(1)
1
>>> counter(1)
2

lisp里面的macro跟lazy evaluation是什么关系?求指教

Lisp果然是装逼神器

Python 3 的实现:
——————————————
def accumulator(n):
def f(x):
nonlocal n
n = n + i
return n
return f

但是还是不支持在匿名函数里面使用nonlocal

引用oldrev的发言:

lexical variable 翻译成“局部变量”不合适

我当时也是对这个词汇的完美翻译纠结了好久。不知有什么不错的建议吗?

java 代码的话。。。。的确要很长,试着实现了一下。我不知道Lisp为什么这么不流行,是学习曲线太陡吗。groovy不知道可否代替之。

诸位都很牛逼。lisp更接近语言这个词语的本源。

开始学习强大的lisp。

等了10年 还是没等来python的本质进化么

foo = lambda n: lambda i,j=[n]: (j.append(i),sum(j))[1]

引用JunkFood的发言:

Python 3 的实现:
——————————————
def accumulator(n):
def f(x):
nonlocal n
n = n + i
return n
return f

但是还是不支持在匿名函数里面使用nonlocal

#2.7 def aa(n): def result(x): return x+n return result aa4=aa(4) print(aa4(6))

感谢翻译这样一本好书,虽然不懂程序,但是学到不少东西。

其实lisp虽然难学,但是绝对能扩闊吾辈编程时的思维,而且确实非常厉害。

看过这本书,当时感觉就是醍醐灌顶,心潮澎湃,学习Lisp中ing

这里提到的Lisp的九种新思想,Mathematica都有实现。

那个问题在Mathematica中这样实现:


foo = Function[n, n += #1 & , HoldFirst]

如果写成树形表示法,就是:

Set[foo, Function[n, Function[i, AddTo[n, i]], HoldFirst]]

Mathematica中处理符号的时候,如果没有HoldFirst一类的限制,总是先代入符号的值再进行计算。所以这里只好有点别扭地加上一个HoldFirst。

既然函数也是一种数据类型,赋值和定义函数就应该用同样的符号。Lisp没有这样用让我觉得很奇怪。

引用AlephAlpha的发言:

既然函数也是一种数据类型,赋值和定义函数就应该用同样的符号。Lisp没有这样用让我觉得很奇怪。

沒錯,這是可以的。

(setf a (lambda (n) (1+ n))) 就在變量空間內定義了一個詞法閉包。

調用就是 (funcall a 2) => 3

刚看完《黑客与画家》一书,受到了不少启发与鼓舞,特地过来感谢一下,这年头要找到一本好书的同时又有一位好的译者真的不容易啊!

不错,学习了。
不知以下c片段能不能实现题目:
void lambda(void i, void * n){i+=*n;}
void * foo(void n){void i; return &lambda(i,&n);}

闭包的局部变量好像能用指针传址近似处理,而返回函数好像能用函数指针近似实现。

修改,防止局部变量失效;需要程序员维护内存工作了。。。
void lambda(void i, void * n){i+=*n;}
void * foo(void n){void i; &n=alloc(...);return &lambda(i,&n);}

引用Simon的发言:

我当时也是对这个词汇的完美翻译纠结了好久。不知有什么不错的建议吗?

直译为 词法变量 如何

这个累加器在C++中这么实现:

template
T foo(T x){ return [](T y){ return x+y; }; }

则么样?其实这个累加器没多大用,它只是把x和y的值加起来了而已。再看看LISP的几项“特性”:
1.条件结构:晕……,哪种语言没有这个?
2.函数在现代大多数语言中都以数据结构的形式出现。
3.递归:更晕……
4.变量的动态类型:这大约就是LISP成为龟速语言的原因之一了。
5.垃圾回收:目前这个吗……,被Java程序猿骂的很厉害……
6.由表达式组成的语言会很混乱,加入有一个LISP程序猿这样做:

(setq defun ;some)

他就完了……
7.Symbol:谁理解了Symbol?
8.大量符号和常量组成的树会然你晕。
9.RHEL:这个……

语言的诞生都是为了某个目的的,没有哪个语言是全能的,可能 LISP 的普适性更好一点,但不代表它就是完美的,更不能因此否定其他的语言,我相信任何语言都能找到某个比 LISP 更好的特性!

只要是语言就会有缺点。

lisp怎么样我不清楚,但是这本书很喜欢,导出闪耀着思想的火花!

…………(c)你自己变成这个难题的人肉编译器。

儅你寫LISP代碼時,您已經成為人肉编译器了。

LISP的那種語法大多數人是不習慣的。作爲對比,FORTRAN比LISP早出現幾年,一直都是科學計算的主要語言。而LISP雖然受到計算機理論界的關注,卻一直都是小衆語言。FORTRAN能取代彙編風靡起來,是因爲能夠用自然的方式書寫科學計算的代碼,並且運行效率不低于彙編代碼。LISP只能用人工智能的方式寫人工智能的代碼,並且運行效率極低。

LISP的各種方言就是妥協,加入了其他語言的特性,如setq和loop,讓程序員更容易入門,也讓代碼更符合程序員,而不是理論家的口味。

LISP的牛逼的灵活性和人工智能是以牺牲程序员的时间为代价的:短程序不一定写(构思和打字)得快,不一定容易调试。而自由散漫的语法肯定不利于调试。这样一种既忽视程序员的时间,又漠视计算机的时间的语言,注定只能成为被研究的对象,无法成为生产工具。

C++流行,别的(同时代的以及更早的)面向对象语言被束之高阁,也是类似的道理。C++象C,因此程序员们的时间投资得到保护,C++实现了面向对象,而编译时间和运行效率与C相当(有轻微逊色)。

引用Simon的发言:

我当时也是对这个词汇的完美翻译纠结了好久。不知有什么不错的建议吗?

通常以为词法变量

引用AlephAlpha的发言:

既然函数也是一种数据类型,赋值和定义函数就应该用同样的符号。Lisp没有这样用让我觉得很奇怪。

Common Lisp对实用性作出了妥协,貌似Scheme里面是一样的。

引用biohuang的发言:

…………(c)你自己变成这个难题的人肉编译器。

儅你寫LISP代碼時,您已經成為人肉编译器了。

Lisp的一个重要思想便是:一个新的Lisp程序便是对Lisp语言本身的扩展。好用的语法被不断创造出来。

按照这个道理,Common Lisp社区应该进化出一套快捷共享语法包的东西才对,不知为什么,我并未见到这种东西,也算是一种遗憾。

哈哈,突然仔细一想,觉得前面各种有才、各种丧心病狂用不同语言写累加器的朋友们都正中了 Paul Graham 的下怀啊……o(∩∩)o...

我把《黑客与画家》,整本书都读完了。和国内别的人翻译得不一样。就是翻译得很好。

人肉编译器,这比喻太贴切了。

很喜欢这篇文章,引用了哈,对我很有启迪作用。。
说点意见(很多不对的地方,欢迎指教):语言确实是越像lisp靠拢越优雅,但是我在实践的工程架构中是不会使用的,我会选用更为使用稳定的,容错能力更高语言或框架。例如首先会考虑tornado,其次考虑node.js,其次是go语言。但是不会用ruby。我的原因很简单:1.tornado是对epoll的封装,性能很好,而且python编程效率很高,python配备的开源资源足够丰富;2.node.js实现异步更为容易,但是缺少长期实践的大型的上线应用;3.golang其实很优雅,面向对象的多数高级特性都有。4.作为爱好者,我很喜欢ruby的设计,可能因为能力有限吧,我使用的ruby项目性能始终是个问题。

以前在大学做autocad的时候用过一阵子lisp,当时非常喜欢这东西,因为用的时候不知道为什么脑子会进入一种flow的状态

引用Simon的发言:

我当时也是对这个词汇的完美翻译纠结了好久。不知有什么不错的建议吗?

最好翻译成词法变量(lexical variable).
ANSI Common Lisp 中文翻译版中把lexical context翻译成词法语境就翻译得很好。
在编译原理中lexical向来翻译成词法。而有些clojure的书中翻译成字面量,实在不太容易理解。
而context一直在计算机中翻译成上下文,这一二十年都是这样,不好。翻译成语境非常好

&n=alloc(...);

C++第一次见到这种写法, 拜读了...

看了书, 原来python是从lisp借鉴的.
python脚本比c强大很多, 也就是程序短, 应该没什么疑问吧...

最后那个题目和强类型语言比是不是有些过分?
javascript实现:

function foo(n){
return function(i){return n+i;};
}

以下是百度上看到的对这门语言一些的评价
lisp是一门反人类的语言,有两个特征,函数式编程,和它是一门面向语言的语言

关于第一个特征:
没有赋值,状态变量不能保存,只能通过不停调用函数地运作,强制要你进行递归性的思考,由此而衍生出非常抽象的概念和晦涩的技巧,如以函数作为算子运算出函数的函数,堪称计算机领域的泛函语言,由于在思维上与数学有天然的契合度,lisp适合于类型推演等理论方向。

关于第二个特征:
lisp即list processing,表处理语言,对于数据和代码都只采用同一种结构来表示,这意味着lisp可以很优雅地将它的代码当作数据来处理。。。是的,在lisp里面,数据跟代码的界限是非常模糊。所以lisp是可以生成代码的语言,你完全可以利用lisp发明出属于你自己的特定语言,此所谓面向语言的语言。。。

什么人在用:
基于上述两点,你可以知道lisp非常难学,也非常难用。那么谁在用呢?有四类人:
学生。SICP听过吧?全球知名课程,这类人基本是闲的无聊,用用lisp来折磨折磨自己的大脑,感受感受这种当今世界中最接近神的宗教式的语言
Geek。这类人痴迷一切小众装13的技术,lisp自然成为喜欢泡在代码堆里的他们饭后闲暇的谈资,睡前冥想的材料,周末消遣的玩意; 此外,Geek也是希望通过lisp找到一条通往天堂的路
计算机科学家。这类人喜欢思考,尤喜那些错综复杂、扑所迷离的概念,抽象复杂却又大道至简的lisp自然正合其意,也是顺便找个可以发paper升职加薪的方向
凡人。lisp有很多方言,所谓方言,即lisp的变种。如Autolisp,用在制图领域;Elisp,用在配置编辑器emacs。这里面有不少伪用户(当然elisp高手除外),他们只是工作的需要而泛泛一用,对lisp的本质和方法未做深思,也谈不上了解

class Accumulator:
----'''这是一个累加器,接受一个初始数'''
----def __init__(self, init_num=0):
--------self.__init_num = init_num

----def count(self, num):
--------'''并且每次计数后进行累加'''
--------self.__init_num = self.__init_num + num
--------return self.__init_num

编程就像在一块画布上画画,设计就是怎么画,好的设计就是尽量让大家共同协作时能够相互理解。
我认为用python去模仿Lisp里需要的理念不是非常必要,程序员最重要的是解决好实际问题,所以用这个论点来证明Lisp比python先进显然也不是十分合理的。
对待累加器这个功能来说,优先分析它的特性,是否支持多实例,支持的计数类型,得知它是一个很可能需要多实例的并且在当前模块单元内只支持数字类型,因此对于C程序员来说,要将其实现为ADT(抽象数据类型),C++就是类,python来说就像上面的写法就可以了。
简单才是真正的优雅

虽然很有感慨却又不知道该说什么,这篇文章似乎更坚定了自己的一份信念……

def foo(n):
def bar(b):
bar.a += b
return bar.a
bar.a = n
return bar

这才是正确的python版本

文章最后拿代码行数作为开发速度的标准来分析是个败笔

c最好,其他语言都用不习惯!

引用Phoenix的发言:

这是用 Python 2.7 实现的一个累加器:

def my_foo(n):
return lambda i, lst=[n]: (lst.insert(0, lst.pop()+i), lst[0])[1]


Python 3.x 增加了一个新的关键字 —— nonlocal,实现起来会更优雅一些~


其实无需nonlocal。不要忘了,在python里,函数也是对象,也可以具有属性,所以:
def getacl(n):
def acl(i):
acl.n += i
return acl.n
acl.n = n
return acl

引用捉急的企鹅的发言:

其实无需nonlocal。不要忘了,在python里,函数也是对象,也可以具有属性,所以:
def getacl(n):
def acl(i):
acl.n += i
return acl.n
acl.n = n
return acl

14~15年评论们,注意一下文章的时间,是不是应该用当时的版本去测试呢?

Lisp的强大只在于只有七个操作符(define是为了把长长的代码缩小,并且可以重用 lambda貌似是继承自λ演算的吧 )
quote atom car cdr cons cond
他们可以构成大部分的事物(未验证)

数学像一个虫洞

从kindle电子书过来膜拜译者的 (๑•̀ㅂ•́)و✧

引用JunkFood的发言:

Python 3 的实现:
——————————————
def accumulator(n):
def f(x):
nonlocal n
n = n + i
return n
return f

但是还是不支持在匿名函数里面使用nonlocal

这算是个解决方案

用C++14简单实现了。

#include <iostream>
using namespace std;

int main()
{
auto a = [](auto n){ return [&](auto i){ return n += i; }; }(1);
cout << a(2) << endl;
cout << a(3) << endl;
return 0;
}

第一次发现,lambda还有这种保存内部变量状态的作用,了解了这一点,整个人都震惊了!
先前以为只是简单的加法函数,后来看到评论里面对于Python写法的争论才知道不是那么浅显的东西。

都是些装逼的脑残在吹捧LISP。

c++ java php 这等语言能成为主流,必然有他们不同于失败语言的特点。什么特点?就是语言伪代码的逻辑性符合人类语言结构。

对于一个了解基本c++语法的新手来说,看一个大工程的代码基本还是能分清执行逻辑的。而一个熟练的lisp程序员看一个很简单的lsp代码都得费很多功夫来理解程序功能执行逻辑。

说白了,lisp只是些歪客装逼卖弄的工具。

lisp这逼装的可以,你们可以去找那个王垠一起装逼

值得一读

引用m的发言:
修改,防止局部变量失效;需要程序员维护内存工作了。。。void lambda(void i, void * n){i+=*n;}void * foo(void n){void i; &n=alloc(...);return &lambda(i,&n);}

static void lambda(void i, static void n){i+=n;}
void * foo(void n){void i; return &lambda(i,n);}

使用static静态变量可以吗?还是说要考虑内存和其它限制?累加器实现不是计算机最基本和底层的吗?用程序语言在底层直接调用CPU的累加器功能可以吗?

引用daiyugoal的发言:

呵呵,我刚开始用Python不到半年,对Python历史了解不多,lambda是从哪个版本引入python的呢?

Python在2.2版才开始引入lambda,2.2正式版于2002年12月发布,2.2的早期版本并不支持lambda。本译文原文于2002年5月发布。目前还能搜到Python2.3版中关于lambda的bug报告。

这种级别的人写东西,不要太担心会把有的写成没有,全世界程序员的眼睛盯着呢。

es6简单到爆:

f = n => i => n += i

LISP唯一的优点,就是代码就是数据,数据就是代码。
最大的缺点,不适合普通人的大脑,
没有高等专家大脑,看到那一堆代码数据,基本转不过弯来。
C就通吃了,简单的大脑就可以看懂了。
Javascript就有点LISP毛病,有一些奇淫技术。
幸好Javascript是C这一派的,
函数式编程不适合人脑。

引用朱兆龙的发言:

你确实理解有误。

文中要求的是累加器,执行结果应该呈现出这样:
>>> a = s(10)
>>> a(10)
20
>>> a(11)
31

您好,我在目前版本的ruby中运行了原文中的代码,其反馈是
>> foo(20).call(10)
>> 30
>> foo(20).call(10)
>> 30
并没有出现您所描述的情况,是因为ruby的语法改了吗?

auto foo(n){
return [n](int i){ return n += i; }
}

LISP优秀的是语言特性吧

刚刚看完《黑客与画家》,感谢

各位全是人类的精英分子

引用c的发言:

我当初看这个文章一直不明白这个观点:一个语言比另一个语言优秀?

作者谈数学,我觉得恰恰他自己没搞清楚数学。

计算理论上这些语言都是图灵完备(Turing complete)的,所以没有一个语言做的了,而另一个语言做不了的。

实际层面所有语言都要转化成汇编(assembly)和机器语言(binary)运行。虚拟机和解释器算不算?我觉得也差不多。只不过这个转化没有编译那么直接。

Lisp有一些好的程序语言设计的思想。如果要我简单表述可能是:这个语言的一些特性使其描述问题解法的一些方式更接近人对问题/算法的理解。

但这篇文章的一些论据不太站的住脚。简单的说作者(在写这篇文章的时候)是一个Lisp fanboy。

都是图灵完备没错,但你忽略了一点:人脑能处理问题的复杂程度是有限的。并不是某种语言写不出某个程序,而是“人”不能够用某种语言写出某个程序。编程语言的极限不在这里,但人的极限要近得多。

比如生命周期控制,用高级语言一般很容易就可以完成,C/C++稍微花点功夫,而汇编就是花数月才能达到一样的水准,效率说不定还比-O2低。
比如Stack Trace查错,汇编需要花非常长的时间,但Python下基本可以立即确定错误点。
如果Wolfram语言不是用Mathematica语言来实现而要用C语言,估计现在都还没一撇。

更何况现在的汇编都在转向面向编译器。就连CPU厂都不想要手写汇编了。

这不是闭包, 谢谢 : ) ,
无聊翻看 lisp 的文章, 看评论一堆连累加器定义都不知道的秀代码真的是醉了,
哪怕是很多年前的评论

haskell 更简单
foo n = (n + )

Rust 语言(支持模板参数):
fn foo(n: i32) -> Boxi32>{
Box::new(move |i| n+i)
}

瞻仰各位大佬,也看到了10年的年轻人和20年的年轻人在心气上并没有太多差别,哈哈哈

评论中10年到20年各语言对累加器的实现不是印证了书里的说法?

评论的 C++ 方案里没一个用 mutable 的,都是错的……

template<typename T>
auto foo(T n) {
    return [n](auto i) mutable { return n += i; };
}

C++14 下通过。

Rust 1.26 后可以使用 impl Trait :

fn foo(mut n: i32) -> impl FnMut(i32) -> i32 {
    move |i| {
        n += i;
        n
    }
}

文章里用的 Common Lisp,而如果用 scheme 的话:

(define (foo n)
  (lambda (i) (set! n (+ n i))))

虽然长了点,但似乎更清楚。

看完从10年到20年的评论,太有意思了!
作者原书发行与2002年,阮老师的翻译版发行与2010年,我留言时现在是2021年1月
今天重新翻看这本《黑客与画家》,给我带来的冲击甚至比多年前读时更大了。
因为书中大量预见性的思想和观点,今天都早已成真,而且有些东西彻底改变了世界和我们的生活方式——手机,移动互联网,云计算……

“苹果公司还没有失败,如果他能把iPod升级成手机,并且将网络浏览器包括在其中,那么微软公司就有大麻烦了”(阮老师译文注:这段话写于2001年9月。。。)
“要是微软不能控制终端设备,他就只剩下一条路,就是把用户推向自己的互联网软件”
(微软在走了十几年的弯路后,换了个印度CEO终于走回到这句话所指的方向上了)

这本书宝藏太多,可惜多年前刚入行的时候没读懂。。。太遗憾

说回Lisp,上学时候没学过,多年过去了,现在打算重头学一下,像一个少年一样,不为什么,只是纯粹的去领略,去思考。

从10年到20年的评论,就好像是书中的一个实例。
感叹作者敏锐的预见性和强大的判断力!

引用酱油瓶的发言:

14~15年评论们,注意一下文章的时间,是不是应该用当时的版本去测试呢?

def foo(n):
def bar(i):
bar.n += i
return bar.n
bar.n = n
return bar

留意到你的评论,大半夜心血来潮找了早期版本的Python测试。
2001年12月21日发布的Python 2.2.0可以运行@仙木人和@捉急的企鹅的代码。根据其他评论的描述,这篇文章是2002年写的,所以当时的Python的确可以这样实现。
同时测试发现,2001年6月22日发布的Python 2.0.1不支持动态添加类属性的特性,所以无法运行上面的代码,不排除通过其他技巧性方法实现类似的功能,但这样的实现肯定是不如@仙木人和@捉急的企鹅的代码优雅。

可见,涉及编程语言之间的相互比较的观点,往往不可避免持有偏见,除非论者对评价的各种语言都充分了解。显然阮老师发表这篇时没有做到,当然这无可厚非,因为当时Python语言自身正在飞速发展,非使用者不会紧密关注新特性。

另外,Python中,变量、函数和类同属对象以及对象属性支持动态声明的特性,使得内层函数可以通过动态添加属性的方式实现了状态保存,并且免于繁琐的类声明。评论区中存在部分观点,认为其他语言能优雅地实现相同功能,是因为语言加入了借鉴自Lisp的语法糖,而Python这种写法有力反击了这样的观点。

最后,感谢@仙木人和@捉急的企鹅提供的思路。

@湛忠胜:

cpp 无脑粉、我觉得你并没有完全学习过 lisp 的任何一门方言,否则就不会犯将 repl 拼成 rhel 的错误了

用python设一个dict实现一组累加器(好处是可以一览无余看到所有累加器当前的状态,而且别处也可以改这些数据呀)

global_dic= dict({0:0})

def accu_gen(start_val):
new_id= max(list(global_dic.keys()))+1
global_dic[new_id]=start_val

def accumulator(summand, my_id):
global_dic[my_id] += summand
return global_dic[my_id]

return lambda x: accumulator(x, new_id)
#testing
mywallet= accu_gen(99)
print(mywallet(10))
print(mywallet(20))

yourwallet= accu_gen(22)
print(yourwallet(22))
print(mywallet(20))

print(yourwallet(22))

……
好多评论啊 懒得看了(内容(见下)重复勿喷)
1. 虽然文章……但似乎现在python3也能用lambda表达式了欸

2. 说Lisp是反人类语言的,略微不是很赞同:
在下以前是学习C的,当时觉得C是一门非常优美规整的语言(当然现在还是这么认为),并且鄙视python,认为其语法很乱(现在并不这么认为了[捂脸]);
但当我后来学了Lisp,熟悉了Lisp的思维模式,之后因为一些需要重新入手C,当时觉得C!才是反人类(当然现在重温了之后也习惯了)。

个人从两种语言对比的角度觉得:
之所以C程序员觉得Lisp反人类(反过来亦然),可能是因为两者语言风格差别太大,导致人们长期浸淫在两种语言的熏陶中,思维模式也变得更加适应当前的语言;因此当接触到与自己所用语言风格迥异的语言时,由于思维模式的冲突,自然而然感到极大不适与约束。
其实无论是C也好,Lisp也好,都是非常优秀的语言:Lisp有C(直接)做不到的操作,难道C没有一些方面胜于Lisp吗?比如对运行速度的优化,与显式指针的处理,同样可以做出一些Lisp无法做到的“tricks”(Lisp根本没有显式的指针);因此从语言的局限来评价两种语言孰优孰劣,个人感觉是不科学的,更别提踩一捧一本就不对。这并不是说不能对语言有某种偏好,但是偏好并不意味着客观的优越性;任何无标准、无背景的评判都是耍流氓。

关于两种语言的关系,似乎有一种这样的说法:C和Lisp好比语言的两座高山,而其他语言则是介于两者之间的山谷。当然这并不说明其他语言就劣等,因为“中间方案”往往有其灵活性;至于偏好的问题,个人认为,两种语言阵营应以相互学习、相互借鉴为是,而非挑起语言界的“新冷战”。

我要发表看法

«-必填

«-必填,不公开

«-我信任你,不会填写广告链接