简洁即力量

2002年5月

| “代数符号将大量意义压缩到小空间,这是我们习惯于通过它们进行推理的另一个便利。”

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

在LL1邮件列表上关于《书呆子的复仇》引发的问题的讨论中,保罗·普雷斯科德写了一些让我印象深刻的话。

Python的目标是规则性和可读性,而不是简洁性。

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

Python的目标是规则性和可读性,而不是力量。

这似乎不是一个你想要做出的权衡(如果它确实是一个权衡)。这几乎等于说Python的目标不是作为一种编程语言有效。

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

假设

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

在我看来,简洁性是编程语言的_目的_。计算机同样乐意直接用机器语言被告知要做什么。我认为我们费心开发高级语言的主要原因是获得杠杆作用,这样我们可以用10行高级语言说(更重要的是,思考)需要1000行机器语言的内容。换句话说,高级语言的主要点是使源代码更小。

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

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

度量标准

但在什么意义上小呢?代码大小的最常见度量是代码行数。但我认为这个度量标准最常见是因为它最容易测量。我不认为有人真的相信它是程序长度的真正测试。不同的语言有不同的约定,关于你应该在一行上放多少东西;在C中,很多行上除了一个或两个分隔符外什么都没有。

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

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

这个度量标准需要充实,对于特定语言可能需要解释,但我认为它试图衡量正确的东西,即程序有多少部分。我认为你在这个练习中画的树是你必须在头脑中构建的,以便构思程序,因此它的大小与你必须做的工作量成比例来编写或阅读它。

设计

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

如果程序的概念负载与其复杂性成比例,而给定的程序员可以容忍固定的概念负载,那么这与问,我可以做什么使程序员能够完成最多的工作?这对我来说似乎等同于问,我如何设计一个好的语言?

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

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

比较

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

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

我还没有看到在我看来是决定性的证据,我也不期望看到。像Lutz Prechelt的编程语言比较这样的研究,虽然产生了我预期的结果,但倾向于使用太短的问题作为有意义的测试。对语言的更好测试是在需要一个月编写的程序中发生的事情。如果你像我一样相信语言的主要目的是思考(而不仅仅是在你想好后告诉计算机做什么),那么真正的测试是你能用它写出什么新东西。所以任何你必须满足预定义规范的语言比较都是在测试稍微错误的东西。

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

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

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

爱立信内部开发项目之间的比较表明,无论使用哪种语言(Erlang,PLEX,C,C++,或Java),行/小时的生产力相似,包括软件开发的所有阶段。那么不同语言的区别在于源代码量。

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

品味测试

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

这种超敏感性会有代价。你会发现你无法_忍受_用笨拙的语言编程。我发现没有宏的语言编程难以忍受地限制,就像习惯动态类型的人发现必须回到必须声明每个变量类型,并且不能制作不同类型对象列表的语言编程难以忍受地限制一样。

我不是唯一一个。我知道许多Lisp黑客也这样。事实上,衡量编程语言相对力量的最准确标准可能是知道该语言的人中有多少比例会接受任何能让他们使用该语言的工作,无论应用领域如何。

限制性

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

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

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

可读性

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

在这里我们必须小心区分单行代码的可读性和整个程序的可读性。重要的是第二个。我同意一行Basic可能比一行Lisp更可读。但用Basic写的程序将比用Lisp写的相同程序有更多的行(特别是当你进入Greenspunland后)。阅读Basic程序的总努力肯定会更大。

总努力=每行努力x行数

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

每行可读性对第一次接触语言的用户来说意味着源代码看起来_不吓人_。所以每行可读性可能是一个好的营销决策,即使它是一个糟糕的设计决策。它与让人们分期付款的非常成功的技术同构:不是用高额的前期价格吓唬他们,你告诉他们低的月付款。分期付款计划对买家来说是净损失,尽管每行可读性对程序员来说可能是。买家将做出_很多_那些低,低的付款;程序员将阅读_很多_那些单独可读的行。

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

到什么程度?

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

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

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

语言,不是程序

我们应该清楚我们在谈论语言的简洁性,而不是个别程序的简洁性。个别程序写得过于密集当然是可能的。

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

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

所以关于这一点没有争论——至少,不是来自我。个别程序当然可以过于简洁而对自己不利。问题是,语言可以吗?一种语言可以迫使程序员以整体可读性为代价编写短(在元素上)的代码吗?

一个难以想象语言过于简洁的原因是,如果有某种过于紧凑的表达方式,可能也有一种更长的方式。例如,如果你觉得使用大量宏或高阶函数的Lisp程序太密集,你可以,如果你愿意,编写与Pascal同构的代码。如果你不想在Arc中将阶乘表达为高阶函数的调用(rec zero 1 * 1-),你也可以写出递归定义:(rfn fact (x) (if (zero x) 1 (* x (fact (1- x))))。虽然我一时想不出任何例子,但我对语言是否可以过于简洁的问题感兴趣。有没有语言强迫你以一种难以理解和难以理解的方式编写代码?如果有人有例子,我会非常有兴趣看到它们。

(提醒:我在寻找的是根据上面概述的“元素”度量标准非常密集的程序,而不仅仅是因为可以省略分隔符和所有东西都有一个字符名称而短的程序。)