基础知识
这里有些基本的东西需要写清楚,避免因理解不同而产生误解。
现代编程语言分类
运行方式
编程语言通常分为解释型语言和编译型语言。二者存在多方面的区别,能否生成可执行文件是二者较为显著的区别之一。编译型语言能够生成可执行文件,也就是无需特定软件便可直接在操作系统中执行,像常见的 C/CPP 以及较为现代的 Rust 和 Go 都属于编译型语言。而解释型语言则需借助相应的解释器边解释边执行,其典型代表有 Python、Java、JavaScript 等。在性能表现方面,编译型语言一般性能比较优异,甚至可以接近汇编语言的性能水平。解释型语言相对而言性能较差,不过 Java 是个例外,由于其具备静态类型并且引入了大量运行时优化,所以也能达到很强的性能。此外,解释型语言常常容易引入其他语言编写的库,部分解释型语言也因此被称为 “胶水语言”,例如 CPython 就有着丰富的机器学习库,这些库基本都是由 C/CPP 编写的。
内存管理
可以分为有垃圾回收和无垃圾回收两种类型。C/CPP 和 Rust 属于无垃圾回收的常用语言,其中 C/CPP 主要通过手动管理内存的方式来处理内存分配问题,Rust 则采用所有权系统来管理内存。其他多数语言都具备垃圾回收机制,不过由于编译型和解释型语言本身的差异,其垃圾回收的实现方式大不相同。比如在 Java 中,垃圾回收是由 Java Virtual Machine 负责处理的;而 Go 语言会在可执行文件中注入一些程序来跟踪垃圾回收的情况。需要注意的是,并非只有解释型语言才需要额外的运行时来进行垃圾回收,编译型语言同样可能涉及相关机制,只是实现形式各有特点。
范式设计
编程语言可分为函数式语言和面向对象语言。函数式语言最为出名的当属 Lisp 和 Haskell,它们严格遵循数学定义,有着浓厚的学院派风格,在很多大学的数学系都会教授(加拿大高校教的是 Racket,但我没学过)。面向对象语言在日常编程中使用更为普遍,它是一种重要的编程范式,有着封装、继承、多态等核心概念。其中对类的支持是比较常见的体现,但即使像 C 语言这种本身没有原生面向对象支持的语言,也可以通过一定的方式模拟出面向对象编程的特性。
数据类型
就数据类型而言,可从静态和动态、强弱类型这两个维度进行分类,排列组合后会呈现出四种形态。静态类型意味着一个变量在被定义后,只能存储与其定义相符的一种类型的数据,比如一个整型变量不可以再被赋值为布尔值。而动态类型则允许变量存储不同类型的数据。强类型是指不允许隐式类型转换,例如,在没有运算符重载的情况下,字符串加一个数字这种操作是不被允许的。弱类型则允许进行隐式类型转换。常用的 Python 是动态强类型语言,C/CPP 属于静态类型语言,不过 C/CPP 允许部分的隐式转换情况存在,相对来说规则没那么严格。Rust 则属于严格的静态强类型语言,对类型的管控更为严谨。
编译运行流程
和很多程序设计过程类似,编程语言的处理流程通常也是前后端分离的。在前端部分,一般会包含分词、语法解析、语义分析、中间代码生成这些环节。而后端部分则会因语言运行类型的不同而存在极大差异。对于编译型语言而言,后端会对生成的中间代码进行优化,随后编译成符合目标平台的二进制文件。而解释型语言的后端同样会生成相应的中间代码,不过其编译结果是自定义的字节码,需要由虚拟机来执行。虚拟机是一个较为复杂的系统,它旨在将低级指令抽象成更高级的接口,在编程语言领域有很多著名的虚拟机,例如 JavaScript 的 V8 引擎、Java 的 Java Virtual Machine 等。另外,虚拟机在操作系统虚拟化方面也有应用,不过这里就不做过多展开了。整个编程语言的处理流程往往会依据不同语言自身的需求出现各种变化,在实际应用中需要根据具体案例来分析其侧重点以及具体的实现方式。
技术选型
参考以上内容,Felys 的最终选择是:解释型、递归运行、无垃圾回收、类函数式、动态强类型,但最终目标是静态类型。理由如下:
- 解释型语言的制作难度下限低,不需要考虑编译相关的内容,因为没时间学习 LLVM 技术
- 由于是直接递归运行,会有很大的复制开销,虽然内存利用率低下,但实现简单
- 纯函数式过于学术,传统面向对象对于繁杂,最终设计整体类似于 Rust 和 C 语言
- 静态强类型能够保证程序的正确性,暂时使用动态类型是因为实现简单,无需语义分析