Blub 困境
Lisp语言到底好在什么地方?如果它真的这么好,为什么没有得到广泛使用呢?这种问题听起来有点像绕口令,但是实际上回答起来很简单。Lisp语言的好处不在于它有一些狂热爱好者才明白的优点,而只在于它是目前最强大的编程语言。它没有得到广泛使用的原因就是因为编程语言不仅仅是技术,也是一种习惯性思维,非常难于改变。当然,上面两句话都需要进一步解释。
我先从一个争议极大的命题开始讲起:编程语言的编程能力有差异。至少不会有人反对高级语言比机器语言更强大这一观点。今天的大多数程序员通常情况下都不会想用机器语言编程,而是使用一种高级语言,然后再让编译器帮你把它翻译成机器语言。这种观念甚至已经移植到了硬件,从20世纪80年代开始,硬件的指令集都是针对编译器而不是针对程序员设计的。
大家都知道,徒手用机器语言写出整个程序是一件很蠢的事。但是,把这个观点推广到一种更普遍的情况,知道的人就不多了。如果你有好几种语言可以选择,在不考虑其他因素的情况下,你不选择最强大的那种语言就是一件很蠢的事^。
^「如果从图灵等价(Turing-equivatent)的角度来看,所有语言都是一样强大的,但是这对程序员没有意义。(没人想为图灵机编程。)程序员关心的那种强大也许很难正式定义,但是有一个办法可以解释,那就是有一些功能在一种语言中是内置的,但是在另一种语言中需要修改解释器才能做到,那么前者就比后者更强大。如果A语言有一个运算符,可以移除字符串中的空格,而B语言没有这个运算符,这可能不足以称A语言比B语言强大,因为你可以在B语言里写一个函数实现这个功能。但是,如果A语言支持某种高级功能(假定是递归),而B语言不支持,你就不可能通过自己编写函数库解决了,所以这就代表A语言比B语言更强大。」
上面这个观点有许多例外情况。如果在开发的程序必须与另一个程序紧密配合,那么可能最好还是使用后者的开发语言。如果你的程序只是要做一些很简单的事(比如整数运算或者位操作),那就不妨使用一种比较靠近机器的低层次语言,主要原因是这样运行起来会更快一些。如果你的程序很短,只是为了特定场合一次性使用,那么你最好根据自己要解决的问题选择具有最强大函数库的语言,不过,总的来看,对于应用程序来说,还是应该选择总体最强大、效率也在可接受范围内的编程语言,否则都是不正确的选择,就好像你选择机器语言编程一样,只是程度上有差异而已。
大家都公认机器语言属于非常低层次的语言。但是,至少在社会上很多人眼里,高级语言其实也差不多。但事实并非如此,高级语言与机器语言的差别很大。从技术上看,“高级语言”并不是一个定义很清晰的名词。在高级语言与机器语言之间并不存在一条明确的分界线。语言的抽象性是一条连续曲线,从最强大的语言一直到最底层的机器语言,每一种语言的能力都有差异^。
^「语言之间的关系或许还可以比喻成栅格结构(lattice),从下往上朝着顶端慢慢收窄。具体的形状在这里并不重要,重点是语言之间至少存在着一种偏序关系(partial order)。」
以Cobol语言为例,通过编译器,它可以被编译成机器语言。从这个角度来说它是一种高级语言。但是,有谁会真的把Cobol当成与其他高级语言(比如Python)—样强大的语言?比起Python,它可能更接近机器语言。
Perl 4如何?与Perl 5相比,它不支持闭包。所以,大多数Perl的黑客都认为Perl 5比Perl 4更强大。如果你同意这一点,就意味着你也认可一种高级语言可以比另一种高级语言更强大。因此,必然能够接着推导出,除了某些特殊情况,你就是应该使用目前最强大的语言。
不过在现实中这个结论很少能落实。到了一定年龄之后,程序员极少主动更换自己的编程语言。不管习惯使用的是哪一种语言,他们往往认为这种语言已经足够好了。
程序员非常忠于他们心爱的语言,我不想伤害任何人的感情,所以为了解释我的观点,我假设有一种Blub语言。它的抽象程度正好落在编程能力曲线的中点。它不是最强大的语言,但是要比Cobol或机器语言更高级。
我们假设Blub程序员既不使用机器语言也不使用Cobol语言。他认为前者是编译器的工作,后者他不知道有什么用(Cobol语言甚至连XX功能也没有,Blub语言就具备这个功能)。
只要这位程序员向曲线下方望去,他就肯定知道自己正在看的是一些比较低层次的语言。因为那些语言明显不如Blub语言强大,缺少他习惯使用的某些功能。但是,当他向曲线上方望去,他不会意识到自己正在看更高层次的语言,而是仅仅觉得自己正在看某些奇怪的语言。他可能认为那些语言也许与Blub一样强大,但是加入了不少怪东西。他觉得Blub语言已经够用了,不用再考虑那些语言了。这时,他的思维就是已经被Blub同化了。
但是,当我们转换视角,把自己想象成使用曲线更上方某一种语言的程序员并往下看的时候,我们就会发现,自己也同样轻视Blub语言。你怎么用Blub语言完成工作呢?它甚至连YY功能都没有!
通过归纳法我们就会知道,唯一洞悉所有语言优劣的人必然是懂得最强大的那种语言的人。(这大概就是埃里克·雷蒙德所说的Lisp语言使你成为一个更好的程序员的意思。)由于Blub困境的存在,你无法信任其他人的意见:他们都满足于自己碰巧用熟了的那种语言,他们的编程思想都被那种语言主宰了。
我自己的经历也证实了这个看法。高中时我喜欢用Basic语言编程。这种语言功能很弱,甚至不支持递归,很难想象没有递归还怎么编程。但是我那时根本没觉得有损失,Basic语言控制了我的思维。当时我非常精通Basic语言,只要是学过的部分都能熟练地使用。
雷蒙德推荐的五种黑客应该学会的语言,其强大程度各有不同,分布在编程能力曲线五个不同的点上。它们的相对位置是一个敏感的话题。我只想说,我认为,Lisp语言在最上方。为了证明这个论断,让我告诉你,我发现Lisp有一个功能,其他四种语言都没有。我觉得,没有宏(macro)的话,那些语言怎么编程呢^?
^「把宏说成一种独立的功能有误导之嫌。在实际运用中,如果没有其他Lisp功能(比如闭包和函数的rest参数)的配合,Lisp的宏也不会有太大作用。」
许多语言自称也有宏,但是Lisp的宏是独一无二的。信不信由你,Lisp宏的作用与括号有关。Lisp语言的设计者大量使用括号并不是为了标新立异。Blub语言的程序员会觉得Lisp代码看上去很怪,有那么多括号,但这是有原因的。它们是Lisp与其他语言存在巨大差异的外在表现。
Lisp代码由Lisp数据对象构成。其他语言的源代码一般由字符组成,字符串是主要数据类型之一,但是Lisp语言不完全是这样。经过解析器处理之后,Lisp代码就变成了你可以遍历的数据结构。
如果你理解编译器的工作原理,那么事实是,与其说Lisp有一种很奇特的语法,还不如说它根本没有语法。一般的源代码程序经过编译器解析会生成解析树。Lisp的奇特之处就在于,你可以完全写出程序,控制这种解析树,进行任意的存取操作。Lisp的这种程序就叫做宏,它们可以用来生成其他程序。
生成其他程序的程序?什么时候需要用到它们?如果你用Cobol语言思考,会觉得很少需要用到它们。如果你用Lisp语言思考,会发现它们无所不在。我要是在这里举一个Lisp宏功能强大的实例,可能更便于说明问题。你看这个例子!是不是很方便啊?但是如果这样做,对于不懂Lisp语言的人来说,这篇文章就不知所云了。本文没有办法把所有事情都解释清楚,无法帮助你彻底理解这门语言。我在Ansi Common Lisp一书中已经尽可能地简化内容、快速讲解,但是也要到全书篇幅将近一半的地方(第11章)才能讲到宏。
但是我想可以给出事实证明我的这个观点。Viaweb编辑器的源码之中大约20%~25%是宏。它们比普通的Lisp函数难写,而且如果用在不必要的地方,反而是一种很不良的编程习惯。所以,我们代码中的每一个宏都有充分的使用理由。这意味着这个程序至少20%~25%代码的功能无法轻易地用其他语言实现。我在前文一再声称Lisp语言无比强大,无论Blub语言的程序员对此多么怀疑,看到这个事实应该足以让他感到很好奇,我们居然用到了这么多宏。我们这样写代码并不是为了好玩。我们是一家小创业公司,拚尽全力写代码,只是为了给竞争对手布下重重障碍,不让他们赶上来。
抱有怀疑态度的人可能会想上面的论断是否成立,两者之间是否存在相关关系。我们的一大块代码能够做到其他语言很难做到的事。只凭这一点是否能得出结论:我们的软件能够做到竞争对手的软件做不到的事?我必须说,这里面可能就是存在相关关系。我鼓励你继续深入思考这个问题。表面上,一个老年人拄着拐杖蹒跚而行,你不要只是看看而已,他背后可能有更多的故事值得了解,你应该想得更深一些。
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论