为什么是 Lisp?

本文是翻译,版权归原作者所有


原文地址(original source):http://blog.rongarret.info/2015/05/why-lisp.html


关于昨天我在 Hacker News 上的一条评论,很多人联系到我,要求我详细解释一下,比如:

我的印象是,Lisp “只是”一种不同的记号表示法。这是正确的,还是我漏掉了什么?我没有看到 Lisp 代码匹配数据结构(我的假设就是,这种匹配就是“为什么是 Lisp”的答案)为什么是重要的------我忽视了宏的重要性,或者还有我没注意到的地方吗?

这个问题的答案太长了,因此我打算写成一篇博文。

答案的简短版本是,Lisp 不只是一种不同的记号表示法,对于思考编程是什么,它是一种本质上不同的方式。主流模式是一种设计,由输出独立构件的、称之为程序(program)的东东组成,它操作其它的构件,称之为数据(data)。当然,每个人都理解程序就是数据,但主流模式是围绕这两种概念之间的、人为的区别。的确,程序就是数据,不过它们只是为某种特别的程序准备的,它叫编译器。编译器编写起来难度高,对于它们而言,这是一个研究领域。大部分人不写他们自己的编译器(偶尔的教学练习除外),使用少数人写的编译器,而这些人玷污了需要编写一个不只是玩具的编译器所需要的精通程度。

Lisp 的模式是,编程更像是和一台机器所产生的一种更宽泛的交互。对于你想让机器做什么的描述,这种行为就是,机器实际上做着你已经描述的步骤、观察结果、然后根据观察去修改你想要机器做什么的描述。在程序被完成和程序本身成为一个构件之间,没有明确的分界线。当然,在 Lisp 里画出这样一条线并输出独立的可执行程序是有可能的,这种可能就像在 C 里编写交互程序一样。但是 Lisp 的意图是可交互(因为发明它是为了支持人工智能的研究),而 C 不是(因为它是为编写操作系统才发明出来的)。交互性对于 Lisp 是固有的,对于 C 则是不相关的,正如开发独立的可执行程序是 C 固有的、而和 Lisp 不相关。

当然,有很多次,你除了迭代别无选择。有时候你不明白产生一份完整设计的各个方面,这是你需要知道的,因此你不得不做一些实验,实验做得越快,你的状况就越好。把一些小程序组合成大点儿的程序,这种通用的机制是非常有帮助的,C 里就有这样的机制:管道。然而,C 里没有的东西是序列化和反序列化数据的标准方式。尤其是,C 没有序列化和反序列化层次化数据的标准方式。不过,C 有相当宽泛的、不同种类的序列化格式:固定宽度、分隔符分隔、MIME、JSON、ICAL、标准通用标记语言(Standard Generalized Markup Language,SGML) 及其延伸 HTML 和 XML【注1】,不再一一列举。这些只是数据的序列化格式。如果你想写代码,每种编程语言都有自己特质的语法。

C 的生态系统已经引发了独特的心态,认为语法是重要的。很多精力耗在了语法设计上。像 LEX 和 YACC 之类的工具被广泛使用【注2】。对于 C 而言,编写分析器是任何程序员生命中的最大部分。

时不时地,C 世界里有人产生了伟大的想法,试图使用其中一种数据序列化格式来表示代码。这些努力都不是长久的,因为与使用为表示代码而专门设计的语法来表示的代码相比,那些使用 XML 或 JSON 表示的代码看起来是相当恐怖的。他们得出的结论是,把代码当做数据来表示是一种糟糕的想法,然后回去继续编写分析器。

但是,他们错了。

用 XML 或 JSON 表示的代码看起来糟糕的原因,不是因为把代码当做数据来表示是可怕的想法,而是因为 XML 和 JSON 是糟糕地设计出来的序列化格式。它们被糟糕地设计出来的原因非常简单:符号太多。对于 XML,还有过多的重复。Lisp 能够在其它语法失败的地方成功地把代码当做数据来表示的原因在于 S 表达式【注3】是精心设计的序列化格式,它被精心设计的原因在于它是最小化的。对比一下:

**XML**: abcpqrxyz **JSON**: ['abc', 'pqr', 'xyz'] **S 表达式**: (abc pqr xyz)

在这个例子中,XML 可怕的臃肿就已经很明显了。JSON 和 S 表达式之间的区别稍稍有些微妙,但是请考虑:这是一个合法的 S 表达式。

(for x in foo collect (f x))

JSON 的同等写法为:

['for', 'x', 'in', 'foo', 'collect', ['f', 'x']]

XML 的同等写法就留作练习了。

如果你尝试打出这些表达式而不只是盯着看,那么其中差别就尤为明显了。(试一下!)引号和逗号,对于小的数据结构貌似不足以造成伤害,对于任何真正复杂的表达式,它们马上就变成了无法忍受的负担(当然,和所有 SGML 派生品一样,XML 只是完全没有希望)。

Lisp 如此强悍的原因在于,引导人们尽量把代码当做数据来表示的意图是真正正确的。它是一个强大得让人难以相信的杠杆。因此,为它们发明新语言和编写解释器、编译器就成了 Lisp 日常编程的一部分,正如编写分析器是 C 世界里的普通工作。不过为了让它运行,你必须从把代码当做数据来表示的合适语法开始,这意味着你必须从一种最小化的语法开始,因为任何其它的语法都会让你陷入逗号、引号和尖括号的海洋。

这意味着你必须从 S 表达式开始,因为它们是表示层次化数据的最小化语法。请思考:为了表示层次化数据,你需要两种语法元素:标记分隔符(token separator)和区块定界符(block delimiter)。在 S 表达式里,空格是标记分隔符,小括号是区块定界符。就这么多,这是最小的了。

值得提出的是,小括号在 Lisp 里如此突出的原因,不是 Lisp 比其它编程语言有更多的小括号,而是因为 Lisp 只使用了一种区块定界符(小括号),所以小括号就非常突出了,因为没有其它区块定界符了。其它语言根绝要被定界的区块,有着不同的区块定界符。比如,在类似 C 的语言里,() 用作参数列表和子表达式,[] 用作数组、{} 用作代码区块和字典【注4】,它还使用逗号和分号做为区块定界符。如果你比较 apples 和 apples,那么 Lisp 通常使用比类似 C 的语言更少的区块定界符。尤其对于 Javascript 而言,callback 随处可见,经常陷入较深的定界符麻烦当中,然后它变成了程序员认知上的负担,他要根据上下文弄清楚要放入的正确定界符。Lisp 程序员从来不必担心这种事情:如果你想关闭一个区块,就敲入一个“)”。这常常不费脑子,从而让 Lisp 程序员把更多精力聚焦在他们实际要解决的问题上。

说到这里,我可能应该回去写代码了。当然是迭代的。


译文:为什么是 Lisp? 》| 腊八粥