如何让编程语言流行起来
2001年5月
(本文是为一门新语言写的商业计划。因此它忽略了(因为认为理所当然)一个好的编程语言最重要的特性:非常强大的抽象能力。)
我的一个朋友曾经告诉一位著名的操作系统专家,他想设计一个真正好的编程语言。这位专家告诉他这将是浪费时间,因为编程语言的流行与否并不取决于它们的优点,所以无论他的语言有多好,都不会有人使用。至少,这就是他设计的语言所遭遇的命运。
是什么让一门编程语言流行起来?流行的语言是否值得它们的流行度?是否值得尝试定义一门好的编程语言?如何定义?
我认为通过观察黑客,了解他们想要什么,就能找到这些问题的答案。编程语言是为黑客设计的,一门编程语言作为编程语言的好坏(而不是作为指称语义或编译器设计的练习)完全取决于黑客是否喜欢它。
1. 流行度的机制
确实,大多数人并不是简单地根据编程语言的优点来选择它们。大多数程序员都是被其他人告知使用什么语言。然而,我认为这些外部因素对编程语言流行度的影响并不像人们有时认为的那么大。我认为更大的问题是,黑客对好的编程语言的看法与大多数语言设计者的看法不同。
在这两者之间,黑客的意见才是重要的。编程语言不是定理。它们是工具,是为人类设计的,必须像鞋子必须适合人的脚一样,设计得适合人类的优点和缺点。如果一双鞋穿上去会夹脚,它就是一双坏鞋,不管它作为雕塑品有多么优雅。
也许大多数程序员无法分辨一门语言的好坏。但这与其他任何工具都是一样的。这并不意味着尝试设计一门好的语言是浪费时间。专业黑客能看出什么是好的语言,他们会使用它。专业黑客确实是一个极小的少数群体,但正是这个极小的少数群体编写了所有好的软件,他们的影响力如此之大,以至于其他程序员往往会使用他们使用的任何语言。实际上,这往往不仅仅是影响力,而是命令:专业黑客往往是那些作为老板或导师告诉其他程序员使用什么语言的人。
专业黑客的意见并不是决定编程语言相对流行度的唯一力量 — 遗留软件(Cobol)和炒作(Ada, Java)也起着作用 — 但我认为从长远来看,这是最强大的力量。给定一个初始的临界质量和足够的时间,一门编程语言可能会变得与其应得的流行度相当。而流行度会进一步区分好的语言和坏的语言,因为真实用户的反馈总是会带来改进。看看任何流行的语言在其生命周期中发生了多大的变化。Perl和Fortran是极端的例子,但即使是Lisp也发生了很大变化。例如,Lisp 1.5没有宏;这些是在MIT的黑客们使用Lisp编写真实程序几年后才发展出来的。[1]
所以不管一门语言是否必须好才能流行,我认为一门语言必须流行才能好。而且它必须保持流行才能保持好。编程语言的技术水平不会停滞不前。然而,我们今天拥有的Lisp基本上还是MIT在1980年代中期拥有的样子,因为那是Lisp最后一次拥有足够大且要求高的用户群。
当然,黑客必须先知道一门语言才能使用它。他们如何听说?从其他黑客那里。但必须有一些初始的黑客群体在使用这门语言,其他人才能听说它。我想知道这个群体需要多大;多少用户才能形成临界质量?凭直觉,我会说是二十个。如果一门语言有二十个独立的用户,意味着二十个自己决定使用它的用户,我会认为它是真实的。
达到这个目标并不容易。如果从零到二十比从二十到一千更难,我也不会感到惊讶。获得这最初的二十个用户的最好方法可能是使用特洛伊木马:给人们一个他们想要的应用程序,而这个应用程序恰好是用新语言编写的。
2. 外部因素
让我们先承认一个确实影响编程语言流行度的外部因素。要变得流行,一门编程语言必须是一个流行系统的脚本语言。Fortran和Cobol是早期IBM大型机的脚本语言。C是Unix的脚本语言,后来Perl也是。Tcl是Tk的脚本语言。Java和Javascript旨在成为Web浏览器的脚本语言。
Lisp不是一门非常流行的语言,因为它不是一个非常流行的系统的脚本语言。它保留的流行度可以追溯到1960年代和1970年代,当时它是MIT的脚本语言。当时许多伟大的程序员都在某个时候与MIT有关。在1970年代早期,在C出现之前,MIT的Lisp方言,称为MacLisp,是严肃黑客想要使用的为数不多的编程语言之一。
今天Lisp是两个中等流行系统的脚本语言,Emacs和Autocad,因此我怀疑今天大多数Lisp编程都是在Emacs Lisp或AutoLisp中完成的。
编程语言不会孤立存在。编程是一个及物动词 — 黑客通常是在编程某样东西 — 在实践中,语言是根据它们用来编程什么来评判的。所以如果你想设计一门流行的语言,你要么必须提供比语言更多的东西,要么必须设计你的语言来替代某个现有系统的脚本语言。
Common Lisp部分不流行的原因是它是一个孤儿。它最初确实带有一个用来编程的系统:Lisp机器。但Lisp机器(连同并行计算机)在1980年代被通用处理器的日益强大所碾压。如果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的政治正确性是一个异常。早期的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服务器的应用程序,我们花了多年时间才让人们理解它不需要下载。这不是因为他们愚蠢。他们只是把我们调出了他们的频道。
好消息是,简单的重复解决了问题。你所要做的就是继续讲述你的故事,最终人们会开始听。不是当人们注意到你在那里时他们才注意;而是当他们注意到你仍然在那里时。
通常需要一段时间才能获得动力,这很好。大多数技术在首次推出后都会发生很大变化 — 编程语言尤其如此。对一项新技术来说,没有什么比被一小群早期采用者使用几年更好的了。早期采用者很老练且要求高,会很快找出你的技术中剩下的任何缺陷。当你只有几个用户时,你可以与他们保持密切联系。而且早期采用者在你改进系统时会很宽容,即使这会导致一些破坏。
新技术有两种引入方式:有机增长方法和大爆炸方法。有机增长方法的典型例子是经典的、资金不足的车库创业。几个家伙,在默默无闻中工作,开发一些新技术。他们推出时没有营销,最初只有几个(狂热忠诚的)用户。他们继续改进技术,同时他们的用户群通过口口相传而增长。在他们知道之前,他们已经很大了。
另一种方法,大爆炸方法,以VC支持的、大力营销的创业公司为例。他们匆忙开发产品,大力宣传推出,并立即(他们希望)拥有大量用户群。
一般来说,车库家伙羡慕大爆炸家伙。大爆炸家伙很圆滑、自信,受到VC的尊重。他们能负担得起最好的东西,围绕推出的公关活动有让他们成为名人的副作用。有机增长家伙,坐在他们的车库里,感到贫穷和不受欢迎。然而我认为他们常常错误地为自己感到遗憾。有机增长似乎比大爆炸方法产生更好的技术和更富有的创始人。如果你看看今天的主导技术,你会发现它们大多数都是有机增长的。
这种模式不仅适用于公司。你在赞助研究中也能看到它。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-word。但我的猜测是我们不应该害怕把新的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发布晚两年。最初缺少的是避免变量捕获和多次求值的方法;Hart的例子两者都有。
[2] 在When the Air Hits Your Brain中,神经外科医生Frank Vertosick讲述了一个对话,其中他的首席住院医师Gary谈到外科医生和内科医生(“跳蚤”)的区别:Gary和我点了一个大披萨,找到了一个空位。首席点了一支烟。“看看那些该死的跳蚤,喋喋不休地谈论他们一生中只会看到一次的疾病。这就是跳蚤的问题,他们只喜欢奇怪的东西。他们讨厌他们的日常病例。这就是我们和该死的跳蚤的区别。看,我们喜欢大而多汁的腰椎间盘突出,但他们讨厌高血压….“很难把腰椎间盘突出想象为多汁的(除了字面意思)。然而我认为我知道他们的意思。我经常有一个多汁的bug要追踪。不是程序员的人会觉得很难想象bug中会有乐趣。当然,如果一切都能正常工作会更好。在某种意义上,是的。然而,在追踪某些类型的bug时,确实有一种阴沉的满足感。
英文版:paulgraham.com/popular.html|中文版:HiJiangChuan.com/paulgraham/008-being-popular
更新记录:
- 2025-02-22 HiJiangChuan 初稿翻译,术语待验证;