You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

89 lines
12 KiB
Markdown

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

# 27 | 课前导读:学习现代语言设计的正确姿势
你好,我是宫文学。
到目前为止你就已经学完了这门课程中前两个模块的所有内容了。在第一个模块“预备知识篇”我带你梳理了编译原理的关键概念、算法等核心知识点帮你建立了一个直观的编译原理基础知识体系在第二个模块“真实编译器解析篇”我带你探究了7个真实世界的编译器让你对编译器所实际采用的各种编译技术都有所涉猎。那么在接下来的第三个模块我会继续带你朝着提高编译原理实战能力的目标前进。这一次我们从计算机语言设计的高度来印证一下编译原理的核心知识点。
对于一门完整的语言来说,编译器只是其中的一部分。它通常还有两个重要的组成部分:一个是**运行时**,包括内存管理、并发机制、解释器等模块;还有一个是**标准库**,包含了一些标准的功能,如算术计算、字符串处理、文件读写,等等。
再进一步来看,我们在实现一门语言的时候,首先要做的,就是确定这门语言所要解决的问题是什么,也就是需求问题;其次,针对需要解决的问题,我们要选择合适的技术方案,而这些技术方案正是分别由编译器、运行时和标准库去实现的。
所以,从计算机语言设计的高度来印证编译原理知识,我们也能更容易理解编译器的任务,更容易理解它是如何跟运行时环境去做配合的,这也会让你进一步掌握编译技术。
好了,那接下来就一起来看看,到底用什么样的方式,我们才能真正理解计算机语言的设计思路。
首先,我们来聊一聊实现一门计算机语言的关键因素:需求和设计。
## 如何实现一门计算机语言?
我们学习编译原理的一个重要的目标就是能够实现一门计算机语言。这种语言可能是你熟悉的某些高级语言也可能是某个领域、为了解决某个具体问题而设计的DSL。就像我们在第二个模块中见到的SQL以及编译器在内部实现时用到的一些DSL如Graal生成LIR时的模式匹配规则、Python编译器中的ASDL解析器还有Go语言编译器中IR的重写规则等。
**那么要如何实现一门优秀的语言呢?**我们都知道,要实现一个软件,有两个因素是最重要的,一个是需求,一个是设计。**计算机语言作为一种软件,具有清晰的需求和良好的设计,当然也是至关重要的。**
我先来说说**需求问题**,也就是计算机语言要解决的问题。
这里你要先明确一件事,如果需求不清晰、目标不明确,那么想要实现这门语言其实是很难成功的。通常来说,我们不能指望任何一种语言是全能的,让它擅长解决所有类型的问题。所以,每一门语言都有其所要解决的针对性问题。
举个例子JavaScript如果单从设计的角度来看有很多细节值得推敲有不少的“坑”比如null、undefined和NaN几个值就很令人困惑你知道“null==undefined”的值是true还是false吗但是它所能解决的问题也非常清晰就是作为浏览器的脚本语言提供Web的交互功能。在这个方面它比同时期诞生的其他竞争技术如ActiveX和Java Applet都更具优势所以它才能胜出。
历史上的计算机语言都是像JavaScript那样在满足了那个时代的某个需求以后而流行起来的。其中根据“硅谷创业之父”保罗·格雷厄姆Paul Graham在《黑客与画家》中的说法这些语言往往是一个流行的系统的脚本。比如说C语言是Unix系统的脚本COBOL是大型机的脚本SQL是数据库系统的脚本JavaScript、Java和C#都是浏览器的脚本Swift和Objective-C是苹果系统的脚本Kotlin是Android的脚本。让一门语言成为某个流行的技术系统的脚本为这个生态提供编程支持就是一种定位很清晰的需求。
![](https://static001.geekbang.org/resource/image/f9/f7/f9034be8cf1e452808154bb16b3c7df7.jpg "各种编程语言的产生,以及与技术生态的关联")
好,明确了语言的需求以后,我们再来说说**设计问题**。
设计是实现计算机语言这种软件所要做的技术选择。你已经看到,我们研究的不同语言,其实现技术都各有特点,分别服务于该语言的需求问题,或者说设计目标。
我还是拿JavaScript来举例子。JavaScript被设计成了一门解释执行的语言这样的话它就能很方便地嵌入到HTML文本中随着HTML下载和执行并且支持不同的客户端操作系统和浏览器平台。而如果是需要静态编译的语言就没有这么方便。
再进一步由于HTML下载完毕后JavaScript就要马上执行从而也对JavaScript的编译速度有了更高的要求所以我们才会看到V8里面的那些有利于快速解析的技术比如通过查表做词法分析、懒解析等。
另外因为JavaScript早期只是在浏览器里做一些比较简单的工作所以它一开始没有设计并发计算的能力。还有由于每个页面运行的JavaScript都是单独的并且在页面退出时就可以收回内存因此JavaScript的垃圾收集功能也不需要太复杂。
作为对比Go语言的设计主要是用来编写服务端程序的那么它的关键特性也是与这个定位相适应。
* **并发**服务端的软件最重要的一项能力就是支持大量并发任务。Go在语言设计上把并发能力作为第一等级的语言要素。
* **垃圾收集**由于垃圾收集会造成整个应用程序停下来所以Go语言特别重视降低由于垃圾收集而产生的停顿。
那么总结起来,我们要想成功地实现一门语言,要把握两个要点:第一,要弄清楚该语言的需求,也就是它要去解决的问题;第二,要确定合适的技术方案,来更好地解决它要面对的问题。
计算机语言的设计会涉及到比较多的内容,为了防止你在学习时抓不到重点,我在第三个模块里,挑了一些重点的内容来做讲解,比如前面提到的垃圾收集的特性等。我会以第二个模块所研究的多门语言和编译器作为素材,一起探讨一下,各门语言都是采用了什么样的技术方案来满足各自的设计目标的,从而让你对计算机语言设计所考虑的因素、编译技术如何跟其他相关技术打配合,形成一个宏观的认识。
## “现代语言设计篇”都会讲哪些内容?
这个模块的内容,我根据计算机语言的组成和设计中的关键点,将其分成了三个部分。
**第一部分,是对各门语言的编译器的前端、中端和后端技术做一下对比和总结。**
这样,通过梳理和总结,我们就可以找出各种编译器之间的异同点。对于其共同的部分,我们可以看作是一些最佳实践,你在自己的项目中可以大胆地采用;而有差异的部分,则往往是某种编译器为了实现该语言的设计目标而采用的技术策略,你可以去体会各门语言是如何做取舍的,这样也能变成你自己的经验储备。
**第二部分,主要是对语言的运行时和标准库的实现技术做一个解析。**
我们说过一门语言要包括编译器、运行时和标准库。在学习第二个模块的时候你应该已经有了一些体会你能发现编译器的很多特性是跟语言的运行时密切相关的。比如Python有自己独特的对象体系的设计那么Python的字节码就体现了对这些对象的操作字节码中的操作数都是对象的引用。
那么在这一部分,我就分为了几个话题来进行讲解:
* 第一,是对语言的运行时和标准库的宏观探讨。我们一起来看看不同的语言的运行时和它的编译器之间是如何互相影响的。另外,我还会和你探讨语言的基础功能和标准库的实现策略,这是非常值得探讨的知识点,它让一门语言具备了真正的实用价值。
* 第二,是垃圾收集机制。本课程分析、涉及的几种语言,它们所采用的垃圾收集机制都各不相同。那么,为什么一门语言会选择这个机制,而另一种语言会选择另一种机制呢?带着这样的问题所做的分析,会让你把垃圾收集方面的原理落到实践中去。
* 第三是并发模型。对并发的支持对现代语言来说也越来越重要。在后面的课程中我会带你了解线程、协程、Actor三种并发模式理解它们的优缺点同时你也会了解到如何在编译器和运行时中支持这些并发特性。
**第三部分是计算机语言设计上的4个高级话题。**
第一,是**元编程技术**。元编程技术是一种对语言做扩展的技术相当于能够定制一门语言从而更好地解决特定领域的问题。Java语言的注解功能、Python的对象体系的设计都体现了元编程功能。而Julia语言更是集成了Lisp语言在元编程方面的强大能力。因此我会带你了解一下这些元编程技术的具体实现机制和特点便于你去采纳和做好取舍。
第二,是**泛型编程技术**。泛型或者说参数化类型大大增强了现代语言的类型体系使得很多计算逻辑的表达变得更简洁。最典型的应用就是容器类型比如列表、树什么的采用泛型技术实现的容器类型能够方便地保存各种数据类型。像Java、C++和Julia等语言都支持泛型机制但它们各自实现的技术又有所不同。我会带你了解这些不同实现技术背后的原因以及各自的特点。
第三,是**面向对象语言的实现机制**。面向对象特性是当前很多主流语言都支持的特性。那么要在编译器和运行时上做哪些工作来支持面向对象的特性呢对象在内存里的表示都有哪些不同的方式如何实现继承和多态的特性为什么Java支持基础数据类型和对象类型而有些语言中所有的数据都是对象要在编译技术上做哪些工作来支持纯面向对象特性这些问题我会花一讲的时间来带你分析清楚让你理解面向对象语言的底层机制。
第四,是**函数式编程语言的实现机制**。函数式编程这个范式出现得很早不少人可能不太了解或者不太关注它但最近几年出现了复兴的趋势。像Java等面向对象语言也开始加入对函数式编程机制的支持。在第三个模块中我会带你分析函数式编程的关键特征比如函数作为一等公民、不变性等并会一起探讨函数式编程语言实现上的一些关键技术比如函数类型的内部表示、针对函数式编程特有的优化算法等让你真正理解函数式编程语言的底层机制。
该模块的最后一讲,也是本课程的最后一讲,是对我们所学知识的一个综合检验。这个检验的题目,就是**解析方舟编译器**。
方舟编译器应该是第一个引起国内IT界广泛关注的编译器。俗话说外行看热闹内行看门道。做一个编译器到底有哪些关键的技术点它们在方舟编译器里是如何体现的我们在学习了编译原理的核心基础知识在考察了多个编译器之后应该能够有一定的能力去考察方舟编译器了。这也是学以致用、紧密结合实际的表现。通过这样的分析你能了解到中国编译技术崛起的趋势甚至还可能会思考如何参与到这个趋势中来。这一讲我希望同学们都能发表自己的看法而我的看法呢只是一家之言你作为参考就好了。
## 小结
总结一下。咱们课程的名称是《编译原理实战课》,而最体现实战精神的,莫过于去实现一门计算机语言了。而在第三个模块,我就会带你解析实现一门计算机语言所要考虑的那些关键技术,并且通过学习,你也能够根据语言的设计目标来选择合适的技术方案。
从计算机语言设计的高度出发,这个模块会带你对编译原理形成更全面的认知,从而提高你把编译原理用于实战的能力。