PG Paul Graham 文集
100%
Part 5 · 早期文章 Early Essays

简洁即力量

2002年5月

“代数符号在狭小空间内压缩的意义量,是促进我们习惯于借助它们进行推理的另一个因素。”

——查尔斯·巴贝奇,引自艾弗森的图灵奖演讲

在LL1邮件列表中关于《书呆子的复仇》所引发问题的讨论中,保罗·普雷斯科德写了一句话,一直萦绕在我心头。

Python的目标是规整性和可读性,而非简洁性。

表面上看,这对一种编程语言来说似乎是一个相当不利的说法。据我所知,简洁性=力量。如果是这样,那么替换一下,我们就得到:

Python的目标是规整性和可读性,而非力量。

而且这似乎不是一个你愿意做出的权衡(如果这确实是一个权衡的话)。这离说Python的目标不是作为一种编程语言而有效并不遥远。

简洁性等于力量吗?在我看来,这是一个重要的问题,也许是对任何对语言设计感兴趣的人来说最重要的问题,而且是一个值得直接面对的问题。我还不确定答案是否简单的是“是”,但这是一个很好的起点假设。

假设

我的假设是,简洁性就是力量,或者至少在非病态的例子中,你可以将它们视为等同。

在我看来,简洁性正是编程语言的目的所在。计算机对于直接用机器语言告诉它们做什么同样乐此不疲。我认为我们费尽心思开发高级语言的主要原因是为了获得杠杆效应,这样我们就能用10行高级语言说出(更重要的是,思考出)需要1000行机器语言才能表达的东西。换句话说,高级语言的主要意义在于让源代码变得更小。

如果更小的源代码是高级语言的目的,而某事物的力量在于它实现其目的的程度,那么衡量一种编程语言力量的标准就是它能让你的程序变得多小。

反过来说,一种不能让你的程序变小的语言,在编程语言应当做的事情上就做得不好,就像一把切不好的刀子,或者难以辨认的印刷品。

度量标准

但“小”是在什么意义上呢?衡量代码大小最常用的标准是代码行数。但我认为这个度量标准之所以最常用,是因为它最容易测量。我不认为真的有人相信它是程序长度的真正检验标准。不同的语言对于一行应该放多少内容有不同的惯例;在C语言中,很多行除了一个或两个分隔符外什么都没有。

另一个简单的检验标准是程序中的字符数,但这也不太好;有些语言(例如Perl)只是使用了比其他语言更短的标识符。

我认为衡量程序大小的更好标准是元素的数量,其中元素是指如果你画一棵表示源代码的树时,任何会成为一个独立节点的东西。变量或函数的名称是一个元素;一个整数或浮点数是一个元素;一段字面文本是一个元素;模式中的一个元素或格式指令是一个元素;一个新块是一个元素。有些边缘情况(-5是一个元素还是两个?)但我认为大多数情况在所有语言中都是一样的,所以它们不太影响比较。

这个度量标准需要进一步完善,在特定语言的情况下可能需要解释,但我认为它试图衡量的是正确的东西,即程序有多少个部分。我认为在这个练习中你会画的树,就是你在脑海中为了构思程序而必须构建的东西,所以它的大小与你编写或阅读程序所需的工作量成正比。

设计

这种度量标准可以让我们比较不同的语言,但至少对我来说,这不是它的主要价值。简洁性检验的主要价值是作为设计语言的指南。语言之间最有用的比较是在同一语言的两种潜在变体之间。我能在语言中做些什么来让程序更短?

如果程序的概念负荷与其复杂性成正比,而给定的程序员能承受固定的概念负荷,那么这就等同于问:我能做些什么来让程序员完成最多的工作?而在我看来,这与问“我如何设计一种好的语言?”是同一回事。

(顺便说一句,没有什么比设计语言更能清楚地表明“所有语言都是等价的”这句老生常谈是错误的了。当你设计一种新语言时,你不断在比较两种语言——如果我做了x的语言,和如果我没做的语言——来决定哪个更好。如果这真的是一个无意义的问题,你不如抛硬币决定。)

以简洁为目标似乎是发现新想法的好方法。如果你能做一些让许多不同程序变得更短的事情,那可能不是巧合:你很可能发现了一个有用的新抽象。你甚至可以编写一个程序来搜索源代码中的重复模式以提供帮助。在其他语言中,那些以简洁著称的语言会是寻找新想法的对象:Forth、Joy、Icon。

比较

据我所知,第一个写这些问题是弗雷德·布鲁克斯在《人月神话》中。他写道,无论使用什么语言,程序员每天似乎生成大致相同数量的代码。当我在二十出头第一次读到这个时,它让我大为惊讶,似乎具有巨大的含义。这意味着(a)唯一能更快编写软件的方法是使用更简洁的语言,以及(b)一个费心这样做的人可以让那些不这样做的竞争对手望尘莫及。

如果布鲁克斯的假设是真的,它似乎就处于黑客技术的核心位置。在之后的岁月里,我一直密切关注任何关于这个问题的证据,从正式研究到关于个别项目的轶事。我没有看到任何反驳他的东西。

我还没有看到在我看来确凿的证据,也不期待看到。像卢茨·普雷切尔特的语言比较这样的研究,虽然产生了符合我预期的结果,但往往使用太短的问题,不足以成为有意义的测试。对语言的更好测试是那些需要一个月才能写出来的程序会发生什么。而如果你像我一样相信一种语言的主要目的是适合思考(而不仅仅是想到之后告诉计算机做什么),那么唯一真正的测试是你能用它写出什么新东西。所以任何需要你满足预定义规格的语言比较都稍微测试错了东西。

语言的真正测试是你如何能发现和解决新问题,而不是你如何能很好地用它来解决别人已经提出的问题。这两者是相当不同的标准。在艺术中,像刺绣和马赛克这样的媒介,如果你事先知道要做什么,效果很好,但如果你不知道,效果就非常糟糕。当你想要在制作过程中发现图像——就像你必须处理像人物图像这样复杂的东西一样——你需要使用更流动的媒介,比如铅笔、水墨或油画。事实上,挂毯和马赛克在实践中的制作方式是先画一幅画,然后复制它。(“卡通”这个词最初就是用来描述为此目的而作的画。)

这意味着我们可能永远不会有编程语言相对力量的准确比较。我们会有精确的比较,但不会有准确的比较。特别是,为比较语言而设计的明确研究,因为它们可能会使用小问题,而且必然使用预定义问题,往往会低估更强大语言的力量。

来自实践领域的报告,虽然必然不如“科学”研究那么精确,但可能更有意义。例如,爱立信的乌尔夫·维格做了一项研究,得出结论:Erlang比C++简洁4-10倍,并且相应地开发软件更快:

爱立信内部开发项目之间的比较表明,相似的行/小时生产率,包括软件开发的各个阶段,与使用哪种语言(Erlang、PLEX、C、C++或Java)相当独立。那么区分不同语言的就变成了源代码量。

该研究还明确处理了布鲁克斯书中仅仅隐含的一点(因为他衡量的是调试过的代码行数):用更强大语言编写的程序往往有更少的bug。在像网络交换机这样的应用中,这本身就成为一个目标,可能比程序员生产力更重要。

品味测试

最终,我认为你必须凭直觉行事。用这种语言编程是什么感觉?我认为找到(或设计)最佳语言的方法是变得对语言让你思考的顺畅程度高度敏感,然后选择/设计感觉最好的语言。如果某个语言特性很笨拙或限制性强,不用担心,你会意识到的。

这种高度敏感会付出代价。你会发现你无法忍受用笨拙的语言编程。我发现没有宏的语言编程让我感到难以忍受的束缚,就像习惯了动态类型的人发现不得不回到必须声明每个变量类型、不能创建不同类型对象列表的语言中编程一样难以忍受。

我不是唯一有这种感觉的人。我认识很多Lisp黑客都有这种经历。事实上,编程语言相对力量最准确的衡量标准可能是:知道该语言的人中有多少百分比愿意接受任何能用这种语言工作的职位,而不管应用领域是什么。

束缚感

我认为大多数黑客都知道语言让人感到束缚是什么意思。当你感到束缚时发生了什么?我认为这就像你想走的路被封了,你不得不绕很远的路才能到达你想去的地方。有你想说的东西,而语言不允许你说。

我认为这里真正发生的事情是,一种束缚人的语言就是不够简洁的语言。问题不仅仅是你不能按计划表达。而是语言让你走的弯路更长。试试这个思想实验。假设有某个你想写的程序,语言不让你按计划的方式表达,而是强迫你以某种更短的其他方式写程序。至少对我来说,那不会让人感到束缚。就像你想走的路被封了,但路口的警察指引你走一条捷径而不是绕路。太好了!

我认为束缚感的大部分(百分之九十?)来自于被迫把用语言写的程序比你脑海中的程序写得更长。束缚感主要是缺乏简洁性。所以当一种语言让人感到束缚时,这(主要)意味着它不够简洁,而当一种语言不够简洁时,它就会让人感到束缚。

可读性

我开头引用的名言提到了另外两个品质,规整性和可读性。我不确定规整性是什么,或者规整且可读的代码比仅仅可读的代码有什么优势(如果有的话)。但我想我知道可读性是什么意思,而且我认为它也跟简洁性有关。

我们必须在此小心区分单行代码的可读性与整个程序的可读性。真正重要的在于后者。我承认,一行Basic代码可能比一行Lisp代码更易读。但用Basic编写的程序,其行数很可能超过用Lisp编写的同一程序(尤其是当项目踏入Greenspunland的领域时)。阅读Basic程序的总工作量无疑更大。

总工作量 = 每行付出的努力 × 行数

我虽不如确信简洁与能力成正比那样,确信可读性与简洁性成正比,但毫无疑问,简洁性是可读性的一个因素(从数学意义上讲;见上述公式)。因此,说语言目标是可读性而非简洁性,可能并无意义;这就好比说目标是可读性,而不是可读性一样。

对于初次接触某种语言的用户而言,单行可读性意味着源代码看起来不吓人。因此,即使单行可读性可能是一个糟糕的设计决策,它却不失为一个好的营销策略。这与分期付款的成功技巧异曲同工:不用高昂的一次性价格吓退顾客,而是告诉他们低月供。然而,分期付款对买家来说总体上是吃亏的,正如单纯追求单行可读性对程序员而言可能同样不利。买家将要支付很多笔那低低的分期款;而程序员也将阅读很多行那单独来看都易读的代码。

这种权衡在编程语言出现之前就已存在。如果你习惯阅读小说和报纸文章,首次阅读数学论文的体验可能会令人沮丧。单页内容可能需花半小时才能读完。然而,尽管感觉如此,我相当确定问题不在记号本身。数学论文难读是因为思想本身艰深。倘若用散文形式表达同样的思想(正如数学家们在发展出简洁记号前不得不做的那样),它们也不会变得更容易读,因为论文会膨胀到一本书的篇幅。

到什么程度?

不少人否定了简洁等于能力这一观点。我认为,与其简单争论它们是否相同,不如提问:简洁等于能力在多大程度上成立?因为很明显,简洁是高级编程语言的一大用途。如果这不是它们唯一的用途,那么它们还有什么其他用途,并且相对而言这些功能有多重要?

我提出这个问题,不只是为了让辩论更文明。我真的很想知道答案。一种语言何时——如果有那么一天——会因过于简洁而适得其反?

我最初的假设是,除病态示例外,简洁可被视为等同于能力。我的意思是,在任何语言设计中,两者将是同一的,但如果有人特意设计一种语言来反驳这一假设,他们或许也能做到。实际上,我对此并不十分确定。

语言,而非程序

我们应明确,所讨论的是语言的简洁性,而非个别程序的简洁性。个别程序确实可能写得过于紧凑。

我在《On Lisp》中曾写到这一点。一个复杂的宏可能需要节省自身长度很多倍的代码才算合理。如果编写某个棘手的宏能在每次使用时节省十行代码,而宏本身也是十行,那么使用一次以上就能净省行数。但这可能并非明智之举,因为宏定义比普通代码更难读。你可能需要使用该宏十次或二十次,才能在可读性上取得净改善。

我确信每种语言都有这样的权衡(尽管我怀疑语言越强大,风险就越高)。每个程序员一定都见过某些聪明人用可疑的编程技巧让代码略微缩短的情况。

因此,关于这一点没有争议——至少我没有异议。个别程序当然可能过于简洁而适得其反。问题是,一种语言能否如此?语言能否迫使程序员编写简短(按元素计)但牺牲整体可读性的代码?

难以想象一种语言会过于简洁的原因之一是,如果某种表达方式异常紧凑,通常也会有更冗长的替代方式。例如,若你觉得大量使用宏或高阶函数的Lisp程序过于密集,你可以选择编写与Pascal同构的代码。如果你不想在Arc中将阶乘表达为高阶函数的调用

(rec zero 1 * 1-)

也可以写出递归定义:

(rfn fact (x) (if (zero x) 1 (* x (fact (1- x)))))

虽然我一时想不出任何例子,但我对语言是否可能过于简洁这个问题很感兴趣。是否有语言强迫你以晦涩难懂的方式编写代码?如果有人有例证,我将非常感兴趣。

(提醒:我正在寻找的是根据上述“元素”度量而显得非常密集的程序,而非仅仅因为可以省略分隔符、所有东西都是单字符名而变得简短的程序。)