跳转至

编程语言

基本概念

编程语言最核心的两个部分是 规范实现

规范和实现的分离让编程语言的开发更加便利。你可以照着现有的规范去实现一门语言,或者设计一份规范让别人来实现。

规范

编程语言的规范定义了语言应该是什么样的,它通常包括

  • 语法:规定了代码的结构
  • 语义:确定了代码的含义
  • 标准库:定义了语言核心功能的接口和行为
  • 其他约定:如内存管理方式、错误处理机制、并发模型等

规范通常以标准文档(如 ECMAScript 标准、Python 的 PEP、C++ 的 ISO 标准)的形式存在。规范确保了不同实现之间的一致性和程序的可移植性。

实现

每个编程语言都至少要有一个实现,否则就只能活在理论中。编程语言的实现方式非常多,比如

  • 通过 编译器 把源代码变成一个可执行文件,比如 C/C++/Rust
  • 通过 解释器 逐行执行源代码,比如 JavaScript/Python
  • 通过 转译器 把源代码翻译成另一种高级语言,然后借用该语言的实现,比如 TypeScript/V 以及早期的 C++
  • 先通过 编译器 把源代码变成字节码,然后在 虚拟机 中运行字节码,比如 Java/C#
虚拟机

虚拟机 含义非常丰富。虽然这里我们只讨论编程语言的实现,但此时 虚拟机 仍然存在多种可能的含义

  1. 运行字节码的程序。这个含义最常见,比如 JVM。在这个含义上 虚拟机解释器 很类似,不过 解释器 执行的是源代码而非字节码。
  2. 编译器基础设施。这个含义比较少见,一般都是指 LLVM。LLVM 全称为低层次虚拟机,但实际上它并不符合传统的 虚拟机 概念,我个人认为这有点滥用术语。LLVM 为编译器实现了一个新的抽象层——LLVM IR,即中间表示,并提供了优化中间表示和将中间表示变成机器码的工具。LLVM 通过这个新的抽象层解耦了编译器的开发工作,使得编译器开发有了明确的 前端后端:前端将 源代码 翻译为 LLVM IR,后端用 LLVM IR 生成 机器码。很多语言都有利用 LLVM 实现的编译器,比如 LLVM 项目自己维护的 C/C++ 编译器前端 CLANG、Rust 的官方编译器 rustc。

其中有些实现方式还有变种,比如

  • 有些使用 解释器/虚拟机 的语言会引入 JIT 技术,可以把反复执行的热点代码动态地编译成机器码从而提高运行速度。这需要语言实现一个 JIT 编译器,典型例子是 JavaScript/Java
  • 有些使用 虚拟机 的语言会引入 AOT 技术,可以提前把部分字节码编译成机器码从而提高启动速度。这需要语言实现一个 AOT 编译器,典型例子是 Java
  • 利用已经实现的 虚拟机,把自己的语言编译成可以在该虚拟机上运行的字节码,典型的例子是 Kotlin

一个语言可以不只一个实现。比如 C++ 有非常多的编译器实现,同时还有解释器实现 虽然没什么人用 C++ 的解释器,并且可以编译为 WASM 在浏览器的虚拟机中运行

生态

对于现代的编程语言,仅有规范和实现是不够的,生态系统也是编程语言重要的组成部分,通常包括

  • 工具链:各种开发工具,比如调试器、构建系统、包管理器、编辑器拓展等
  • 社区与文档:教程、论坛、最佳实践、第三方库和框架
  • 主流实践:社区形成的编程风格、设计模式和惯用法

主要编程语言

如何选择和学习编程语言

每个开发者都会有自己的喜好,在不限定背景的情况下讨论哪个语言更好没什么意义。语言事实上只是 工具,工具的出现是为了 解决问题。你应该根据 遇到的问题自身的需求 去选择最合适的语言。不要畏惧新东西,因为我们只需使用 语言的子集 而非掌握 语言的全部

对于现代的编程语言,通常官方文档就是最好的资料。毕竟编程语言也是个高速发展的领域,书籍/课程从出版/发布的那一刻起就逐渐过时了,但官方文档总是随着语言一起更新的。

不过也有部分语言相关的知识是难以通过只看文档掌握的,比如应该选择什么工具/库/框架、最佳实践是什么样的、使用哪种设计模式更合适等。其中部分问题和具体的领域有关,只有真正上手写了项目,才能学会这些东西。

这里只列出我接触过的编程语言。现存的编程语言非常多,但真正到了需要编程解决问题的时候,其实能选择的语言反而比较少。

编程语言按以下方式进行分类

  • 编译型。用编译器将源代码变成一个可执行文件
  • 混合型。用编译器将源代码变成字节码,再交给虚拟机执行
  • 解释型。用解释器直接执行源代码
语言的分类问题

如前所述,使用 编译型/混合型/解释型 来划分语言是有局限的

  • 某些语言有多种实现。比如 C++ 既有编译器实现也有解释器实现
  • 某些语言的实现并不完全符合上述的类别。比如 TypeScript 通过转译借用了 JavaScript 的实现,比如 Python 也可以将源代码编译为字节码,比如 JavaScript 常常使用 JIT 编译技术提高运行速度

因此目前的分类并不完美,我只能主观地对那些有争议的语言进行分类

编译型

  • C 和 UNIX 生态紧密结合的语言,流行的很大原因是因为 UNIX。属于 C++ 的子集,因此合并到了 C++ 章节中。
  • C++ 古老而强大的语言。但由于历史原因,C++ 有非常多的缺陷,同时也是主流语言里少数没有官方实现的语言。但同样由于历史原因,C++ 有非常多的代码遗产,一时半会没法转移,所以还能苟活很久。
  • CUDA 严格地说 CUDA 不是一门语言,而是由 NVIDIA 推出的一个异构编程模型,不过也可以把它当成一个 C++ 方言。CUDA 拓展了 C++ 的语法,可以声明哪些代码应该在 GPU 上运行。
  • Rust 更现代的 C++,有更合理的、更统一的工具链,能够从语法层面保障内存/并发安全,可以在编译期发现大多数错误。尽管语法复杂,学习曲线陡峭,但我仍然认为 Rust 是充满前景的。
  • Go 由 Google 设计并开源的语言,能够简单地编写并发程序,可作为大多数后端项目的首选。很多云原生基础设施也都是用 Go 写的,比如 Docker、Kubernetes。
  • Zig 现代版的 C 语言,但还没发布稳定版本。
  • Swift 现代版的 Object-C,Apple 钦定的 iOS/macOS 原生开发语言。
  • V 语法类似 Go,但编译时将 C 作为自己的后端,和早期的 C++ 一样。目前还没发布稳定版本。
  • Haskell 纯函数式语言,相比于工程项目更适合学术研究。

混合型

  • C# 类似 Java 但略有不同,Microsoft 钦定的 Windows 原生开发语言。依赖 .NET 运行时,但中间代码会嵌入可执行文件,且运行时是个动态链接库。
  • Java 面向对象语言,依赖 Java 虚拟机。
  • Kotlin 现代版的 Java,Google 钦定的 Android 原生开发语言。同样依赖 JVM,尽管可以编译成机器码,但这一特性目前还是实验性的。
  • Erlang 函数式语言,比起 Haskell 更注重实用性,依赖 BEAM 虚拟机。
  • Elixir 现代版的 Erlang,同样依赖 BEAM 虚拟机。

解释型

  • Python 人工智能领域无可置疑的标准,同时在 Web 里也有一席之地。
  • Julia 在科学计算领域很流行,不过仅限学术圈,工业界较少使用。
  • R 同样在学术圈流行,专注于统计计算。
  • JavaScript Web 的基石,是浏览器可以理解的语言,语法也相对简单,因此生态非常好。不过部分语法特性饱受诟病。
  • TypeScript 为 JavaScript 增加了静态类型系统。属于 JavaScript 的超集,因此合并到了 JavaScript 章节中。
  • Ruby 流行的主要原因应该是 Ruby on Rails 这个 Web 框架。
  • Lisp 经典的函数式语言,但当下非常小众。用于教学目的挺合适,但 MIT 现在教 SICP 这门课也不用 Lisp 而改为 Python 了。
  • Lua 因为解释器非常轻量而经常嵌入其它应用中,比如 neovim 的配置文件、xmake 的构建描述、pandoc 的过滤器都使用了 Lua。