Loading...

简洁就是力量

Original

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)无关。那么,不同语言之间的区别就变成了源代码的体积。

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

味觉测试

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

这种高度敏感会有代价。你会发现你无法忍受在笨拙的语言中编程。我发现,在没有宏的语言中编程是令人无法忍受的限制,就像习惯于动态类型的人发现不得不回到一个必须声明每个变量类型的语言中编程是令人无法忍受的限制,无法制作不同类型对象的列表。

我并不是唯一一个。我知道许多Lisp黑客都经历过这种情况。事实上,编程语言相对力量的最准确衡量标准可能是知道该语言的人中愿意接受任何使用该语言的工作的百分比,无论应用领域如何。

限制性

我认为大多数黑客都知道语言感觉限制性意味着什么。当你感到这种情况时,发生了什么?我认为这与当你想走的街道被封锁时的感觉是一样的,你不得不绕远路才能到达你想去的地方。你想说的某些东西,语言却不允许你说。

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

我认为大多数(90%?)限制感来自于被迫使你在语言中编写的程序比你脑海中的程序更长。限制性主要是缺乏简洁性。因此,当一种语言感觉限制时,这(主要)意味着它不够简洁,而当一种语言不够简洁时,它会感觉限制。

可读性

我开始时引用的那句话提到了另外两个特性,规律性和可读性。我不确定规律性是什么,或者规律性和可读性代码相比有什么优势(如果有的话)。但我认为我知道可读性是什么意思,我认为它也与简洁性有关。

我们必须小心区分单行代码的可读性和整个程序的可读性。后者才是重要的。我同意一行Basic代码的可读性可能比一行Lisp代码更高。但用Basic编写的程序行数将比用Lisp编写的同一程序多(尤其是当你进入Greenspunland时)。阅读Basic程序的总工作量肯定会更大。

总工作量 = 每行工作量 x 行数

我不太确定可读性是否与简洁性成正比,但我确定力量是的,但简洁性无疑是可读性的一个因素(在数学意义上;见上面的方程)。因此,可能甚至没有意义说语言的目标是可读性,而不是简洁性;这可能就像说目标是可读性,而不是可读性。

每行可读性对第一次接触该语言的用户意味着源代码将看起来不具威胁性。因此,每行可读性可能是一个好的市场决策,即使它是一个糟糕的设计决策。这与让人们分期付款的非常成功的技术同构:而不是用高昂的前期价格吓到他们,你告诉他们低廉的月供。然而,分期付款对买家来说是净损失,正如每行可读性对程序员来说可能是的那样。买家将会支付很多这样的低月供;而程序员将会阅读很多这些单独可读的行。

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

在多大程度上?

许多人拒绝了简洁性 = 力量的观点。我认为,除了简单地争论它们是否相同之外,问一下:简洁性在多大程度上 = 力量?因为显然简洁性是高级语言的一个重要部分。如果它不是它们的全部目的,那么它们还有什么目的,这些其他功能相对有多重要?

我并不是为了让辩论更文明而提出这个问题。我真的想知道答案。当一种语言是否过于简洁以至于对其自身不利时?

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

语言,而不是程序

我们应该明确我们在谈论语言的简洁性,而不是单个程序的简洁性。单个程序确实可能写得过于密集。

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

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

所以对此没有争论——至少我没有。单个程序确实可能对其自身不利。问题是,语言会不会?一种语言能否迫使程序员编写短(在元素上)的代码,而牺牲整体可读性?

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


(rec zero 1 * 1-)

你也可以写出递归定义:


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

虽然我现在无法想到任何例子,但我对语言是否可能过于简洁的问题很感兴趣。是否有语言迫使你以一种狭隘和难以理解的方式编写代码?如果有人有例子,我将非常感兴趣。

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