计算机编程语言必须能够自举吗?

设计一种新的计算机编程语言的时候,用新设计的语言编写语言自己的编译器或解析器是不是编程语言设计过程中必须的一步? 当有一种新的编程语言火起来的时候, 经常看到有新闻说用该语言重写了自己的编译器。这么做除了证明这个语言可以自举以外还有什么意义? 有没有不能自举的编程语言(不是指没有人写,而是指从理论上就不能)?
关注者
160
被浏览
15636
目前一共有三种类型的编程语言:编译型、解释型、虚拟机型。
其中,编译型和虚拟机型语言是可以自举的,而解释型语言是不能自举的。其中,编译型语言可以完整自举。不过虚拟机型语言的自举是不完整的——它不能自己实现自己的虚拟机。

举个例子,bash脚本是一种解释型语言,每次执行都需要/bin/bash去读取每条指令并执行对应的操作。如果bash脚本想要自举,就必须用脚本去实现本身应该由/bin/bash实现的功能,但是无论脚本怎么努力,它都不可能做到——因为脚本自身是无法脱离/bin/bash独立运行的。即使你用bash脚本实现了脚本解释器,你也必须要另外一个由其他编程语言编写出的/bin/bash来启动这个解释器。这样根本不能称为自举。

再来看虚拟机语言。Java的编译器是Java写的,而它的任务是把.java源文件编译成.class字节码。然后,这些字节码可以在Java虚拟机中执行。只要有一个java虚拟机和一个事先编译好的java编译器,我们就可以把Java编译器的源代码由.java编译成.class,然后在虚拟机中执行这些.class,把其他Java源代码编译成.class。这样就实现了自举。
不过,Java的自举并不完整,因为Java语言的.class文件始终无法脱离Java虚拟机独立运行,而这个虚拟机必须是其他编程语言实现的。但是这确实是一种自举。如果我们用虚拟化的观点来看待虚拟机,完全可以把Java虚拟机视为一台特殊的电脑,这台电脑上只能运行Java字节码。这样,只要Java语言实现了能在这台电脑(Java虚拟机)上编译出用Java写的Java编译器,那它就完成自举了。而这台电脑(Java虚拟机)到底是怎么制造出来的,自举的时候可以不关心。比如,不排除未来会有人研发直接运行Java虚拟机指令集的硬件设备。

(说句题外话,其实在x86这样的复杂指令集cpu里,“机器语言”也是解释执行的。x86 cpu会把机器语言即时编译为“微指令”,真正被执行的不是机器语言而是微指令。但是这并不影响x86上的C语言的一种编译型语言。解释型语言和编译型语言的区别在于程序运行的方式,是从源代码直接运行还是从目标代码运行,而目标代码到底是怎样运行的,与编程语言是解释型还是编译型其实没有关系。)

编译型语言就不用说了,它们的自举是最彻底的,只依赖于特定的硬件设备(比如PC机)。不过虚拟化的广为流行让“硬件设备”的概念开始变得模糊了。大量真实的操作系统其实并非直接运行在物理计算机上,而是运行在虚拟机里。那么,在虚拟机中工作的C语言是否是一种“虚拟机型语言”,就变得很有趣了。

特别是,如果我们实现一种特别的虚拟机,它采用的指令集是目前没有硬件实现的(比如开发一种类似于x86但是与x86不兼容的指令集,以至于为它编译的程序只能运行在虚拟机中)。然后,我们为该虚拟机实现C语言,那此时,运行在该虚拟机中的C语言就变成了“虚拟机型语言”。有人说应该把虚拟机型语言视为解释型语言,这是不准确的。比如该例中为虚拟机实现的C语言通常被视为编译型语言。它与其他编译型语言的唯一区别是,它的目标指令集是只有软件实现,而没有硬件实现的。Java虚拟机正是这样一种“目前不存在硬件实现”的虚拟机。

从这个角度来看,其实没有“虚拟机型语言”,虚拟机型语言应该被视为编译型语言,只是它编译出来的代码只能在“用软件实现的计算机”上运行罢了。
同样,这个全新的角度也可以解决类似“即时编译”型语言到底是什么类型的问题。在这里,应该把即时编译型语言视为解释型语言,因为它产生中间代码只是为了加快解释速度,它并没有中间代码的输出,并且也不能从中间代码直接运行一个程序。

于是,对这个问题的回答也就变成了:
1、解释型语言不能自举。解释型语言是指那些每次都从源代码开始运行的语言,无论它们运行过程中是否产生中间代码。因为解释型语言即使产生了中间代码也不会保存,下次并不能从中间代码直接运行程序,因此无法脱离解释器独立运行。“用某种解释型语言去实现它自己的解释器”看上去就是一个玩笑。虽然确实可以写出来,但是为了运行该解释器,我们必须先运行另一个解释器。然后,如果另一个解释器也是该语言写出来的,我们为了运行这个解释器,就必须先运行下一个解释器……如果我们希望运行的所有解释器都是该语言写出来的,那么,我们将永远也无法运行该程序。因此,解释型语言无法自举。

2、编译型语言可以自举。编译型语言是指那些在运行前需要先把程序由源代码转换成另一种形式,并且只运行转换后的形式的一种编程语言。无论该语言是运行在通常的计算机硬件上还是特殊的“虚拟计算机”上,只要它能在该运行平台自我编译,它就实现了自举。此时,我们可以安全的删除先前由其他语言写成的编译器,只保留自举编译器。自举编译器可以在该平台独立运行,并且编译该编译器自己的源代码,从而得到新版本的自举编译器。

————附加的内容————

其实,“C语言是编译型语言”这种说法是不准确的,因为现在确实有C语言解释器的存在,我就曾经用过一款。运行在该解释器上的C语言毫无疑问是一种解释型语言。

同样的道理,我们也可以编写一款“bash脚本编译器”,把bash脚本直接编译为可独立运行的目标代码,从而让bash脚本变成编译型语言。

此外还有,Lua这样的语言既可以从源代码直接运行,也可以先输出编译后的字节码,然后从字节码运行。所以它既是解释型语言,又是编译型语言。

因此,在讨论某个特定的编程语言到底是解释型还是编译型这个问题时,我们需要结合当前实际的运行方式来确实。不过,这并不是该问题的主题。