English Version

如何变得流行

2001年5月

(本文是作为一种新语言的商业计划书而写的。因此它缺少(因为想当然)一个好的编程语言最重要的特征:非常强大的抽象能力。)

我有一个朋友曾经告诉一位著名的操作系统专家,他想设计一种真正好的编程语言。专家告诉他,这将是浪费时间,编程语言不会因为其优点而变得流行或不流行,所以无论他的语言有多好,都没人会使用。至少,这是他设计的语言所发生的情况。

到底是什么让一种语言变得流行?流行的语言是否配得上它们的流行度?试图定义一个好的编程语言是否值得?你会怎么做?

我认为这些问题的答案可以通过观察黑客并了解他们想要什么来找到。编程语言是为黑客而设计的,而编程语言作为编程语言(而不是,比如说,作为指称语义练习或编译器设计练习)是好的,当且仅当黑客喜欢它。

1 流行的机制

当然,大多数人在选择编程语言时并不仅仅基于它们的优点。大多数程序员被告知要使用什么语言。然而我认为这些外部因素对编程语言流行度的影响并不像人们有时认为的那么大。我认为更大的问题是黑客对好的编程语言的想法与大多数语言设计者的想法不同。

在这两者之间,黑客的意见才是重要的。编程语言不是定理。它们是工具,为人们设计的,必须像鞋子必须为人类的脚设计一样,适合人类的优点和缺点。如果鞋子穿上时夹脚,那它就是一双坏鞋,无论它作为雕塑品多么优雅。

可能是大多数程序员无法区分好语言和坏语言。但这与其他工具没有什么不同。这并不意味着尝试设计好语言是浪费时间。专家黑客在看到好语言时能够识别出来,他们会使用它。诚然,专家黑客是极少数,但这个极少数群体编写了所有好的软件,他们的影响力使得其他程序员倾向于使用他们使用的任何语言。通常,这不仅仅是影响,而是命令:专家黑客通常就是那些作为老板或导师告诉其他程序员使用什么语言的人。

专家黑客的意见并不是决定编程语言相对流行度的唯一力量——遗留软件(Cobol)和炒作(Ada、Java)也起作用——但我认为它是长期内最强大的力量。考虑到初始的关键质量和足够的时间,编程语言可能会变得与其应得的流行度相当。而流行度进一步将好语言与坏语言分开,因为来自真实用户的反馈总是导致改进。看看任何流行语言在其生命周期中发生了多大变化。Perl和Fortran是极端的例子,但即使是Lisp也发生了很大变化。例如,Lisp 1.5没有宏;这些是后来发展的,在麻省理工学院的黑客花了几年时间使用Lisp编写真实程序之后。[1]

所以无论一种语言是否必须好才能流行,我认为一种语言必须流行才能好。它必须保持流行才能保持好。编程语言的工艺水平不会停滞不前。然而我们今天拥有的Lisp几乎与麻省理工学院在1980年代中期的Lisp相同,因为那是Lisp最后一次拥有足够大和要求高的用户群的时候。

当然,黑客在使用语言之前必须了解它。他们如何听到?从其他黑客那里。但必须有一些初始的黑客群体使用该语言,其他人才能听到。我想知道这个群体必须多大;多少用户构成关键质量?我随口一说,二十个。如果一种语言有二十个独立的用户,意味着二十个用户自己决定使用它,我会认为它是真实的。

达到那里并不容易。如果从零到二十比从二十到一千更难,我不会感到惊讶。获得那初始二十个用户的最好方法可能是使用特洛伊木马:给人们一个他们想要的应用程序,而这个应用程序恰好是用新语言编写的。

2 外部因素

让我们首先承认一个确实影响编程语言流行度的外部因素。要变得流行,编程语言必须是流行系统的脚本语言。Fortran和Cobol是早期IBM大型机的脚本语言。C是Unix的脚本语言,后来Perl也是。Tcl是Tk的脚本语言。Java和Javascript旨在成为Web浏览器的脚本语言。

Lisp不是一个大规模流行的语言,因为它不是大规模流行系统的脚本语言。它保留的流行度可以追溯到1960年代和1970年代,当时它是麻省理工学院的脚本语言。当时的许多伟大程序员都在某个时期与麻省理工学院有关联。在1970年代初期,在C之前,麻省理工学院的Lisp方言MacLisp是严肃黑客想要使用的唯一编程语言之一。

今天,Lisp是两个适度流行系统的脚本语言,Emacs和Autocad,因此我怀疑今天大部分Lisp编程都是在Emacs Lisp或AutoLisp中完成的。

编程语言不是孤立存在的。Hack是一个及物动词——黑客通常在hack某物——在实践中,语言是相对于它们用来hack的任何东西来评判的。所以如果你想设计一种流行的语言,你要么必须提供比语言更多的东西,要么你必须设计你的语言来取代某个现有系统的脚本语言。

Common Lisp不受欢迎的部分原因是因为它是一个孤儿。它确实伴随着一个要hack的系统:Lisp Machine。但Lisp Machines(以及并行计算机)在1980年代被通用处理器日益增长的力量压垮了。如果Common Lisp是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中,我经常想要迭代结构的字段——例如,梳理对已删除对象的引用,或找到未初始化的字段。我知道结构在底层只是向量。然而我无法编写一个可以在任何结构上调用的通用函数。我只能通过名称访问字段,因为这就是结构应该意味着的意思。

黑客可能只想在一个大程序中一两次颠覆事物的预期模型。但能够这样做会产生多大的不同。这可能不仅仅是解决问题的问题。这里也有一种乐趣。黑客分享外科医生秘密的探索内脏的乐趣,青少年秘密的挤痘痘的乐趣。[2] 至少对于男孩来说,某些类型的恐怖是迷人的。Maxim杂志每年出版一卷照片,包含美女写真和可怕事故的混合。他们了解他们的观众。

从历史上看,Lisp在让黑客为所欲为方面一直很好。Common Lisp的政治正确性是一种异常。早期的Lisp让你可以接触到一切。幸运的是,这种精神的大部分在宏中得以保留。能够对源代码进行任意转换,这是一件多么美妙的事情。

经典的宏是真正的黑客工具——简单、强大和危险。理解它们的作用是如此简单:你调用一个函数在宏的参数上,无论它返回什么都被插入到宏调用的地方。卫生宏体现了相反的原则。它们试图保护你理解它们在做什么。我从未听过卫生宏用一句话解释过。它们是决定程序员被允许想要什么危险的经典例子。卫生宏旨在保护我免受变量捕获等问题的影响,但变量捕获正是在某些宏中我想要的东西。

真正好的语言应该是既干净又肮脏的:干净地设计,具有少量良好理解和高度正交的操作符核心,但肮脏在它让黑客为所欲为的意义上。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,因为它有强大的字符串库并且可以与操作系统对话。

前缀记号有两个可能的问题:它对程序员来说不熟悉,而且它不够密集。Lisp世界中的传统观点是第一个问题是真正的问题。我不太确定。是的,前缀记号让普通程序员恐慌。但我不认为普通程序员的意见重要。语言变得流行或不流行是基于专家黑客对它们的看法,我认为专家黑客可能能够处理前缀记号。Perl语法可能非常难以理解,但这并没有阻碍Perl的流行。如果有的话,它可能帮助培育了Perl崇拜。

更严重的问题是前缀记号的扩散性。对于专家黑客来说,这确实是一个问题。没有人想写(aref a x y)当他们可以写a[x,y]时。

在这个特定情况下,有一种方法可以巧妙地解决问题。如果我们将数据结构视为索引上的函数,我们可以写(a x y)而不是,这甚至比Perl形式更短。类似的技巧可能会缩短其他类型的表达式。

我们可以通过使缩进有意义来摆脱(或使可选)很多括号。这就是程序员无论如何阅读代码的方式:当缩进说一件事而分隔符说另一件事时,我们遵循缩进。将缩进视为有意义将消除这种常见的错误来源,同时也使程序更短。

有时中缀语法更容易阅读。对于数学表达式尤其如此。我整个编程生涯都在使用Lisp,但我仍然不觉得前缀数学表达式自然。然而,能够接受任意数量参数的操作符是方便的,尤其是在生成代码时。所以如果我们确实有中缀语法,它可能应该作为某种读取宏来实现。

我不认为我们应该宗教上反对在Lisp中引入语法,只要它以良好理解的方式转换为底层的s-表达式。Lisp中已经有相当多的语法。引入更多并不一定是坏事,只要没有人被迫使用它。在Common Lisp中,一些分隔符为语言保留,表明至少一些设计者打算将来有更多语法。

Common Lisp中最不像Lisp的语法片段之一出现在格式字符串中;format本身就是一种语言,而那种语言不是Lisp。如果有在Lisp中引入更多语法的计划,格式说明符可能可以包括在内。如果宏能够像生成任何其他类型的代码一样生成格式说明符,那将是一件好事。

一位著名的Lisp黑客告诉我,他的CLTL副本打开在format部分。我的也是。这可能表明有改进的空间。这也可能意味着程序做大量的I/O。

8 效率

众所周知,好的语言应该生成快速的代码。但在实践中,我不认为快速的代码主要来自语言设计中的事情。正如Knuth很久以前指出的,速度只在某些关键瓶颈中重要。正如许多程序员从那时起观察到的,人们经常误判这些瓶颈在哪里。

所以,在实践中,获得快速代码的方法是拥有非常好的分析器,而不是通过,比如说,使语言强类型。你不需要知道程序中每个调用中每个参数的类型。你确实需要能够声明瓶颈中参数的类型。更重要的是,你需要能够找出瓶颈在哪里。

人们对Lisp的一个抱怨是很难判断什么是昂贵的。这可能是真的。如果你想要一个非常抽象的语言,这也可能是不可避免的。无论如何,我认为好的分析器会大大有助于解决这个问题:你会很快学到什么是昂贵的。

这里的问题部分是社会性的。语言设计师喜欢编写快速的编译器。这就是他们衡量自己技能的方式。他们认为分析器最多只是一个附加组件。但在实践中,好的分析器可能比生成快速代码的编译器更能提高用该语言编写的实际程序的速度。在这里,语言设计师再次与他们的用户有些脱节。他们在解决稍微错误的问题上做得非常好。

拥有主动分析器可能是个好主意——将性能数据推送给程序员,而不是等待他来询问。例如,当程序员编辑源代码时,编辑器可以用红色显示瓶颈。另一种方法是以某种方式表示正在运行的程序中发生的事情。这在基于服务器的应用程序中将是一个特别大的胜利,在那里你有很多正在运行的程序可以查看。主动分析器可以图形化地显示程序运行时内存中发生的事情,甚至发出告诉正在发生什么的声音。

声音是对问题的很好提示。我曾经工作过的一个地方,我们有一个大的仪表板显示我们的Web服务器正在发生什么。指针由小型伺服电机移动,它们在转动时会发出轻微的噪音。我从我的办公桌看不到仪表板,但我发现我能够立即通过声音判断服务器何时有问题。

甚至可能可以编写一个能自动检测低效算法的分析器。如果某些内存访问模式被证明是坏算法的可靠迹象,我不会感到惊讶。如果有一个小家伙在计算机内部执行我们的程序,他可能会像联邦政府雇员一样对他的工作有同样长而悲伤的故事。我经常有一种感觉,我正在发送处理器进行许多徒劳的追逐,但我从未有过好的方法来看看它在做什么。

许多Lisp现在编译成字节码,然后由解释器执行。这通常是为了使实现更容易移植,但它可能是一个有用的语言特性。将字节码作为语言的官方部分可能是个好主意,并允许程序员在瓶颈中使用内联字节码。那么这样的优化也将是可移植的。

最终用户感知的速度性质可能正在改变。随着基于服务器的应用程序的兴起,越来越多的程序可能证明是I/O绑定的。使I/O快速将是值得的。语言可以通过简单直接的措施如简单、快速、格式化的输出函数来帮助,也可以通过缓存和持久对象等深刻的结构变化来帮助。

用户对响应时间感兴趣。但另一种效率将变得越来越重要:每个处理器可以支持的并发用户数量。在不久的将来编写的许多有趣应用程序将是基于服务器的,每台服务器的用户数量是任何托管此类应用程序的人的关键问题。在提供基于服务器应用程序的企业的资本成本中,这是除数。

多年来,在大多数最终用户应用程序中效率并不重要。开发人员能够假设每个用户将在他们的办公桌上拥有一个越来越强大的处理器。根据帕金森定律,软件已经扩展到使用可用资源。随着基于服务器的应用程序,这将改变。在那个世界里,硬件和软件将一起提供。对于提供基于服务器应用程序的公司来说,他们每台服务器能够支持多少用户将对底线产生非常大的影响。

在某些应用程序中,处理器将是限制因素,执行速度将是最重要的优化事项。但通常内存将是限制;并发用户数量将取决于每个用户数据所需的内存量。语言在这里也可以帮助。对线程的良好支持将使所有用户能够共享单个堆。拥有持久对象和/或语言级别的惰性加载支持也可能有帮助。

9 时间

流行语言需要的最后一个成分是时间。没有人想用一种可能消失的语言编写程序,正如许多编程语言那样。所以大多数黑客倾向于等待一种语言存在几年后才考虑使用它。

奇妙新事物的发明者经常惊讶地发现这一点,但你需要时间让人们理解任何信息。我的朋友很少在第一次有人要求他做事时就做。他知道人们有时要求的东西他们最终并不想要。为了避免浪费时间,他等到第三次或第四次有人要求他做事时;到那时,要求他的人可能相当恼火,但至少他们可能真的想要他们所要求的东西。

大多数人对他们听到的新事物学会了做类似的过滤。直到他们听到某事十次后他们才开始注意。他们是完全合理的:大多数热门的新事物最终证明是浪费时间,最终会消失。通过延迟学习VRML,我完全避免了学习它。

所以任何发明新事物的人必须期望多年来不断重复他们的信息,人们才会开始理解。我们编写了,据我所知,第一个基于Web服务器的应用程序,我们花了多年时间才让人们理解它不必被下载。不是他们愚蠢。他们只是把我们调出去了。

好消息是,简单的重复解决了问题。你所要做的就是继续讲述你的故事,最终人们会开始听到。重要的不是人们注意到你在那里,而是他们注意到你仍然在那里。

通常需要一段时间才能获得动力,这也是好事。大多数技术在首次推出后会进化很多——编程语言尤其如此。对于新技术来说,没有什么比几年内只被少数早期采用者使用更好的了。早期采用者是复杂和挑剔的,并迅速冲洗掉你的技术中仍然存在的任何缺陷。当你只有少数用户时,你可以与所有他们保持密切联系。当你改进你的系统时,即使这造成了一些破坏,早期采用者也会原谅。

有两种引入新技术的方法:有机增长方法和大爆炸方法。有机增长方法以经典的、资金不足的车库创业公司为代表。几个人,在默默无闻中工作,开发一些新技术。他们没有营销就推出它,最初只有少数(狂热的)用户。他们继续改进技术,同时他们的用户群通过口碑增长。在他们意识到之前,他们已经变大了。

另一种方法,大爆炸方法,以风险投资支持的、大量营销的创业公司为代表。他们匆忙开发产品,以极大的宣传推出,并立即(他们希望)拥有大量用户群。

通常,车库的人羡慕大爆炸的人。大爆炸的人是圆滑、自信和受风险投资家尊重的。他们能负担得起最好的东西,围绕发布的宣传活动使他们成为名人也有副作用。有机增长的人,坐在他们的车库里,感到贫穷和不被爱。然而我认为他们经常错误地为自己感到难过。有机增长似乎比大爆炸方法产生更好的技术和更富有的创始人。如果你看看今天的主导技术,你会发现大多数都是有机增长的。

这种模式不仅适用于公司。你在赞助研究中也看到它。Multics和Common Lisp是大爆炸项目,Unix和MacLisp是有机增长项目。

10 重新设计

“最好的写作是重写,“E.B.怀特写道。每个好作家都知道这一点,这对软件也是如此。设计中最重要的部分是重新设计。编程语言尤其如此,没有足够地重新设计。

要编写好的软件,你必须同时在头脑中保持两个相反的想法。你需要年轻黑客对自己能力的天真信念,同时需要老手的怀疑。你必须能够用一半的大脑思考这能有多难?而另一半思考这将永远不会成功。

诀窍是认识到这里没有真正的矛盾。你想要对两件不同的事情乐观和怀疑。你必须对解决问题的可能性乐观,但对你到目前为止的任何解决方案的价值持怀疑态度。

做好工作的人经常认为他们正在做的事情不好。其他人看到他们所做的,充满惊奇,但创造者充满担忧。这种模式并非巧合:正是担忧使工作变好。

如果你能够保持希望和担忧平衡,它们将像你的双腿驱动自行车前进一样驱动项目前进。在双循环创新引擎的第一阶段,你受到你能够解决问题的信心的鼓舞,疯狂地工作于某个问题。在第二阶段,你在清晨的冷光中查看你所做的,非常清楚地看到它的所有缺陷。但只要你的批判精神不超过你的希望,你就能够看着你承认不完整的系统,并思考,剩下的路能有多难?从而继续循环。

保持两种力量平衡是棘手的。在年轻黑客中,乐观主义占主导。他们生产出一些东西,相信它很棒,从不改进它。在老黑客中,怀疑主义占主导,他们甚至不敢承担雄心勃勃的项目。

你能做任何事情来保持重新设计循环都是好的。散文可以一遍又一遍地重写,直到你满意为止。但作为规则,软件没有足够地重新设计。散文有读者,但软件有用户。如果作家重写一篇文章,阅读旧版本的人不太可能抱怨他们的思想被某些新引入的不兼容性破坏。

用户是一把双刃剑。他们可以帮助你改进你的语言,但他们也可以阻止你改进它。所以要小心选择你的用户,并缓慢增长他们的数量。拥有用户就像优化:明智的做法是延迟它。此外,作为一般规则,你在任何给定时间都可以改变比你认为更多的东西。引入变化就像撕掉创可贴:疼痛几乎在你感觉到它时就成为记忆。

每个人都知道由委员会设计语言不是一个好主意。委员会产生糟糕的设计。但我认为委员会最危险的危险是它们干扰重新设计。引入变化是如此多的工作,没有人愿意麻烦。委员会决定的任何事情往往会保持那样,即使大多数成员不喜欢它。

即使是两人的委员会也会妨碍重新设计。这尤其发生在由两个不同的人编写的软件片段之间的接口中。要改变接口,双方必须同意同时改变它。所以接口往往根本不改变,这是一个问题,因为它们往往是任何系统中最临时的部分之一。

这里的一个解决方案可能是设计系统,使接口是水平的而不是垂直的——这样模块总是垂直堆叠的抽象层。然后接口将倾向于由其中一个拥有。两个层次中较低的一个要么是较高层次编写的语言,在这种情况下较低层次将拥有接口,要么它是一个奴隶,在这种情况下接口可以由较高层次支配。

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-word。但我猜测我们不应该害怕称呼新的Lisp为Lisp。Lisp在最好的黑客中仍然有很多潜在的尊重——那些上过6.001并理解它的人,例如。而那些是你需要赢得的用户。

在《如何成为黑客》中,Eric Raymond将Lisp描述为类似拉丁语或希腊语的东西——你应该作为智力练习学习的语言,即使你不会实际使用它:

Lisp值得学习,因为当你最终理解它时你将获得的深刻启蒙体验;即使你实际上不会大量使用Lisp本身,这种体验也将使你在余下的日子里成为更好的程序员。

如果我不了解Lisp,阅读这个会让我提出问题。一种能让我成为更好程序员的语言,如果它意味着任何东西的话,意味着一种对编程更好的语言。而这实际上是Eric所说的话的含义。

只要那个想法仍然存在,我认为黑客会对新的Lisp足够接受,即使它被称为Lisp。但这个Lisp必须是黑客的语言,像1970年代的经典Lisp。它必须是简洁、简单和可hack的。它必须拥有强大的库来做黑客现在想要做的事情。

在库的问题上,我认为有空间在Perl和Python自己的游戏中击败它们。未来几年需要编写的许多新应用程序将是基于服务器的应用程序。新的Lisp没有理由不应该有与Perl一样好的字符串库,如果这个新Lisp也有用于基于服务器应用程序的强大库,它可能会非常流行。真正的黑客不会对能让他们用几个库调用解决困难问题的新工具嗤之以鼻。记住,黑客是懒惰的。

拥有对基于服务器应用程序的核心语言支持可能是一个更大的胜利。例如,对具有多用户程序的明确支持,或在类型标签级别的数据所有权。

基于服务器的应用程序也给了我们这个新Lisp将用来hack什么问题的答案。让Lisp成为Unix的更好脚本语言不会有什么伤害。(让它更糟将是困难的。)但我认为有些领域现有语言会更容易被击败。我认为遵循Tcl的模式可能更好,提供Lisp以及支持基于服务器应用程序的完整系统。Lisp自然适合基于服务器的应用程序。词法闭包提供了一种在ui只是一系列网页时获得子程序效果的方法。S-表达式很好地映射到html,而宏善于生成它。需要有更好的工具来编写基于服务器的应用程序,需要新的Lisp,两者将很好地协同工作。

12 梦想语言

作为总结,让我们尝试描述黑客的梦想语言。梦想语言是美丽、干净和简洁的。它有一个快速启动的交互式顶层。你可以用非常少的代码编写解决常见问题的程序。在你编写的任何程序中,几乎所有代码都是特定于你的应用程序的。其他一切都已为你完成。

语言的语法简洁得过分。你永远不必键入不必要的字符,甚至不必经常使用shift键。

使用大的抽象,你可以非常快速地编写程序的第一个版本。后来,当你想要优化时,有一个非常好的分析器告诉你在哪里集中注意力。你可以使内循环变得极快,如果需要甚至编写内联字节码。

有很多好的例子可以学习,语言足够直观,你可以从例子中在几分钟内学会如何使用它。你不需要经常看手册。手册是薄的,很少有警告和限定。

语言有一个小的核心,以及强大的、高度正交的库,这些库与核心语言一样精心设计。所有库都很好地协同工作;语言中的一切都像精密相机中的零件一样配合。没有东西被弃用,或为兼容性而保留。所有库的源代码都容易获得。与操作系统和其他语言编写的应用程序对话很容易。

语言是分层构建的。更高级别的抽象以非常透明的方式从较低级别的抽象构建,如果你想要,你可以掌握它们。

没有任何东西对你隐藏,除非绝对必须。语言提供抽象只是为了节省你的工作,而不是作为告诉你做什么的方式。事实上,语言鼓励你成为其设计的平等参与者。你可以改变它的一切,甚至它的语法,而你编写的任何东西都具有与预定义内容尽可能相同的地位。

注释

[1] 与现代想法非常接近的宏由Timothy Hart在1964年提出,即在Lisp 1.5发布两年后。最初缺少的是避免变量捕获和多次求值的方法;Hart的例子受两者影响。

[2] 在When the Air Hits Your Brain中,神经外科医生Frank Vertosick讲述了一段对话,他的首席住院医师Gary谈论外科医生和内科医生(“跳蚤”)之间的区别:

Gary和我点了一个大披萨,找到了一个开放的摊位。主任点燃了一支香烟。“看那些该死的跳蚤,叽叽喳喳地谈论他们一生只会看到一次的疾病。这就是跳蚤的麻烦,他们只喜欢奇怪的东西。他们讨厌他们的面包和黄油案件。这就是我们与该死的跳蚤的区别。看,我们喜欢大的多汁的腰椎间盘突出,但他们讨厌高血压……”

很难将腰椎间盘突出视为多汁的(除了字面意思)。然而我想我知道他们的意思。我经常有一个多汁的bug要追踪。不是程序员的人会发现很难想象bug中可能有乐趣。当然,如果一切正常工作会更好。在一方面,是的。然而在追踪某些类型的bug时无疑有一种 grim satisfaction。


后记版本

Arc

语言设计五个问题

如何成为黑客

日语翻译