Loading...

受欢迎

Original

May 2001

(这篇文章写得像一个新语言的商业计划。 所以它缺少(因为它认为理所当然)一个好的编程语言最重要的 特性:非常强大的抽象。)

我的一位朋友曾经告诉一位著名的操作系统 专家,他想设计一种非常好的 编程语言。这位专家告诉他,这将是 浪费时间,编程语言不会因为它们的优点而变得流行 或不受欢迎,因此无论他的语言 有多好,都不会有人使用它。至少,这就是 发生在他自己设计的语言上的事情。

是什么让一种语言流行起来?流行的 语言是否值得它们受欢迎?尝试定义 一种好的编程语言是否值得?你会怎么做?

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

1 流行机制

当然,大多数人不会仅仅根据语言的优点来选择编程 语言。大多数程序员被别人告知 使用什么语言。然而我认为这种外部因素对编程语言流行的影响 并没有人们有时认为的那么大。我认为一个更大的 问题是,黑客对好的编程语言的看法 与大多数语言设计者的看法不同。

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

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

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

因此,无论一种语言是否必须好才能流行,我认为 一种语言必须流行才能好。它必须保持流行 才能保持良好。编程语言的最新技术并没有 停滞不前。然而,我们今天拥有的 Lisps 仍然基本上 是他们在 20 世纪 80 年代中期在麻省理工学院拥有的那些,因为那是 Lisp 最后一次拥有足够大且要求苛刻的用户群。

当然,黑客必须了解一种语言才能 使用它。他们如何得知?从其他黑客那里。但必须 有一些最初的黑客群体使用这种语言,以便其他人甚至 听说过它。我想知道这个群体需要多大;多少 用户才能达到临界质量?凭直觉,我认为是二十个。 如果一种语言有二十个独立的用户,这意味着二十个用户 自己决定使用它,我认为它就是真实的。

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

2 外部因素

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

Lisp 并不是一种非常流行的语言,因为它不是 一个非常流行的系统的脚本语言。它所保留的流行程度可以追溯到 20 世纪 60 年代和 70 年代,当时它是 麻省理工学院的脚本语言。当时许多伟大的程序员 都在某个时候与麻省理工学院有关联。在 20 世纪 70 年代初, 在 C 出现之前,麻省理工学院的 Lisp 方言,称为 MacLisp,是 一个严肃的黑客想要使用的唯一编程语言之一。

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

编程语言并不孤立存在。黑客是一个 及物动词——黑客通常在黑什么东西——在 实践中,语言是相对于它们被用来 黑的东西来评判的。因此,如果你想设计一种流行的语言,你必须 提供不止一种语言,或者你必须设计你的语言 来替换某个现有系统的脚本语言。

Common Lisp 不受欢迎的部分原因是它是一个孤儿。它 最初确实附带了一个要黑的东西:Lisp 机器。但 Lisp 机器(以及并行计算机)在 20 世纪 80 年代被 通用处理器的不断增强的能力所碾压。如果 Common Lisp 曾经是 Unix 的一个好的脚本语言,它可能 会保持流行。不幸的是,它是一个非常糟糕的脚本语言。

描述这种情况的一种方法是说,一种语言不是 根据它自身的优点来评判的。另一种观点是,一种编程语言 实际上不是一种编程语言,除非它也是 某事物的脚本语言。这只有在它让人感到意外时才显得不公平。我认为这与期望一种编程语言具有,比如说,一个实现一样不公平。这只是 编程语言的一部分。

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

一种语言还需要有一本关于它的书。这本书应该 薄,写得很好,并且充满好的例子。K&R 是这里的理想选择。目前,我几乎可以说,一种语言必须有一本 由 O'Reilly 出版社出版的书。这正在成为衡量黑客是否重视 的标准。

还应该有在线文档。事实上,这本书 可以从在线文档开始。但我认为实体 书籍还没有过时。它们的格式很方便,而且出版商施加的 事实上的审查制度是一个有用但并不完美的 过滤器。书店是了解 新语言的最重要场所之一。

3 简洁

假设你可以提供任何语言需要的三个东西——一个 免费的实现、一本书和一些要黑的东西——你如何 制作一种黑客会喜欢的语言?

黑客喜欢的一件事是简洁。黑客很懒,就像 数学家和现代建筑师一样懒:他们 讨厌任何多余的东西。说一个黑客在准备编写程序时会决定使用什么语言,至少在潜意识里,是根据他将要输入的字符总数来决定的,这并不算过分。如果这不是黑客的真实想法,语言设计者最好假装它是。

试图用冗长的表达来取悦用户,这些表达旨在类似于英语,这是一个错误。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 可黑性

对于黑客来说,有一件事比简洁更重要:能够 做你想做的事情。在编程语言的历史上, 令人惊讶的是,人们付出了大量的努力来阻止程序员 做被认为是不恰当的事情。这是一个危险的 自以为是的计划。语言设计者如何知道 程序员将需要做什么?我认为语言设计者最好 将他们的目标用户视为一个天才,他将 需要做他们从未预料到的事情,而不是一个笨蛋,需要 保护自己免受伤害。笨蛋无论如何都会自食其果。你可能会救他免受 引用另一个包中的变量的困扰,但你无法救他免受 编写一个设计糟糕的程序来解决错误问题的困扰,并且 花费很长时间来完成它。

优秀的程序员经常想做危险和不光彩的事情。 我所说的不光彩是指那些超越了语言试图呈现的任何语义表面的事情:例如,获取某个高级抽象的内部 表示。黑客喜欢黑,黑意味着进入事物内部并对 原始设计进行二次猜测。

*允许自己被二次猜测。*当你制作任何工具时,人们会 以你意想不到的方式使用它,对于 像编程语言这样高度结构化的工具来说,尤其如此。许多黑客 会想要以你从未 想象过的方式调整你的语义模型。我说,让他们;给程序员尽可能多的 内部内容的访问权限,而不会危及运行时系统,例如 垃圾收集器。

在 Common Lisp 中,我经常想遍历一个结构的字段——例如,梳理对已删除对象的引用, 或者查找未初始化的字段。我知道结构只是 底层的向量。然而,我无法编写一个通用函数 ,我可以对任何结构调用它。我只能通过 名称访问字段,因为这就是结构应该 意味着的。

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

从历史上看,Lisp 擅长让黑客随心所欲。 Common Lisp 的政治正确性是一种反常现象。早期的 Lisps 让你可以接触到所有东西。幸运的是,这种 精神在很大程度上保留在宏中。多么美妙的事情, 能够对源代码进行任意转换。

经典宏是真正的黑客工具——简单、强大且 危险。理解它们的功能非常容易:你对宏的参数 调用一个函数,它返回的任何内容都会 插入到宏调用的位置。卫生宏体现了 相反的原则。它们试图保护你免受理解它们正在做什么的困扰。我从未听说过卫生宏用一句话解释过。而且它们是决定 程序员被允许想要什么所带来的危险的典型例子。卫生宏旨在保护我免受变量捕获的困扰,以及其他问题,但变量 捕获正是我在某些宏中想要的。

一种真正好的语言应该既干净又脏:设计干净, 核心包含一组经过良好理解且高度正交的 运算符,但脏是指它让黑客可以随心所欲地使用它。C 就是这样。早期的 Lisps 也是如此。真正的黑客 语言总是会带有一点不羁的性格。

一种好的编程语言应该具有让那些使用“软件工程”这个词的人 摇头表示不赞同的特性。在另一端是像 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 服务器正在发生的事情。 指针由一些伺服电机移动,这些电机在转动时会发出轻微的噪音。我从我的办公桌看不到仪表盘,但我 发现我可以通过声音立即判断出 服务器是否有问题。

甚至有可能编写一个分析器,它可以自动 检测低效的算法。我不会感到惊讶,如果某些 内存访问模式被证明是糟糕的 算法的可靠标志。如果有一个小家伙在计算机内部跑来跑去执行我们的程序,他可能 会像联邦政府雇员一样,对他的工作有很长且悲伤的故事要讲。我经常有一种感觉,我让处理器做了很多无用功,但我从未找到一个好的方法来查看它正在做什么。

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

从最终用户感知到的速度的本质上来说,可能正在发生变化。 随着基于服务器的应用程序的兴起,越来越多的程序 可能最终会成为 I/O 绑定的。让 I/O 变得更快将是值得的。 语言可以通过简单的、快速的、格式化的输出函数等直接措施来帮助实现这一点,也可以通过缓存和持久对象等深层结构 变化来帮助实现这一点。

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

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

在某些应用程序中,处理器将是限制因素, 而执行速度将是最重要的优化目标。 但通常内存将是限制因素;并发用户数量将由 每个用户数据所需的内存量决定。语言也可以在这里提供帮助。对 线程的良好支持将使所有用户能够共享一个堆。它可能 还有助于拥有持久对象和/或语言级支持 以实现延迟加载。

9 时间

一种流行语言需要的最后一个要素是时间。没有人愿意 用一种可能消失的语言编写程序,就像许多 编程语言一样。因此,大多数黑客会倾向于等待 一种语言存在几年之后,才会考虑 使用它。

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

大多数人已经学会对他们听到的 新事物进行类似的过滤。他们甚至不会开始关注 直到他们听到某件事十次。他们完全有理由这样做:大多数热门的新事物最终 都是浪费时间,最终会消失。通过延迟学习 VRML, 我避免了完全学习它的必要。

因此,任何发明新事物的人都要预期,在人们开始理解它之前,他们必须不断重复他们的信息。我们 编写了第一个基于 Web 服务器的 应用程序(据我所知),我们花了数年时间才让人们明白 它不需要下载。这不是因为他们愚蠢。他们只是把我们屏蔽了。

好消息是,简单的重复可以解决问题。你所要做的就是不断讲述你的故事,最终人们会 开始听到。人们注意到你在那里的时候,他们 并没有关注你;而是当他们注意到你仍然在那里的时候。

获得动力通常需要一段时间,这很好。 大多数技术即使在首次 推出之后也会发生很大变化——尤其是编程语言。对于 一项新技术来说,没有什么比在最初几年只被少数早期采用者使用更好。早期采用者是成熟且要求苛刻的,他们会迅速发现你的 技术中存在的任何缺陷。当你只有少数用户时,你可以与他们所有人保持密切联系。而且早期采用者在你改进系统时会很宽容,即使这会导致一些问题。

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

另一种方法,大爆炸方法,以 风险投资支持的、大力宣传的创业公司为例。他们争先恐后地开发产品, 以极大的宣传力度推出它,并立即(他们希望)拥有 庞大的用户群。

通常,车库里的家伙会嫉妒大爆炸的家伙。大爆炸的 家伙很圆滑、自信,并且受到风险投资家的尊重。他们可以 负担得起最好的东西,而围绕发布进行的公关活动 会产生让他们成为名人的副作用。有机增长的家伙,坐在他们的车库里,感到贫穷和不受爱戴。然而,我认为他们经常会错误地感到自怜。有机增长似乎比大爆炸方法产生更好的技术和更富有的创始人。如果你看看今天的 主导技术,你会发现它们中的大多数都是有机增长的。

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

10 重构

“最好的写作是重写,”E. B. White 写道。每个优秀的 作家都知道这一点,对于软件来说也是如此。设计中最重要的 部分是重构。编程语言,尤其是 编程语言,重构得不够多。

要编写好的软件,你必须同时在脑海中保留两个相反的 想法。你需要年轻黑客对他能力的幼稚信念,同时也要有老手的怀疑态度。你 必须能够思考 这有多难? 用你大脑的一半思考,同时思考 它永远不会成功 用另一半思考。

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

那些做得好的人经常认为他们正在做的事情 不好。其他人看到他们所做的事情,充满了惊奇, 但创造者却充满了担忧。这种模式并非巧合: 正是这种担忧让作品变得出色。

如果你能保持希望和担忧的平衡,它们将推动项目 前进,就像你的两条腿推动自行车前进一样。在 双循环创新引擎的第一阶段,你努力 解决某个问题,你的信心让你相信你能够 解决它。在第二阶段,你以冷静的眼光审视你所做的事情,并清楚地看到它的所有缺陷。但 只要你的批判精神没有超过你的希望,你就能 审视你那明显不完整的系统,并思考, 要完成剩下的工作有多难?,从而继续 循环。

保持两种力量的平衡很棘手。在年轻的黑客中, 乐观主义占主导地位。他们创造了一些东西,确信它很棒,并且从未改进它。在老黑客中,怀疑主义占主导地位, 他们甚至不敢承担雄心勃勃的项目。

任何可以帮助你保持重构循环的东西都是好的。散文 可以一遍又一遍地重写,直到你对它满意为止。但 软件,通常情况下,重构得不够多。散文有 读者,但软件有用户。如果一个作家重写了一篇文章, 那些读过旧版本的人不太可能抱怨他们的 想法被一些新引入的不兼容性所破坏。

用户是一把双刃剑。他们可以帮助你改进你的 语言,但他们也可以阻止你改进它。因此,要谨慎选择 你的用户,并且要缓慢地增加他们的数量。拥有 用户就像优化一样:明智的做法是延迟它。此外, 作为一般规则,你可以在任何给定时间进行比你想象的更多的更改。引入更改就像撕掉绷带:疼痛几乎在你感觉到它的同时就变成了记忆。

众所周知,让委员会设计语言不是一个好主意。委员会会产生糟糕的设计。但我认为委员会最糟糕的 危险是它们会干扰重构。引入更改需要做很多工作,以至于没有人愿意费心。委员会决定的任何事情都倾向于保持原样,即使大多数 成员不喜欢它。

即使是两个人组成的委员会也会妨碍重构。这在 两个人编写的软件片段之间的接口中尤其如此。要更改接口,两个人都必须同意 同时更改它。因此,接口往往不会发生任何变化, 这是一个问题,因为它们往往是任何系统中最随意 的部分之一。

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

11 Lisp

所有这些都意味着,对于一种新的 Lisp 来说,存在希望。对于任何给黑客他们想要的语言来说,都存在希望,包括 Lisp。我认为我们可能在思考黑客被 Lisp 的奇怪之处所排斥时犯了一个错误。这种令人欣慰的错觉可能阻止我们看到 Lisp 的真正问题,或者至少是 Common Lisp 的问题,即它不适合做黑客想做的事情。黑客的语言需要强大的库和一些可以黑的东西。Common Lisp 两者都没有。黑客的语言简洁且可黑。Common Lisp 不是。

好消息是,不是 Lisp 糟糕,而是 Common Lisp 糟糕。如果我们能够开发一种新的 Lisp,它是一种真正的黑客语言,我认为黑客会使用它。他们会使用任何能完成工作的语言。我们所要做的就是确保这种新的 Lisp 在某些重要方面比其他语言做得更好。

历史提供了一些鼓励。随着时间的推移,连续的新编程语言从 Lisp 中获取了越来越多的特性。在您制作的语言成为 Lisp 之前,已经没有多少东西可以复制了。最新的热门语言 Python 是一种淡化的 Lisp,具有中缀语法,没有宏。一种新的 Lisp 将是这种演进的自然步骤。

我有时认为,称它为 Python 的改进版本将是一个很好的营销策略。这听起来比 Lisp 更酷。对许多人来说,Lisp 是一种缓慢的 AI 语言,有很多括号。Fritz Kunze 的官方传记小心地避免提及 L 字。但我猜想我们不应该害怕称新的 Lisp 为 Lisp。Lisp 在最优秀的黑客中仍然拥有很多潜在的尊重——例如,那些学习过 6.001 并理解它的人。而那些是你需要赢得的用户。

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

学习 Lisp 值得你为此付出努力,因为当你最终理解它时,你将获得深刻的启迪体验;这种体验将使你在余生中成为一名更好的程序员,即使你实际上并没有经常使用 Lisp 本身。

如果我不了解 Lisp,阅读这篇文章会让我提出问题。一种能让我成为更好的程序员的语言,如果它有任何意义,意味着一种更适合编程的语言。而这实际上是 Eric 想要表达的意思。

只要这个想法还在流传,我认为黑客会对一种新的 Lisp 持有足够的接受度,即使它被称为 Lisp。但这种 Lisp 必须是一种黑客语言,就像 1970 年代的经典 Lisp 一样。它必须简洁、简单且可黑。它还必须拥有强大的库来完成黑客现在想做的事情。

在库方面,我认为有空间在 Perl 和 Python 等语言的游戏中击败它们。在未来几年需要编写的许多新应用程序将是 基于服务器的 应用程序。没有理由一种新的 Lisp 不应该拥有像 Perl 一样好的字符串库,如果这种新的 Lisp 还拥有强大的基于服务器的应用程序库,它可能会非常受欢迎。真正的黑客不会对一个新的工具嗤之以鼻,这个工具可以让它们用几个库调用来解决难题。记住,黑客很懒。

拥有对基于服务器的应用程序的核心语言支持将是一个更大的胜利。例如,对具有多个用户的程序的显式支持,或类型标签级别的數據所有权。

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

12 梦想语言

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

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

使用大型抽象,你可以非常快地编写程序的第一个版本。稍后,当你想要优化时,有一个非常好的分析器可以告诉你应该把注意力集中在哪里。你可以使内部循环快得令人眼花缭乱,甚至在需要时编写内联字节码。

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

该语言有一个小的核心,以及强大的、高度正交的库,这些库的设计与核心语言一样精心。这些库都很好地协同工作;语言中的所有内容都像精密的相机中的零件一样完美地组合在一起。没有任何东西被弃用,或者为了兼容性而保留。所有库的源代码都可供使用。与操作系统和用其他语言编写的应用程序进行通信很容易。

该语言是分层构建的。更高层的抽象是以非常透明的方式从更低层的抽象构建的,如果你愿意,你可以获得这些抽象。

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

注释

[1] 与现代理念非常接近的宏是由 Timothy Hart 在 1964 年提出的,比 Lisp 1.5 发布晚了 2 年。最初缺少的是避免变量捕获和多次评估的方法;Hart 的例子都存在这两个问题。

[2] 在 当空气撞击你的大脑 中,神经外科医生 Frank Vertosick 讲述了一个对话,其中他的主治住院医师 Gary 谈论了外科医生和内科医生(“跳蚤”)之间的区别:

Gary 和我点了一份大披萨,找到一个空位。主任点了一根烟。“看看那些该死的跳蚤,喋喋不休地谈论着他们一生中只会遇到一次的疾病。这就是跳蚤的麻烦,他们只喜欢奇怪的东西。他们讨厌他们的面包和黄油案例。这就是我们和那些该死的跳蚤之间的区别。你看,我们喜欢又大又多汁的腰椎间盘突出,但他们讨厌高血压……”

很难想象腰椎间盘突出是多汁的(除了字面意思)。然而,我认为我知道他们的意思。我经常有一个多汁的错误要追踪。一个不是程序员的人会很难想象在一个错误中会有乐趣。当然,如果一切都能正常工作会更好。从某种意义上说,确实如此。然而,不可否认的是,追踪某些类型的错误会带来一种令人不寒而栗的满足感。