English Version

简洁即力量

2002年5月

“通过代数符号将大量意义压缩到一个小空间中,这是另一个有助于我们习惯性推理的因素。” - 查尔斯·巴贝奇,引自艾弗森的图灵奖演讲

在LL1邮件列表上关于《书呆子的复仇》所提出问题的讨论中,保罗·普雷斯科德写了一些让我印象深刻的话。Python的目标是规范性和可读性,而不是简洁性。表面上看来,这对编程语言来说似乎是一个相当不利的评价。据我所知,简洁性=力量。如果是这样,那么替换后我们得到Python的目标是规范性和可读性,而不是力量。这似乎不是一个你想要做出的权衡(如果这确实是一个权衡的话)。这几乎等同于说Python的目标不是作为一种有效的编程语言。

简洁性等于力量吗?在我看来,这是一个重要的问题,可能是对任何对语言设计感兴趣的人来说最重要的问题,直接面对这个问题会很有用。我还不确定答案是否是简单的"是",但这似乎是一个很好的开始假设。

假设

我的假设是简洁性就是力量,或者足够接近,除了在病态的例子中,你可以将它们视为相同。

在我看来,简洁性就是编程语言的意义所在。计算机对于直接用机器语言被告知要做什么同样满意。我认为我们开发高级语言的主要原因是获得杠杆作用,这样我们就可以用高级语言的10行代码来表达(更重要的是思考)需要1000行机器语言才能完成的事情。换句话说,高级语言的主要目的是使源代码更小。

如果更小的源代码是高级语言的目的,而某种东西的力量是它实现其目的的程度,那么编程语言力量的衡量标准就是它使你的程序变得多小。

相反,不能使你的程序更小的语言在编程语言应该做的事情上做得不好,就像一把不好切的刀或难以辨认的印刷品。

衡量标准

但是从什么意义上来说更小呢?代码大小最常见的衡量标准是代码行数。但我认为这个衡量标准最常见是因为它最容易测量。我不认为有人真正相信它是程序长度的真正测试。不同的语言对于在一行中放多少内容有不同的约定;在C语言中,很多行只有一个或两个分隔符。

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

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

这个衡量标准需要充实,在特定语言的情况下可能需要解释,但我认为它试图衡量正确的东西,即程序拥有的部分数量。我认为在这个练习中你会画的树就是你为了构思程序而必须在头脑中构建的东西,它的大小与你必须做的工作量成正比,无论是写还是读。

设计

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

如果程序的概念负荷与其复杂性成正比,而给定的程序员可以容忍固定的概念负荷,那么这与问"我能做什么来让程序员完成最多工作?“是相同的。这在我看来与问"我如何设计一种好的语言?“是相同的。

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

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

比较

据我所知,第一个写这些相关文章的人是弗雷德·布鲁克斯在《人月神话》中。他写道,无论使用什么语言,程序员每天似乎生成大致相同数量的代码。我在二十出头第一次读到这个时,这是一个很大的惊喜,似乎有巨大的含义。这意味着(a)让软件写得更快的方法是使用更简洁的语言,(b)花功夫这样做的人可以把不这样做的竞争对手远远甩在后面。

布鲁克斯的假设,如果它是真的,似乎处于黑客精神的核心。从那以后的几年里,我密切关注我能得到的关于这个问题的任何证据,从正式研究到个人项目的轶事。我没有看到任何与他相矛盾的证据。

我还没有看到我认为决定性的证据,我也不期望看到。像卢茨·普雷克特尔对编程语言的比较这样的研究,虽然产生了我期望的那种结果,但往往使用太短的问题,无法成为有意义的测试。语言的更好测试是在需要一个月编写的程序中发生什么。而真正的测试,如果你像我一样相信语言的主要目的是为了便于思考(而不仅仅是在你思考完后告诉计算机要做什么),是你能用它写出什么新东西。所以任何需要满足预定义规范的语言比较都在测试稍微错误的东西。

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

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

来自现场的报告,虽然它们必然不如"科学"研究精确,但可能更有意义。例如,爱立信的乌尔夫·维格进行了一项研究,结论是Erlang比C++简洁4-10倍,开发软件的速度也相应更快:爱立信内部开发项目之间的比较表明,包括软件开发的所有阶段在内的每行/小时生产力相似,几乎与使用的语言(Erlang、PLEX、C、C++或Java)无关。区分不同语言的成为源代码量。该研究还明确处理了布鲁克斯书中只是隐含的一点(因为他测量的是调试代码的行数):用更强大语言编写的程序往往有更少的错误。这本身就成为一个目的,在网络交换机等应用中,可能比程序员的生产力更重要。

味道测试

最终,我认为你必须凭直觉行事。用这种语言编程感觉如何?我认为找到(或设计)最好语言的方法是对语言让你思考的出色程度变得高度敏感,然后选择/设计感觉最好的语言。如果某些语言功能笨拙或限制性,别担心,你会知道的。

这种高度敏感是有代价的。你会发现你无法忍受在笨拙的语言中编程。我觉得在没有宏的语言中编程是无法忍受的限制性,就像习惯动态类型的人发现必须回到必须声明每个变量类型、不能制作不同类型对象列表的语言中编程是无法忍受的限制性一样。我不是唯一一个这样的人。我知道许多Lisp黑客都经历过这种情况。事实上,编程语言相对力量的最准确衡量标准可能是了解该语言的人愿意接受任何使用该语言的工作的百分比,不管应用领域如何。

限制性

我认为大多数黑客都知道语言感觉限制性是什么意思。当你有这种感觉时发生了什么?我认为这与当你想走的街道被封锁,你必须绕很长的路才能到达你想去的地方时的感觉相同。你有想说的话,但语言不允许你说。

这里真正发生的事情,我认为,是限制性语言是一种不够简洁的语言。问题不仅仅是你不能说你计划说的话。而是语言让你绕的弯路更长。试试这个思维实验。假设你想写某个程序,语言不允许你按计划的方式表达它,而是强迫你用某种更短的方式写程序。至少对我来说,这不会感觉很有限制性。这就像你想走的街道被封锁,十字路口的警察给你指了一条捷径而不是绕路。太好了!

我认为限制性的感觉大部分(百分之九十?)来自于被迫使你在语言中写的程序比你头脑中的更长。限制性主要是缺乏简洁性。所以当一种语言感觉有限制性时,这(大部分)意味着它不够简洁,而当一种语言不够简洁时,它会感觉有限制性。

可读性

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

我们必须小心区分单行代码的可读性和整个程序的可读性。重要的是后者。我同意Basic的一行代码可能比Lisp的一行代码更易读。但用Basic编写的程序行数会比用Lisp编写的相同程序多(特别是当你进入格林斯彭之地时)。阅读Basic程序的总工作量肯定会更大。总工作量 = 每行工作量 × 行数

我不像确信力量与简洁性成正比那样确信可读性与简洁性成正比,但简洁性肯定是可读性的一个因素(在数学意义上;见上面的公式)。所以,说语言的目标是可读性而不是简洁性可能甚至没有意义;这就像说目标是可读性,而不是可读性。

每行可读性对第一次遇到语言的用户来说,意味着源代码看起来不具威胁性。所以每行可读性可能是一个好的营销决策,即使它是一个糟糕的设计决策。它与让人们分期付款的非常成功的技术同构:不是用高昂的前期价格吓唬他们,而是告诉他们低月付款。分期付款对买方来说是净亏损,而每行可读性对程序员来说可能也是如此。买方将进行很多很多低月付款;而程序员将阅读很多很多单独可读的行。

这种权衡比编程语言更早。如果你习惯于阅读小说和报纸文章,你第一次阅读数学论文的经历可能会令人沮丧。阅读一页可能需要半小时。然而,我很确定符号不是问题,尽管感觉上可能是。数学论文难读是因为思想难。如果你用散文表达相同的思想(正如数学家在发展出简洁的符号之前必须做的那样),它们不会更容易读,因为论文会增长到一本书的大小。

到什么程度?

许多人拒绝了简洁性=力量的想法。我认为与其简单地争论它们相同或不同,不如问:简洁性在多大程度上等于力量?因为很明显,简洁性是高级语言重要目的的大部分。如果不是全部,那么它们还有什么其他目的,相对而言,这些其他功能有多重要?

我提出这个不仅仅是为了让辩论更加文明。我真的想知道答案。如果有的话,语言什么时候会因为过于简洁而对自己不利?

我开始的假设是,除了在病态的例子中,我认为简洁性可以被视为等同于力量。我的意思是,在任何任何人会设计的语言中,它们都是相同的,但如果有人想要设计一种语言来明确反驳这个假设,他们可能能够做到。实际上,我甚至不确定这一点。

语言,而不是程序

我们应该清楚我们讨论的是语言的简洁性,而不是单个程序的简洁性。单个程序当然可能写得太密集。

我在《On Lisp》中写过这个。一个复杂的宏可能需要节省自身长度的许多倍才能证明其合理性。如果编写某个复杂的宏每次使用都能为你节省十行代码,而宏本身是十行代码,那么如果你使用超过一次,你就会在行数上获得净节省。但这可能仍然是一个坏举动,因为宏定义比普通代码更难读。你可能需要使用宏十次或二十次才能在可读性上获得净改善。

我确信每种语言都有这样的权衡(虽然我怀疑随着语言变得更强大,赌注会更高)。每个程序员肯定都见过一些聪明人使用可疑的编程技巧使代码略微缩短的例子。

所以对此没有争论——至少,不是我争论。单个程序当然可能因为过于简洁而对自己不利。问题是,语言会吗?语言能强迫程序员编写在元素上很短但以整体可读性为代价的代码吗?

很难想象语言过于简洁的一个原因是,如果有一些过于紧凑的方式来表达某些东西,可能也会有更长的表达方式。例如,如果你觉得使用大量宏或高阶函数的Lisp程序太密集,如果你愿意,你可以编写与Pascal同构的代码。如果你不想在Arc中将对高阶函数的调用表示为阶乘(rec zero 1 * 1-),你也可以写出递归定义:(rfn fact (x) (if (zero x) 1 (* x (fact (1- x)))))

虽然我一下子想不出任何例子,但我对语言是否可能过于简洁的问题感兴趣。是否有语言强迫你以难以理解和晦涩的方式编写代码?如果有人有例子,我会很感兴趣看到它们。

(提醒:我寻找的是根据上面勾勒的"元素"衡量标准非常密集的程序,而不仅仅是因为可以省略分隔符并且所有东西都只有一个字符名而简短的程序。)


日文翻译 | 俄文翻译 Lutz Prechelt:七种语言比较 Erann Gat:Lisp vs Java Peter Norvig 尝试 Prechelt 测试 Matthias Felleisen:语言的表达力 Kragen Sitaker:冗余与力量 Forth | Joy | Icon | JK