本文转载自微信公众号《神说必有光zxg》,作者说必有光zxg。转载此文请联系大神说必有光zxg公众号。作为程序员,我们会接触到各种语言:会用Javascript、Typescript写前端应用,会用Java、Go等写后端应用,会用Python写一些工具脚本。每种语言都有自己的语法和专业领域。不同的编程语言之间有什么区别?编程语言的本质是什么?在这篇文章中,我们尝试探索。不同的语言从硬件到语言,最终都是控制计算机的一些硬件进行工作,从硬件层面来说,它们之间没有区别。各种语言只是描述逻辑和API封装的不同方式,底层都运行在同一套硬件上。你在硬件层面做了什么?以打印机为例。它是一种机械结构,只是机械的工作是电子控制的,即0和1的电信号。一般控制它工作的芯片都有不同的引脚,高低电平代表不同的含义,那么打印机就可以通过高低层次控制做不同的工作。控制硬件工作的程序称为驱动程序,它封装了硬件的能力并提供了各种API。这样我们就可以通过程序来控制各种硬件了。这些硬件中最特别的是CPU。其他硬件提供的指令控制硬件的工作,而CPU提供的指令确实可以描述各种逻辑,读写内存,进而控制其他硬件。这样我们就不需要直接控制其他硬件,而是可以通过CPU间接控制各种硬件,所以叫中央处理器。这些机器指令称为指令集,它们所描述的逻辑就是机器语言。硬件通过电子控制机械,提供驱动程序,再通过CPU实现各种通用逻辑来控制其他硬件。CPU提供的指令集所描述的逻辑称为机器语言,是我们编写的程序的最底层。你为什么要有操作系统?你不能在一台计算机上只运行一个程序。那是最早的计算机,现在的计算机都支持多程序并发。但是不管运行了多少个程序,都是在同一个硬件上运行,只是顺序和优先级是有安排的。这叫做调度,实现多个程序调度的程序就是操作系统。既然是让多个程序运行在同一个硬件上,那么首先要给运行的程序起一个名字,这个名字叫做进程。进程调度是CPU做的主要事情。进程需要使用各种硬件,这也需要内存调度、I/O调度等。简而言之,操作系统主要做的就是支持程序的并发执行,统一管理各种资源,实现进程、内存、IO等的调度。同时,为了安全性,操作系统会将程序的运行状态分为用户态和内核态。只有内核态才能访问驱动程序来控制硬件,然后提供系统调用给用户态使用,因为任何程序如果可以随意操作硬件是不安全的,所以必须对其进行控制。为什么我们在谈论编程语言时要谈论操作系统?因为我们编写的应用层代码是运行在操作系统上的,我们使用的各种API最终都是由操作系统提供的系统调用来实现的。但是我们不是直接使用系统调用,而是使用各种语言的标准库。这些标准库进一步封装了系统调用,如创建进程、访问网络、访问内存等。Node.js和JDK的API都是基于系统调用进行封装的。操作系统实现了多个程序在同一套硬件上的并发执行。为了安全,它还将程序的运行分为内核态和用户态,并提供系统调用来使用操作系统的能力。各种语言都对系统调用做了封装,这样我们就可以通过这些API来控制计算机。编程范式和描述我们谈到了如何通过机器语言控制CPU来控制其他硬件,以及操作系统的功能和它提供的系统调用是如何被编程语言封装起来的。这些是我们能够控制计算机的基础。但是我们仍然停留在机器语言中。用这个写逻辑太麻烦了。我们要考虑逻辑怎么表达,计算机怎么执行,比如访问哪个寄存器,读写哪个内存等等,能不能简化?首先想到的是把机器语言做成一些有意义的字符串,叫做汇编语言,这样描述起来就简单多了。但这还是要考虑两个方面:表达的逻辑和计算机执行的细节,执行细节与逻辑无关,不同的机器和操作系统的执行细节也可能不同。我可以只表达逻辑,然后以某种方式将其转换为具有执行细节的机器语言吗?这是一门高级语言,其特点是没有具体的执行细节,只关心逻辑的表达。这个改造就是编译器。(当然也可以做成解释执行其他程序的中间程序,称为解释器。)描述逻辑有不同的方式。比如我可以通过函数来??组织逻辑,把数学思维接过来。这叫做函数式,逻辑也可以通过对象来组织,这叫做面向对象。这些描述逻辑的不同想法就是编程范式。编程语言主要实现一定的编程范式,使程序员可以用不同的方式描述逻辑,编译器将其转换成具有计算机执行细节的机器码。不同的语言实现的编程范式不同,即描述逻辑的方式不同。这是语言之间最大的区别。至于能做什么,这个没什么区别,只要把系统调用封装起来做成一些库就可以支持了。比如Javascript一开始只能运行在浏览器上,描述渲染逻辑,后来有了Node.js,也可以用来描述一些脚本或者服务端逻辑。像现在的跨端引擎,不就是把操作系统的能力封装起来,通过Js描述逻辑,然后通过native调用操作系统的能力吗?还有electron、hybrid等,都是Javascript运行时。他们扩展的是一个api,并没有扩展js语言本身。那么是什么扩展了Javascript语言本身呢?它就是Typescript,一种编译成Javascript的语言,它提供了一个类型系统,可以静态地检查程序中的一些错误。为了将逻辑的表达与程序执行的细节分离,我们实现了一种高级语言,让程序员只需要专注于逻辑的表达,然后通过编译器将其转化为带有执行细节的机器语言代码/口译员。逻辑表达方式有不同,比如面向对象、函数式等,每种编程语言都会实现其中的几种,这是语言之间最大的区别。语言只是用来表达逻辑的。至于能做什么,那是API的事情。只要封装了系统能力,就可以扩展其他的API,然后就可以写这个领域的逻辑了,比如Node.js、Electron、跨终端引擎等都是对api的扩展。综上所述,我们从硬件、操作系统、编程范式三个层面讨论了编程语言的本质:硬件使用电控机械,通过驱动程序驱动硬件工作,CPU可以描述通用逻辑,进而控制其他硬件。它通过控制CPU来间接控制各种硬件,故称为中央处理器。它提供的指令集所表达的逻辑称为机器语言。操作系统实现程序的并发执行,让多个程序同时运行在一组硬件上,称为进程。操作系统支持进程、内存、IO等各种调度。为了安全起见,程序的执行分为两种状态:用户态和内核态。内核态可以通过驱动来控制硬件,然后做成系统调用暴露给用户态。各种语言的标准库通过系统调用来使用操作系统的能力。机器语言是我们控制计算机最基本的方式,但它既需要逻辑的表达,也需要计算机执行的细节。为了把这两者分开,我们做了一个编译器/解释器来完成这个转换,这样我们只需要表达逻辑就可以了,这叫做高级语言。有不同的描述逻辑的方式,称为编程范式,每种编程语言都实现了一定的编程范式。不同的编程语言之间的区别只是表达逻辑的方式不同。至于可用的API,可以通过库或运行时进行扩展。那么,如果你被要求制作一种编程语言,你会怎么做?你应该首先选择一种编程范式,用它来表达逻辑,然后再设计详细的语法。然后执行编译器/解释器将其转换为控制计算机操作的机器语言。之后,必须实现一个封装操作系统能力的标准库。那么如果要表达不同领域的逻辑,就需要实现一些不同领域的库,比如图形,桌面,web服务器等等。这是实现一门编程语言的思路,也是我们理解一门编程语言的思路。
