“Lisp can be written in itself”是什么意思?

25
保罗·格雷厄姆写道,“Lisp的不寻常之处——实际上是Lisp的定义特点——在于它可以用自身编写。”但对我来说,这似乎一点也不不寻常或者具有决定性的。
在我看来,编程语言由两个方面定义:其编译器或解释器,通过法令定义了语言的语法和语义,以及其标准库,这在很大程度上定义了熟练用户在使用该语言编写代码时使用的惯用语和技术。
除了少数特殊情况(例如.NET家族的非C#成员),大多数语言的标准库都是用该语言编写的,有两个非常好的理由:因为它将共享相同的语法定义、函数调用约定和该语言的总体“外观和感觉”,以及因为编写编程语言的标准库的人很可能是其用户,特别是其设计者。所以这里没有什么独特之处;这是相当标准的。
再次强调,一个语言的编译器被自身编写并不是独特或不寻常的。C编译器是用C编写的。Pascal编译器是用Pascal编写的。Mono的C#编译器是用C#编写的。甚至一些脚本语言也有“用自身编写”的实现。
那么,Lisp被称为是用自身编写的不寻常在哪里呢?

6
如你所说,自托管编译器或解释器并不是什么大新闻。相比之下,一种抽象语法树本身就是代码的语言,其本身作为数据可以完全被操纵,却是一种截然不同的东西。通过查看麦卡锡的论文,保罗·格雷厄姆做出评论,没有任何合理的质疑这种对称性是值得注意的。 - Charles Duffy
2
https://dev59.com/J3M_5IYBdhLWcg3wNgKY - Vijay Mathew
5个回答

12

保罗可能的意思是,将Lisp语法表示为Lisp值已经被标准化并且普遍存在。也就是说,一个Lisp程序只是一种特殊类型的S表达式,编写操作Lisp代码的Lisp代码非常容易。在Lisp中编写解释器只是一个特例,与统一表示代码和数据的一般能力相比不那么令人兴奋。


8
你所描述的是语言具有同构属性(homoiconic),这是其他语言也共有的特征。 - Greg Hewgill
@Greg:我一直对TRAC(以及它的后继者MINT)有着特殊的感情,但我的感觉是那些程序员从未像Lisp程序员那样充分利用同构性... - Norman Ramsey

10

我刚刚删除了一篇非常冗长的回复,可能在这里不合适。

但是请考虑以下几点:

  1. 如果您的意思是像 C/Java/Pascal 等语言那样,“LISP 没有‘语法’” 的话,那么 LISP 是没有“语法”的。(1) 对于 Common LISP 读取器来说,有一个(最初的但可自定义的)语法,但那是另一回事 (Graham 所说的 LISP 不是“Common LISP”,阅读器也不是 LISP 语言,只是一个过程)。例如“(lambda (x) (* x 2))”这样的内容不是 LISP 代码,而是文本,可以由 CL 标准读取器转换为 LISP 代码。

  2. LISP 不仅可以用 LISP 编写(如果您的意思是“引导”能力),而且 它实际上就是这样诞生出来的。eval 的第一个实现是在1950年代末在纸上用 LISP 编写的,然后手动转换成机器语言(2):LISP 起源于一个纯粹的理论想法,不是要实现的。我不知道其他遵循这条路线的计算机语言。例如,C++ 被构想为 C 编译器的预处理器,并且是用 C 编写的,它不是后来转换为 C 的 C++ 程序以便运行的。

LISP 在许多其他方面都很不同,我认为最好的方法是实际上实现一个玩具 LISP 解释器(如果您的“机器语言”是像 Python 这样的高级动态类型语言,则它的工作量比人们想象的要小得多)。

(1) 实际上,LISP中有两种预定义的语法层次。第一种是阅读器的语法,即定义了如何将源代码字符转换为s表达式的规则;第二种是编译器在生成实际机器代码时理解s表达式的规则。但如果LISP中有两种语法层次,为什么说LISP没有语法也是正确的呢?原因是它们都不是固定的。例如,第一层由标准Common Lisp阅读器处理,使用“读取表”可以定制化,在源代码中找到特定字符时执行您的代码。第二层可以使用宏、符号宏和编译器宏进行定制,从而定义新的语法结构。换句话说,LISP没有固定的语法,可以编写一个LISP程序,开始时是标准的LISP,经过一段时间后变成与Python完全相同(我并没有杜撰,曾经有一个名为cl-python的实现支持这种混合源模式:任何以开括号开头的都被认为是使用LISP语法,其他字符则使用Python语法)。

(2) 在http://www-formal.stanford.edu/jmc/history/lisp/node3.html中,您可以找到麦卡锡是如何描述eval[e, a]首先在纸上被发现的,作为一个有趣的理论结果(比通用图灵机更简洁的“通用函数”实现),当Lisp语言的数据结构和基本本地函数已经确定时,该小组正在构建。这个手写函数由S.R. Russell手工以机器代码实现,并开始作为第一个Lisp解释器为他们服务。


6

好的,您提供的链接确实会继续说,如果您继续阅读,他将详细回答您的问题。

关于Lisp的不寻常之处-实际上,Lisp的定义特征-就在于它可以用自身编写。为了理解麦卡锡(McCarthy)这句话的含义,我们将重复他的步骤,并将他的数学符号翻译成运行中的通用Lisp代码。


2
是的,然后文章就结束了。 - Mason Wheeler
@ Mason:您可以下载PostScript文件,然后在此处将其转换为PDF:http://www.ps2pdf.com/convert.htm,最后使用Adobe Reader阅读。它大约有13页。 - Robert Harvey

6
他并不是说Lisp可以用来编写Lisp编译器。他是在说该语言由其自身的数据结构组成。因此,虽然您无法通过C数据结构构建C函数,但在Lisp中却可以。程序由计算机执行的列表组成,这些列表的效果可以创建其他要执行的列表,而这些列表的效果又可以创建更多要执行的列表。C没有这个属性。例如,C代码无法操作其自身的AST

1
C代码可以操作自身。实际上,我相信自修改代码在一段时间内非常流行。 - Ponkadoodle
5
这里有一个微妙的区别,涉及到"语言"和"实现"两个概念。C 代码不能操作它自己,但是可以操作它所实现的机器语言代码。这与在 Lisp 中操作 Lisp 代码完全不同,并处于完全不同的抽象层级。 - Daniel Pryden
哦,关键词是AST,这可能是一个编辑或者我错过的东西。抱歉,并感谢澄清。 - Ponkadoodle

5
主要思想是,语言Lisp只有少数几个函数的核心,并且主要的评估机制可以在单页代码中编写。
这就是Lisp的核心。
要理解该语言的基本工作原理,只需要查看那一页的代码:它解释了变量如何工作,函数调用如何工作,如何创建新函数等等。
想一想,你可以从语言实现中删除多少内容才能达到基本要求。什么是最小的原始集合,什么是最小的执行引擎。要在Lisp中编写Lisp,你可以减少到几乎没有东西。
如果你看看PASCAL实现(以此为例),它并不是用PASCAL实现的,而是用PASCAL + 更多的代码来提供必要的数据结构来表示语言实体、解析器、编译器、运行时等等——PASCAL本身并没有提供太多东西——必须建立这些(或使用库)。
Wirth(Pascal的创建者)写了一本书,讲解了一个非常小的类似Pascal的语言的实现——你仍然需要编写大量的代码来实现该语言——解析器、代码生成器、运行时、加载器等等。
相比之下,在Lisp中,Lisp源代码具有自然表示形式,评估Lisp代码的核心程序只是一个Lisp函数。虽然这可能不是真正的或实用的Lisp实现,但它与PASCAL情况不同,其中源代码没有有用的表示形式(除了字符串或文件),执行引擎也需要更多的代码。
因此,在Lisp中我们有:
- 源代码的简单表示形式(符号列表) - 评估器的简单实现,只需一个小的函数
除此之外,在Lisp中实现Lisp评估器所需的其他内容都不需要。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接