Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

让自己流行起来

原文:Being Popular
作者:Paul Graham 发表:2001-05
译者:Claude(baoyu-translate)

2001 年 5 月

(这篇文章算是为一门新语言写的商业计划书。所以它漏掉了——因为默认了——一门好的编程语言最重要的特性:非常强大的抽象能力。)

我的一个朋友曾经告诉某位著名的操作系统专家,说他想设计一门真正好的编程语言。那位专家告诉他这是浪费时间,编程语言流行与否,跟它本身的优劣没什么关系,所以无论他的语言多好,都不会有人用。至少,他自己设计的那门语言就是这种下场。

到底是什么让一门语言流行?流行的语言配得上它们的人气吗?花力气去定义“好的编程语言“值不值?又该怎么定义?

我觉得这些问题的答案,得从黑客身上去找,看他们想要什么。编程语言是给黑客用的,一门编程语言作为编程语言(而不是作为指称语义或编译器设计的练习)是好是坏,唯一的标准就是黑客喜不喜欢它。

1 流行的机制

确实,大多数人选编程语言并不是只看它好不好。多数程序员是被别人告知该用哪门语言的。但我觉得这种外部因素对编程语言流行度的影响,并没有人们有时想象的那么大。更大的问题在于,黑客眼中“好的编程语言“和大多数语言设计者眼中的不是一回事。

这两种意见里,重要的是黑客的意见。编程语言不是定理。它们是工具,是为人设计的,必须迎合人的长处和短处,就像鞋子必须为人的脚设计一样。一只鞋穿上去夹脚,那就是只烂鞋,哪怕作为一件雕塑作品再优雅也没用。

可能大多数程序员分辨不出语言的好坏。但任何工具都是这样。这并不意味着设计一门好语言就是浪费时间。专家级黑客一眼就能看出哪门语言好,并且会去用它。专家级黑客是极少数,没错,但这极少数人写出了所有好的软件,他们的影响力大到让其他程序员倾向于跟着用他们用的语言。事实上往往不只是影响——而是命令:因为专家级黑客经常正是那些当老板或导师、告诉别的程序员该用什么语言的人。

专家级黑客的意见并不是决定编程语言相对流行度的唯一力量——遗留软件(Cobol)和炒作(Ada、Java)也起作用——但我认为长期来看,它是最强的力量。给定一个初始的临界规模和足够的时间,一门编程语言大概率会变得跟它该有的人气差不多。而流行又会进一步把好语言和坏语言分开,因为来自真实用户的反馈总能带来改进。看看任何一门流行语言一辈子里变了多少。Perl 和 Fortran 是极端例子,但连 Lisp 都变了很多。比如 Lisp 1.5 没有宏;这是后来 MIT 那帮黑客用 Lisp 写了几年真实程序之后才进化出来的。[1]

所以,不管语言要不要好才能流行,我觉得它得流行才能好。而且得保持流行才能保持好。编程语言的最前沿不会停下脚步。可我们今天用的这些 Lisp,跟 1980 年代中期 MIT 那边的差不多还是同一个东西,因为那是 Lisp 最后一次拥有足够大、足够挑剔的用户群。

当然,黑客得先听说过一门语言才能用它。他们怎么听说?从其他黑客那里。但总得有最初一批黑客在用这门语言,别人才有机会听说。我有时候会想:这一批人得多大?多少用户算“临界规模“?凭直觉我会说二十。如果一门语言有二十个独立用户——意思是他们各自决定用它——那我就觉得它是真东西了。

走到那一步并不容易。从零到二十,可能比从二十到一千还难,我一点也不会奇怪。要拿下最初那二十个用户,最好的办法大概是用特洛伊木马:给人一个他们想要的应用,而这个应用恰好是用这门新语言写的。

2 外部因素

我们先承认一个确实会影响编程语言流行度的外部因素。要流行,一门编程语言必须是某个流行系统的脚本语言。Fortran 和 Cobol 是早期 IBM 大型机的脚本语言。C 是 Unix 的脚本语言,后来 Perl 也是。Tcl 是 Tk 的脚本语言。Java 和 JavaScript 想做浏览器的脚本语言。

Lisp 不是大众语言,因为它不是任何大众系统的脚本语言。它今天保留下来的那点人气,可以追溯到 1960、1970 年代,那时它是 MIT 的脚本语言。当年许多伟大的程序员都跟 MIT 沾过边。1970 年代初、C 出现之前,MIT 那个 Lisp 方言 MacLisp,是少数几门正经黑客愿意用的语言之一。

今天 Lisp 是两个中等流行系统的脚本语言:Emacs 和 Autocad。所以我猜,今天写的 Lisp 程序大部分是 Emacs Lisp 或 AutoLisp。

编程语言不是孤立存在的。“hack“是个及物动词——黑客总是在 hack 某个东西——实践中,语言要看它被用来 hack 什么东西,才能下评判。所以如果你想设计一门流行的语言,要么你得提供不止是一门语言的东西,要么你得设计你的语言去替换某个现有系统的脚本语言。

Common Lisp 不流行,部分原因是它是个孤儿。它一开始确实有可以 hack 的系统:Lisp 机。但 Lisp 机(连同并行计算机)在 1980 年代被通用处理器越来越强的算力碾平了。要是 Common Lisp 当时是 Unix 的好脚本语言,可能还能继续流行。可惜,它是个糟糕透顶的 Unix 脚本语言。

描述这个局面的一种说法是:一门语言不是单凭自身优劣被评判的。另一种看法是:一门编程语言要不是某样东西的脚本语言,它就根本不算一门编程语言。这种看法只有在你没意识到的时候才显得不公平。我觉得这跟期望一门编程语言要有个实现一样合理。它就是编程语言这个概念的一部分。

一门编程语言当然需要一个好的实现,而且必须免费。公司会为软件掏钱,但黑客个人不会——而你要吸引的就是黑客。

一门语言还需要一本关于它的书。这本书应该薄、写得好、有大量好例子。K&R 是这方面的范本。眼下我几乎想说,一门语言得有一本 O’Reilly 出版的书才行。这正在变成“是否对黑客重要“的检验标准。

应该也有线上文档。事实上,这本书可以从线上文档起步。但我不觉得纸书已经过时了。它的形式很方便,而且出版商加的事实审查门槛虽然不完美,却是有用的过滤器。书店是了解新语言最重要的场所之一。

3 简洁

假设你能提供任何语言都需要的三样东西——免费的实现、一本书、一个可以 hack 的对象——你怎么做出一门黑客会喜欢的语言?

黑客喜欢的一点是简洁。黑客很懒,跟数学家和现代主义建筑师一样懒:他们讨厌一切多余的东西。说“黑客在动手写程序前选用哪门语言,至少在潜意识里,是按要敲多少字符来决定的“——这话离真相不远。就算黑客的思考方式不完全是这样,语言设计者也最好把它当真。

试图用模仿英语的冗长表达去把用户当婴儿哄,是个错误。Cobol 在这点上臭名昭著。让黑客写

add x to y giving z

而不是

z = x+y

他会觉得这介于侮辱智商和亵渎神明之间。

有人说 Lisp 应该用 first 和 rest 取代 car 和 cdr,因为这能让程序更易读。也许头几个小时是这样。但黑客很快就能记住 car 是表的第一个元素、cdr 是其余部分。用 first 和 rest 意味着多敲 50% 的字符。它们长度还不一样,意味着调用时参数对不齐——而 car 和 cdr 经常连续出现在好几行里。我发现代码在页面上对不对得齐很重要。Lisp 代码用变宽字体排出来我几乎读不下去,朋友说别的语言也一样。

简洁是强类型语言吃亏的一个地方。其他条件相同的情况下,没人想以一堆声明开头一个程序。能默认的,就该默认。

单个标识符也应该短。Perl 和 Common Lisp 在这件事上是两个极端。Perl 程序密得几乎像密码,而 Common Lisp 内置操作符的名字长得可笑。Common Lisp 的设计者大概期望用户用的文本编辑器能帮他们补全这些长名字。但长名字的代价不只是敲字。还有读它的代价、它在屏幕上占空间的代价。

4 可 hack 性

对黑客来说还有一样比简洁更重要的东西:能做你想做的事。在编程语言史上,惊人多的精力被花在阻止程序员做“不正当“的事情上。这是个危险的、自以为是的计划。语言设计者怎么知道程序员将来会需要做什么?我觉得语言设计者最好把目标用户当成一个天才——一个会需要做你从未预料的事的天才——而不是当成一个需要被保护、免得自己伤到自己的笨蛋。笨蛋反正都会搬起石头砸自己脚的。你也许能保住他不去引用别的包里的变量,但你救不了他写出一个解决错问题的烂设计、还耗上无穷无尽的时间。

好程序员经常想做危险又上不了台面的事。我说“上不了台面“,是指那些钻进语言试图维持的语义门面背后的事:比如,去拿到某个高层抽象的内部表示。黑客喜欢 hack,hack 就意味着钻进东西内部、回过头去质疑原作者的设计。

让别人来质疑你。你做出任何工具,人都会用出你没设想过的姿势——对编程语言这种高度精细的工具尤其如此。许多黑客会想用你从没想过的方式去捏你的语义模型。我说,让他们捏;只要不危及垃圾回收器之类的运行时系统,把尽可能多的内部东西交到程序员手上。

在 Common Lisp 里我经常想遍历一个 struct 的字段——比如把指向某个被删对象的引用清掉,或者找出未初始化的字段。我知道 struct 底下就是向量。可我没法写一个能在任何 struct 上调用的通用函数。我只能按名字访问字段,因为 struct 就该是这个意思。

黑客在一个大程序里,可能也就一两次会想绕开既定模型。但能做这件事,差别巨大。而且这可能不只是解决一个问题的事。这里也有一种快感。黑客分享外科医生在血淋淋的内脏里乱戳的那种秘密快感,分享青少年挤痘痘的那种秘密快感。[2] 至少对男孩子而言,某些恐怖的东西就是有种吸引力。Maxim 杂志每年出一本影集,里面混着养眼女郎和恐怖事故。他们摸透了自己的读者。

历史上,Lisp 在让黑客随心所欲这点上一直做得不错。Common Lisp 那种政治正确是个反常。早期的 Lisp 让你能伸手摸到一切。所幸那种精神有相当一部分保留在了宏里。能对源代码做任意变换,多妙的一件事。

经典的宏是真正属于黑客的工具——简单、强大、危险。它们做什么很容易理解:你对宏的参数调用一个函数,函数返回什么,就把它插到宏调用的位置。Hygienic 宏体现的是相反的原则。它们试图保护你不去理解它们在干什么。我从没听过有人能用一句话讲清 hygienic 宏。它们是“擅自决定程序员可以想要什么“这种危险做法的经典案例。Hygienic 宏据说是要保护我远离变量捕获之类的问题,可我有些宏要的就是变量捕获。

一门真正好的语言应该既干净又脏:干净在设计上——一小撮被透彻理解、高度正交的操作符;脏在它让黑客可以为所欲为。C 就是这样。早期的 Lisp 也是。一门真正属于黑客的语言总会带点放荡不羁的气质。

一门好的编程语言应该有一些会让那些张口闭口“软件工程“的人摇头叹气的特性。在这个谱的另一端是 Ada 和 Pascal 这样的语言——端庄得体的范本,适合教学,别的没什么用。

5 一次性程序

要吸引黑客,一门语言必须适合写他们想写的那种程序。这意味着——也许有点出乎意料——它必须适合写一次性程序。

一次性程序是你为了某项有限任务快速写出来的程序:自动化某个系统管理任务,给仿真生成测试数据,把数据从一种格式转成另一种。一次性程序有意思的地方在于,就像二战期间美国大学里建的那么多“临时“建筑一样,它们经常没被扔掉。许多会演化成真正的程序,长出真正的功能,吸引真正的用户。

我有种预感:最好的大型程序是这样起家的,而不是一开始就像胡佛大坝那样按“大“去设计。从零开始造个大东西很吓人。当人们接下一个过大的项目,他们会被压垮。要么项目陷入泥潭,要么结果是个不育的、僵硬的东西:一个购物中心而不是真正的市中心,巴西利亚而不是罗马,Ada 而不是 C。

另一种得到大程序的方法是从一次性程序起步,不断改进它。这条路没那么吓人,而且程序的设计会因演化而获益。我觉得,要是去看一下,会发现大多数大程序都是这么开发出来的。而那些这样演化出来的程序,多半还在用最初写它的那门语言写——因为程序很少会被移植,除了出于政治原因。所以,吊诡的是,要做一门用来写大系统的语言,你必须把它做成适合写一次性程序——因为大系统就是从那儿来的。

Perl 是这一点的鲜明例子。它不仅是为写一次性程序而设计的,它本身也差不多就是一个一次性程序。Perl 起源于一组生成报表的工具,只是因为人们用它写的一次性程序越长越大,它才进化成一门编程语言。直到 Perl 5(甚至要更晚),这门语言才适合写正经程序,可它那时已经大火了。

是什么让一门语言适合写一次性程序?首先,它必须随手可得。一次性程序是你打算一小时内写完的东西。所以这门语言大概必须已经装在你用的电脑上。它不能是用之前还要先去装的东西。它得就在那儿。C 就在那儿,因为它跟着操作系统一起来。Perl 就在那儿,因为它最初是给系统管理员用的工具,你的管理员已经把它装上了。

不过“随手可得“不只是“装好了“。一门带命令行界面的交互式语言,比一门要先编译再运行的语言更“随手“。一门流行的编程语言应该是交互式的,启动要快。

一次性程序还需要简洁。简洁对黑客一直有吸引力,尤其是写一个他们想一小时内搞定的程序时。

6 库

当然,简洁的极致是程序已经替你写好了,你只要调用就行。这就引出了我认为编程语言越来越重要的一项特性:库函数。Perl 之所以赢,是因为它有大量操作字符串的库。这一类库函数对一次性程序尤其重要——一次性程序往往最初是为转换或抽取数据写的。许多 Perl 程序大概一开始就是几个库调用拼在一起。

我觉得未来五十年里,编程语言上的不少进步会发生在库函数上。我觉得未来的编程语言会有跟核心语言一样精心设计的库。语言设计将不再是关于“要做强类型还是弱类型,要不要面向对象、函数式之类“,而是关于如何设计出色的库。那些喜欢琢磨怎么设计类型系统的语言设计者大概会打哆嗦。这几乎就是在写应用了!没办法。语言是给程序员用的,库才是程序员需要的。

设计好的库很难。这不只是写一大堆代码的事。库一旦太大,找你要的那个函数有时比自己写还慢。库需要用一小撮正交的操作符来设计,跟核心语言一样。程序员要能猜得到哪个库调用会做他想做的事。

库是 Common Lisp 短板之一。它操作字符串的库只是雏形,跟操作系统对话的库几乎没有。出于历史原因,Common Lisp 假装操作系统不存在。而既然你没法跟操作系统对话,光靠 Common Lisp 内置的操作符你不太可能写出一个正经程序。你还得用一些特定实现的小把戏,实践中这些把戏又往往给不了你所有想要的。如果 Common Lisp 有强大的字符串库和好用的操作系统支持,黑客对 Lisp 的评价会高得多。

7 语法

一门有 Lisp 这种语法——或者更准确说,没有语法——的语言,有可能流行起来吗?这个问题我没答案。但我确实觉得,语法不是 Lisp 现在不流行的主要原因。Common Lisp 的问题比“语法不熟悉“更严重。我认识好几位习惯前缀语法、却默认用 Perl 的程序员——因为 Perl 有强大的字符串库,能跟操作系统对话。

前缀表示法可能有两个问题:程序员不熟悉,以及不够紧凑。Lisp 圈的传统看法认为前面那个才是真问题。我不太确定。是的,前缀表示法会让普通程序员慌。但我觉得普通程序员的意见不重要。语言流行与否,取决于专家级黑客怎么看——而我觉得专家级黑客应付得了前缀表示法。Perl 语法可以晦涩到几乎无法理解,但这没挡住 Perl 流行。如果说有什么影响的话,可能反而帮它培养了某种邪教般的拥趸。

更严重的问题是前缀表示法的松散。对专家级黑客来说,那才是真问题。没人愿意写 (aref a x y) 当他可以写 a[x,y]

在这个例子上,有办法绕开问题。如果我们把数据结构当作下标上的函数,就可以写成 (a x y),比 Perl 那种形式还短。类似的小技巧也许能缩短其他类型的表达式。

我们可以让缩进有意义,从而消去(或者让其变成可选)大量括号。反正程序员就是这样读代码的:当缩进说一回事、定界符说另一回事时,我们都按缩进走。把缩进当作有意义的东西,能消除这一类常见 bug 的来源,同时让程序更短。

有时中缀语法更易读。数学表达式尤其如此。我一辈子用 Lisp 写程序,到现在还是觉得前缀的数学表达式不自然。可让操作符接受任意多个参数也很方便,特别是在生成代码的时候。所以如果我们要有中缀语法,它大概应该作为某种 read-macro 来实现。

我觉得我们不该从信念上反对给 Lisp 引入语法,只要它能以某种被透彻理解的方式翻译回底层的 s 表达式。Lisp 里其实已经有不少语法。再多引入一些不一定是坏事,只要没人被强迫用。在 Common Lisp 里,有些定界符是给语言本身保留的——这意味着至少有些设计者打算将来再加点语法。

Common Lisp 里最不像 Lisp 的一段语法出现在格式化字符串里;format 本身就是一门独立的语言,而且这门语言不是 Lisp。如果有计划给 Lisp 引入更多语法,format 说明符也许可以纳入其中。要是宏能像生成其他代码那样去生成 format 说明符,那是一件好事。

一位著名的 Lisp 黑客告诉我,他那本 CLTL 翻开来总是停在 format 那一节。我那本也是。这大概说明这块有改进空间。也可能意味着程序在做大量 I/O。

8 效率

谁都知道,一门好语言应该生成快的代码。但实践中我不觉得快代码主要来自语言设计层面的东西。Knuth 早就指出,速度只在某些关键瓶颈处才重要。后来又有许多程序员观察到:人对这些瓶颈在哪里,往往判断错。

所以实践中,得到快代码的办法是有一个非常好的 profiler(性能剖析器),而不是把语言做成强类型之类。你不需要知道程序里每一个调用、每一个参数的类型。你需要的是能够在瓶颈处声明参数的类型。更进一步,你需要的是能够找出瓶颈在哪里。

人们对 Lisp 的一个抱怨是,很难看出什么操作“贵“。这也许是真的。如果你想要一门非常抽象的语言,这或许也无法避免。无论如何,我觉得好的 profiler 大体能解决这个问题:你很快就会知道什么贵。

这里部分是社会层面的问题。语言设计者喜欢写快的编译器。那是他们衡量自己技艺的方式。他们顶多把 profiler 当作附加品。但实践中,一个好的 profiler 对实际程序速度的提升可能比一个能生成快代码的编译器还大。在这点上,语言设计者多少跟用户脱节了。他们对一个“略微错的问题“做出了非常出色的解决方案。

也许应该有“主动 profiler“——把性能数据推给程序员,而不是等他来问。比如,编辑器在程序员编辑源代码时,可以把瓶颈用红色标出来。另一种思路是以某种方式把正在运行的程序里发生的事情可视化。这在服务端应用里特别有用——你有一大堆运行中的程序要看。一个主动 profiler 可以图形化展示程序运行时内存里在发生什么,甚至发出声音告诉你在发生什么。

声音是发现问题的好线索。我以前工作的一个地方,有一面大表盘,显示我们 Web 服务器的状态。指针由小伺服电机驱动,转动时有轻微的声响。我从座位上看不到那面板,但发现自己只凭声音就能立刻判断某台服务器是不是出了问题。

也许甚至能写一个能自动检测低效算法的 profiler。某些内存访问模式被证明是坏算法的可靠信号——这一点都不会让我意外。如果计算机里真有个小人在跑我们的程序,他对自己工作的怨念故事大概会跟联邦政府雇员一样又长又凄惨。我经常觉得自己派处理器去追了一堆野鹅,可我从来没有一个好办法去看它到底在做什么。

现在好几个 Lisp 编译成字节码,然后由解释器执行。这通常是为了让实现更易移植,但也可以是个有用的语言特性。也许把字节码作为语言的官方部分、并允许程序员在瓶颈处用内联字节码,是个好主意。这样的优化也就有可移植性了。

终端用户感知到的“速度“的本质,可能正在变化。随着服务端应用兴起,越来越多的程序可能成为 I/O 受限的。把 I/O 做快会很值得。语言可以在简单层面帮忙——简洁、快速的格式化输出函数;也可以在深层结构上帮忙——缓存和持久化对象。

用户关心响应时间。但另一种效率会越来越重要:每个处理器能支撑多少并发用户。未来几年要写的不少有趣应用都是服务端的,而每台服务器能容纳多少用户,对任何托管这类应用的人来说都是关键问题。在一个提供服务端应用的公司的资本成本里,这是分母。

多年来,效率在多数终端用户应用里都不太重要。开发者一直可以假定每个用户桌上都摆着一颗越来越强大的处理器。按帕金森定律,软件膨胀着把可用资源吃满。服务端应用会改变这一切。在那个世界里,硬件和软件是一起供应的。对提供服务端应用的公司来说,每台服务器能撑多少用户,会对底线产生很大影响。

某些应用里,处理器是限制因素,执行速度是最重要的优化目标。但很多时候内存才是限制;并发用户数取决于每位用户的数据需要多少内存。语言在这里也能帮忙。良好的线程支持能让所有用户共享单个堆。持久化对象,或者语言层面对惰性加载的支持,也可能有帮助。

9 时间

一门流行的语言需要的最后一样东西是时间。没人愿意用一门可能消失的语言写程序——而那么多编程语言都消失了。所以多数黑客倾向于在一门语言出现几年之后,才会考虑要不要用它。

发明了美妙新事物的人常常惊讶地发现:让消息传到人那里需要时间。我有一位朋友,别人第一次找他做事时他几乎不做。他知道人们有时要的东西最后并不真的想要。为了不浪费自己的时间,他要等到第三次或第四次被请求时再动手;那时来求他的人也许已经相当烦躁,但至少他们大概是真的想要那样东西。

大多数人对听到的新东西也学会了类似的过滤。他们要等到听到某样东西十次了,才开始留意。他们这样做完全合理:那些热门的“新某某“大多确实证明是浪费时间,最后也消失了。靠拖延去学 VRML,我成功躲过了根本不必学它。

所以发明新东西的人必须做好心理准备:要重复传达自己的消息好几年,人才会开始懂。我们写了——据我所知——第一个基于 Web 服务器的应用,花了好几年才让人明白它不需要下载。倒不是他们笨。只是他们已经把我们的频道屏蔽掉了。

好消息是,简单地重复就能解决问题。你只要不停讲你的故事,最终人就会开始听。让人留意的,不是他们注意到你“在那儿“——而是他们注意到你“还在那儿“。

通常需要一段时间才能形成势头,这其实正合适。多数技术在首次发布之后还会演化很多——编程语言尤其如此。对一项新技术来说,没有比“前几年只被一小批早期采用者使用“更好的事情了。早期采用者老练而挑剔,会很快把你的技术里残存的缺陷暴露出来。当你只有少数用户时,你能跟他们每个人保持密切联系。而早期采用者在你改进系统时是宽容的,哪怕这改进会破坏一些东西。

新技术被引入有两种方式:有机生长法,和大爆炸法。有机生长法的典型是那种典型的、靠裤兜里仅有几块钱的车库创业。两个家伙默默无闻地在角落里搞出某项新技术。他们没有营销地把它发布,最初只有极少数(狂热忠诚的)用户。他们持续改进技术,与此同时用户群靠口碑增长。他们还没反应过来,自己已经做大了。

另一条路,大爆炸法的典型是 VC 撑腰、重金营销的创业公司。他们赶着开发产品,伴随大量公关把它发布,并且立刻(他们希望)拥有庞大的用户群。

通常车库小子羡慕大爆炸小子。大爆炸小子谈吐流畅、自信、被 VC 尊重。他们买得起最好的一切,发布前后的公关攻势把他们捧成了名人。有机生长的那帮人,蹲在自家车库里,觉得自己又穷又没人爱。可我觉得他们这样自怜往往是搞错了。有机生长法似乎能产出比大爆炸法更好的技术,也能造就更富的创始人。如果你看一看今天主导一切的技术,会发现大多数是有机生长起来的。

这个规律不只适用于公司。资助的研究里也能看到。Multics 和 Common Lisp 是大爆炸项目,Unix 和 MacLisp 是有机生长项目。

10 重新设计

“最好的写作是改写。“E. B. White 写过。每个好作家都明白这一点,对软件也一样。设计中最重要的部分是重新设计。编程语言尤其重新设计得不够。

要写好软件,你必须同时在脑子里装着两个对立的想法。你需要年轻黑客对自己能力的天真自信,同时又需要老兵的怀疑。你得能用半个脑子想“这能有多难?“,同时用另半个脑子想“这永远不会成”。

诀窍是意识到这里其实没有矛盾。你是在对两件不同的事情分别保持乐观和怀疑。你必须对“问题能被解决“这件事乐观,同时对“目前手上这个解决方案的价值“保持怀疑。

做出好东西的人经常觉得自己手头的东西都不行。别人看着他们做的东西满是惊叹,而创作者满是焦虑。这种模式不是巧合:正是焦虑让作品变好。

如果你能让希望和焦虑保持平衡,它们会像你的双腿驱动自行车那样推动一个项目向前。在这台两冲程创新引擎的第一相里,你被“自己能解决“的信心点燃,疯狂地干。第二相里,你在清晨冷光下看自己做的东西,所有缺陷一目了然。但只要你的批评精神没盖过希望,你就还能看着这个公认不完整的系统、想“剩下的路能有多难走?“,从而让循环继续。

把这两股力量平衡好不容易。年轻黑客身上乐观占主导。他们做出点东西,觉得很伟大,从此不再改进它。老黑客身上怀疑占主导,他们甚至不敢再接野心大的项目。

任何能让重新设计循环转起来的事都是好的。散文可以一遍遍重写直到你满意。但软件,一般来说,重新设计得不够。散文有读者,软件有用户。如果作家改写一篇文章,读过旧版的人不太可能抱怨他们的思路被新引入的“不兼容“打断。

用户是双刃剑。他们能帮你改进语言,也能阻止你改进它。所以小心选你的用户,慢慢扩大他们的数量。有用户就像做优化:明智的做法是推迟。还有,作为一般规则,任何时候你能改的都比你以为的多。引入变化就像撕掉一块创可贴:痛感几乎是你刚感觉到就成了记忆。

谁都知道一门语言不该由委员会设计。委员会只会出坏设计。但我觉得委员会最大的危险是它们妨碍重新设计。引入变化要花太多功夫,于是没人愿意去折腾。委员会决定的事,往往就那样定下来——哪怕大多数成员都不喜欢。

哪怕只有两个人的“委员会“,也会挡住重新设计。这种情况尤其容易出现在两个不同的人写的两块软件之间的接口上。要改接口,两人得同时同意改。于是接口往往一动不动——这是个问题,因为接口往往是任何系统里最临时凑合的部分之一。

这里也许的一个解法是把系统设计成接口横向的、而不是纵向的——让模块永远是一层一层纵向堆叠的抽象。这样接口就倾向于属于其中一方。两层之间,下层要么是一门语言、上层是用它写出来的,那这接口归下层;要么下层是仆人,那接口由上层说了算。

11 Lisp

这一切意味着:一门新的 Lisp 是有希望的。任何能给黑客他们想要的东西的语言都有希望,包括 Lisp。我觉得我们也许搞错了——以为黑客是被 Lisp 的怪异吓跑的。这个让人安心的错觉,可能让我们看不到 Lisp、或至少 Common Lisp,真正的问题是:它在做黑客想做的事这件事上很烂。一门黑客的语言需要强大的库和可以 hack 的对象。Common Lisp 两样都没有。一门黑客的语言要简洁、要可 hack。Common Lisp 不是。

好消息是,烂的不是 Lisp,而是 Common Lisp。如果我们能开发一门真正属于黑客的新 Lisp,我觉得黑客会用它。他们会用任何能把活干完的语言。我们要做的就是确保这门新 Lisp 在某个重要任务上比别的语言做得更好。

历史给了一些鼓励。一代代新出的编程语言从 Lisp 那里抄走了越来越多的特性。在你做出来的语言变成 Lisp 之前,可抄的已经不多了。最新的热门语言 Python,是一个有中缀语法、没有宏的、被稀释过的 Lisp。一门新 Lisp 在这个进程中是个自然的下一步。

我有时觉得,把它包装成“Python 的改进版“会是个不错的营销噱头。那听起来比 Lisp 更潮。在很多人看来,Lisp 是一门慢吞吞、括号一大堆的 AI 语言。Fritz Kunze 的官方个人简介小心地避开“L 字头那个词“。但我猜,我们不必怕把这门新 Lisp 叫作 Lisp。Lisp 在最顶尖的黑客中间——比如那些上过 6.001 并且听懂了的人——还有大量潜在的敬意。而那才是你需要争取的用户。

在《How to Become a Hacker》中,Eric Raymond 把 Lisp 描述成像拉丁语或希腊语一样的东西——一门你应该作为智力训练去学、但实际上不会用的语言:

学 Lisp 是值得的,因为当你最终参透它时,会有一种深刻的开悟体验;那段体验会让你余生都成为更好的程序员,哪怕你后来从不真用 Lisp。

如果我不懂 Lisp,读这段话会引出我的一连串疑问。一门会让我成为更好程序员的语言,如果这话有任何意义,那意思就是:一门更好的、用来写程序的语言。事实上,Eric 的言下之意正是这个。

只要这种想法还在流通,我就觉得黑客会对一门新 Lisp 足够开放——哪怕它叫 Lisp。但这门 Lisp 必须是黑客的语言,像 1970 年代那些经典 Lisp 一样。它必须简洁、简单、可 hack。还得有强大的库,去做黑客今天想做的事。

在库这件事上,我觉得有空间在 Perl 和 Python 自己的赛道上击败它们。未来几年要写的很多新应用都是服务端应用。一门新 Lisp 没有理由不能拥有跟 Perl 一样好的字符串库,而且如果它还有为服务端应用准备的强大库,它可能会非常流行。真正的黑客不会对一门“几个库调用就能解决难题“的新工具嗤之以鼻。别忘了,黑客很懒。

为服务端应用提供核心语言层面的支持,可能是更大的胜利。比如,对多用户程序的显式支持,或者类型标签级别的数据所有权。

服务端应用也回答了一个问题:这门新 Lisp 用来 hack 什么?让 Lisp 成为 Unix 更好的脚本语言不会有坏处。(再差也很难。)但我觉得有些领域里更容易击败现有语言。我觉得也许最好走 Tcl 的路子:把 Lisp 跟一整套支持服务端应用的系统一起提供。Lisp 天然适合服务端应用。当 UI 只是一连串网页时,词法闭包提供了一种实现“子程序“效果的方法。S 表达式很好地映射到 HTML 上,宏也擅长生成 HTML。需要更好的工具来写服务端应用,也需要一门新 Lisp,这两件事可以配合得非常好。

12 梦想语言

作为总结,我们试着描述一下黑客的梦想语言。这门梦想语言是漂亮的、干净的、简洁的。它有一个启动很快的交互式顶层。你能用极少的代码写出解决常见问题的程序。你写的任何程序里,几乎所有代码都是你这个应用特有的代码。其他一切都已经替你做好了。

这门语言的语法简到极致。你永远不需要敲一个多余的字符,甚至几乎不用按 Shift 键。

借助大型抽象,你能很快写出程序的第一个版本。后来想优化时,有一个非常好的 profiler 告诉你该把注意力放在哪里。你能让内层循环快得让人睁不开眼,需要时甚至可以写内联字节码。

有大量好例子可学,语言本身够直观,几分钟就能从例子里学会怎么用。你不需要常翻手册。手册很薄,警告和限定条件很少。

这门语言有一个很小的核心,再加上强大的、高度正交的库,库的设计跟核心语言一样精心。所有库彼此配合得很好;语言里的一切像精密相机的零件那样严丝合缝地组装起来。没有任何东西是被废弃的,或为兼容性留下来的。所有库的源码都随手可得。跟操作系统、跟用其他语言写的应用对话都很容易。

这门语言是分层构建的。更高层的抽象以非常透明的方式由更低层的抽象搭起来——而你想要时,可以伸手抓到那些低层。

凡是不绝对必须对你隐藏的,都不对你隐藏。这门语言提供抽象只是为了帮你省事,而不是为了告诉你该做什么。事实上,这门语言鼓励你成为它设计的平等参与者。你可以改变它的一切,包括它的语法,并且你写出来的任何东西,尽可能地享有跟语言预定义部分同等的地位。

注释

[1] 现代意义上的宏概念,由 Timothy Hart(1964 年提出宏概念)于 1964 年提出,时间是 Lisp 1.5 发布两年之后。最初缺的是回避变量捕获和多重求值的方法;Hart 给的例子两种问题都有。

[2] 在《When the Air Hits Your Brain》(神经外科医师 Frank Vertosick 的回忆录)中,神经外科医师 Frank Vertosick 复述了他和总住院医 Gary 的一段对话——Gary 在谈外科医生和内科医生(外科医生把内科医生贬称为“跳蚤“)的差别:

Gary 和我点了一个大披萨,找了张空桌坐下。这位总住院医点上一根烟。“看那帮该死的跳蚤,叽叽喳喳谈论一种他们一辈子可能只会见到一次的病。这就是跳蚤的问题,他们只爱稀奇古怪的玩意儿。他们讨厌那些常规的、安身立命的病例。这就是我们和那群该死的跳蚤的区别。看,我们爱多汁的腰椎间盘突出,他们却讨厌高血压……”

很难把腰椎间盘突出想成“多汁“的(除了字面意义上)。但我觉得我大致明白他们的意思。我经常会有一只多汁的 bug 要追。一个不写程序的人会很难想象 bug 里能有什么乐趣。万事正常运转不是更好吗?某种意义上,是的。可在追捕某些 bug 时,那种冷峻的满足感是无法否认的。