第 1 章:序幕(Prologue)
原文:Anil Madhavapeddy and Yaron Minsky, Real World OCaml: Functional Programming for the Masses, Second Edition, Chapter 1。维护者已确认本书为开源书籍,可翻译并发布用于学习研究。
1.1 为什么选择 OCaml?(Why OCaml?)
编程语言很重要。它们会影响你写出的代码在可靠性、安全性和效率方面的表现,也会影响代码被阅读、重构和扩展的难易程度。你掌握的语言还会改变你的思维方式,即使你没有使用这些语言,它们也会影响你设计软件的方式。
我们写这本书,是因为我们相信编程语言的重要性,也相信 OCaml 尤其值得学习。我们两人在学术生活和职业生涯中使用 OCaml 都已经超过 20 年,在这段时间里,我们逐渐认识到,它是构建复杂软件系统的强大工具。本书的目标,是通过清晰说明在真实世界中有效使用 OCaml 所需的知识,让更广泛的读者能够使用这个工具。
OCaml 的特别之处在于,它在编程语言设计空间中占据了一个恰到好处的位置。它结合了效率、表达力和实用性,而没有其他语言能以同样方式同时做到这几点。很大程度上,这是因为 OCaml 优雅地组合了一组在过去 60 年中发展出来的语言特性,其中包括:
- 用于自动内存管理的垃圾回收。这已经是现代高级语言中的常见特性。
- 一等函数。函数可以像普通值一样被传递,类似于 JavaScript、Common Lisp 和 C# 中看到的做法。
- 静态类型检查。它可以提高性能,并减少运行时错误数量,Java 和 C# 中也有类似机制。
- 参数多态。它让我们能够构建跨不同数据类型工作的抽象,类似于 Java、Rust、C# 中的泛型,以及 C++ 中的模板。
- 对不可变编程的良好支持,也就是不对数据结构进行破坏性更新的编程方式。这在 Scheme 等传统函数式语言中存在,也常见于从分布式大数据框架到用户界面工具包的各种系统中。
- 类型推断。你不必为程序中的每个变量标注类型;相反,类型会根据值的使用方式推断出来。C# 的隐式类型局部变量和 C++11 的
auto关键字提供了有限形式的类似能力。 - 代数数据类型和模式匹配。它们用于定义和操作复杂数据结构,Scala、Rust 和 F# 中也能看到这些特性。
有些读者会熟悉并喜爱所有这些特性;对另一些读者来说,它们可能大多是新的。但大多数人至少会在自己用过的其他语言中见过其中一部分。正如本书后续将展示的那样,当这些特性全部出现在同一种语言中,并且能够彼此配合时,会产生一种改变编程体验的力量。
尽管这些思想很重要,但它们进入主流语言的程度仍然有限。即使它们进入了主流语言,例如 C# 中的一等函数,或 Java 中的参数多态,通常也会以受限且笨拙的形式出现。真正完整体现这些思想的语言,是 OCaml、F#、Haskell、Scala、Rust 和 Standard ML 这样的静态类型函数式编程语言。
在这一组优秀语言中,OCaml 之所以突出,是因为它在提供强大能力的同时保持了高度实用。OCaml 编译器采用直接明了的编译策略,不需要重度优化,也不需要动态即时编译(JIT)的复杂性,就能生成性能良好的代码。再加上 OCaml 的严格求值模型,程序的运行时行为也更容易预测。它的垃圾回收器是增量式的,可以避免与垃圾回收(GC)相关的大停顿;它也是精确的,意味着所有未被引用的数据都会被回收,这不同于许多引用计数式回收器。此外,OCaml 的运行时简单且高度可移植。
所有这些因素使 OCaml 成为一种很好的选择:它适合那些希望迈向更好编程语言,同时仍然想完成实际工作的程序员。
1.1.1 简史(A Brief History)
OCaml 于 1996 年由法国 INRIA 的 Xavier Leroy、Jérôme Vouillon、Damien Doligez 和 Didier Rémy 编写。它受到自 20 世纪 60 年代以来 ML 研究传统的启发,并且至今仍与学术界保持着深厚联系。
ML 最初是 LCF(Logic for Computable Functions)证明助手的元语言。LCF 由 Robin Milner 于 1972 年发布,最初在 Stanford,后来在 Cambridge 发展。为了让 LCF 更容易在不同机器上使用,ML 被实现为编译器;到 20 世纪 80 年代,它逐渐发展成了一个完整的独立系统。
Caml 的第一个实现出现于 1987 年。它由 Ascánder Suárez 创建,当时是 INRIA 由 Gérard Huet 领导的 Formel 项目的一部分,后来由 Pierre Weis 和 Michel Mauny 继续推进。1990 年,Xavier Leroy 和 Damien Doligez 构建了一个名为 Caml Light 的新实现,它基于字节码解释器,并带有快速的顺序垃圾回收器。在接下来的几年中,一些有用的库陆续出现,例如 Michel Mauny 的语法操作工具,这帮助 Caml 在教育和研究团队中得到推广。
Xavier Leroy 继续为 Caml Light 扩展新特性,最终产生了 1995 年发布的 Caml Special Light。这个版本加入了快速原生代码编译器,显著提升了可执行程序的效率,使 Caml 的性能能够与 C++ 等主流语言竞争。一个受 Standard ML 启发的模块系统也提供了强大的抽象设施,使构建更大规模的程序变得更容易。
现代 OCaml 出现于 1996 年,当时 Didier Rémy 和 Jérôme Vouillon 实现了一个强大而优雅的对象系统。这个对象系统值得注意之处在于,它能以静态类型安全的方式支持许多常见的面向对象惯用法,而同样的惯用法在 C++ 或 Java 等语言中需要运行时检查。2000 年,Jacques Garrigue 为 OCaml 扩展了若干新特性,例如多态方法、变体,以及带标签参数和可选参数。
过去二十多年中,OCaml 吸引了相当规模的用户群体。为了支持不断增长的商业与学术代码库,语言本身也持续改进。到 2012 年,OCaml 4.0 版本加入了广义代数数据类型(GADTs)和一等模块,以提高语言灵活性。此后,OCaml 保持了稳定的年度发布节奏;在本书第二版成书时,带有多核支持的 OCaml 5.0 已经近在眼前。OCaml 还为 x86_64、ARM、RISC-V 和 PowerPC 等最新 CPU 架构提供快速原生代码支持,因此在资源使用、可预测性和性能都很重要的系统中,OCaml 是一个很好的选择。
1.1.2 Base 标准库(The Base Standard Library)
无论语言本身多么优秀,仅有语言并不够。你还需要一组用于构建应用程序的库。OCaml 学习者常见的挫败感之一,是随编译器提供的标准库比较有限,只覆盖了通用标准库应有功能的一个子集。原因在于,这个标准库本质上并不是一个通用工具;它的根本作用是引导编译器自身启动,因此被有意保持得小巧且可移植。
好在开源软件世界中,没有什么会阻止人们编写替代库来补充编译器附带的标准库。Base 就是这样一个库,本书大部分内容都会使用它作为标准库。
Jane Street 是一家使用 OCaml 超过 20 年的公司。Base 中的代码最初是 Jane Street 为内部使用而开发的,但从一开始,它就以通用标准库为目标来设计。和 OCaml 语言本身一样,Base 的工程目标也围绕正确性、可靠性和性能展开。它也被设计成易于安装且高度可移植。因此,只要是 OCaml 支持的平台,Base 也都能工作,包括 UNIX、macOS、Windows 和 JavaScript。
Base 随一组语法扩展一起分发,这些扩展为 OCaml 提供了有用的新功能。还有一些额外的库被设计成与 Base 良好协作,包括 Core 和 Async。Core 是 Base 的扩展,包含大量新的数据结构和工具;Async 是一个并发编程库,适用于构建用户界面或网络应用时经常遇到的并发场景。所有这些库都以宽松的 Apache 2 许可证发布,允许在业余、学术和商业环境中自由使用。
1.1.3 OCaml 平台(The OCaml Platform)
Base 是一个全面且有效的标准库,但 OCaml 软件生态远不止于此。自 1996 年首次发布以来,一个庞大的程序员社区一直在使用 OCaml,并创造了许多有用的库和工具。本书会在示例展开过程中介绍其中一些库。
第三方库的安装和管理可以通过名为 opam 的包管理工具变得容易得多。本书后续会进一步解释 opam,但它构成了 OCaml 平台的基础。OCaml 平台是一组工具和库,与 OCaml 编译器一起,让你能够快速而有效地构建真实世界的应用程序。OCaml 平台的组成工具包括 dune 构建系统,以及用于集成 Visual Studio Code、Emacs 或 Vim 等常见编辑器的语言服务器。
我们还会使用 opam 安装 utop 命令行界面。这是一个现代交互式工具,支持命令历史、宏展开、模块补全以及其他便利功能,使使用 OCaml 变得愉快得多。本书会全程使用 utop,让你能够交互式地逐步运行示例。
相关链接:
opam:https://opam.ocaml.org/dune:https://dune.build
1.2 关于本书(About This Book)
Real World OCaml 面向已经有传统编程语言经验,但未必了解静态类型函数式编程的程序员。取决于你的背景,本书讨论的许多概念可能是新的,包括高阶函数、不可变数据类型等传统函数式编程技术,也包括 OCaml 强大类型系统和模块系统的各个方面。
如果你已经了解 OCaml,本书也许仍会让你感到意外。Base 重新定义了大部分标准命名空间,以便更好地利用 OCaml 模块系统,并默认暴露一批强大且可复用的数据结构。较旧的 OCaml 代码仍然可以与 Base 互操作,但为了获得最大收益,你可能需要对它做一些调整。本书编写的所有新代码都会使用 Base;我们相信 Base 模型值得学习。它已经成功用于数百万行规模的大型代码库,并移除了用 OCaml 构建复杂应用程序的一大障碍。
只使用传统编译器标准库的代码永远都会存在,也有其他在线资源可以帮助你学习它的工作方式。Real World OCaml 关注的,是作者在个人经验中用于构建可扩展、健壮软件系统的技术。
1.2.1 你可以期待什么(What to Expect)
Real World OCaml 分为三个部分:
- 第 I 部分介绍语言本身,开篇是一段导览,用来快速勾勒这门语言。不要期待完全理解导览中的每一个细节;它的目的,是让你尝到语言许多不同方面的味道,而其中涉及的思想会在后续章节中更深入地解释。在介绍核心语言之后,第 I 部分会转向模块、函子和对象等更高级的特性。理解这些概念可能需要一些时间,但这很重要。即使超出 OCaml 的范围,当你转向其他现代语言时,这些思想也会让你站在更好的位置,因为许多现代语言都从 ML 中汲取了灵感。
- 第 II 部分在基础之上,围绕解决常见实际应用所需的有用工具和技术展开,从命令行解析到异步网络编程。一路上,你会看到第 I 部分中的一些概念如何被粘合进真实的库和工具,并以良好效果组合语言的不同特性。
- 第 III 部分讨论 OCaml 的运行时系统和编译器工具链。与其他一些语言实现相比,例如 Java 或 .NET 的 CLR,它们非常简单。阅读这一部分后,你将能够构建非常高性能的系统,或与 C 库交互。这里也会讨论使用 GNU
gdb等工具进行性能分析和调试的技术。
1.2.2 安装说明(Installation Instructions)
Real World OCaml 使用了我们在写作本书时开发的一些工具。其中一些工作带来了对 OCaml 编译器的改进,这意味着你需要确保拥有最新的开发环境。本书第二版使用的是 4.13.1 版本的编译器。安装过程大体上通过 opam 包管理器自动完成。关于如何设置它以及需要安装哪些包,可以参见安装页面。
我们使用的一些库,尤其是 Base,可以在任何 OCaml 支持的平台上工作,特别是也支持 Windows 和 JavaScript。本书第 I 部分的示例大多会坚持使用这类高度可移植的库。不过,有些使用到的库需要基于 UNIX 的操作系统,因此只适用于 macOS、Linux、FreeBSD、OpenBSD 和 Windows Subsystem for Linux(WSL)等系统。Core 和 Async 就是这里值得注意的例子。
本书并不是一本参考手册。我们的目标是讲授这门语言,以及能帮助你成为更高效 OCaml 程序员的库、工具和技术。但它不能替代 API 文档、OCaml 手册或 man 页面。书中提到的所有库和工具的文档都可以在 OCaml Packages 在线找到。
1.2.3 代码示例(Code Examples)
本书中的所有代码示例都以接近公有领域的许可证在线免费提供。欢迎你按照自己的需要,把任何代码片段复制并用于自己的代码中,无需署名,也没有其他使用限制。
本书全文以及所有示例代码都可以在 realworldocaml/book 找到。
1.3 贡献者(Contributors)
我们尤其感谢以下个人对 Real World OCaml 的改进:
- Jason Hickey 是本书第一版的共同作者,他对本修订版赖以建立的结构和内容起到了关键作用。
- Leo White 和 Jason Hickey 对第 13 章“对象(Objects)”和第 14 章“类(Classes)”的内容与示例做出了重要贡献。
- Jeremy Yallop 编写并记录了第 23 章“外部函数接口(Foreign Function Interface)”中描述的 Ctypes 库。
- Stephen Weeks 负责 Base 和 Core 背后大部分模块化架构,他的大量笔记构成了第 24 章“值的内存表示(Memory Representation of Values)”和第 25 章“理解垃圾回收器(Understanding the Garbage Collector)”的基础。随后,Sadiq Jaffer 刷新了垃圾回收器一章,以反映 OCaml 4.13 中的最新变化。
- Jérémie Dimino 是
utop的作者;utop是本书全程使用的交互式命令行界面。我们尤其感谢他推动了一些变更,使utop在本书语境中工作得更好。 - Thomas Gazagnaire、Thibaut Mattio、David Allsopp 和 Jonathan Ludlam 为 OCaml 平台一章做出了贡献,包括修复核心工具,以更好地帮助新用户安装。
- Ashish Agarwal、Christoph Troestler、Thomas Gazagnaire、Etienne Millon、Nathan Rebours、Charles-Edouard Lecat、Jules Aguillon、Rudi Grinberg、Sonja Heinze 和 Frederic Bour 致力于改进本书工具链。这使我们能够更新本书,以跟踪 OCaml 以及各种库和工具的变化。Ashish 还开发了新版且更完善的本书网站。
- Cambridge University Press 的 David Tranah、Clare Dennison、Anna Scriven 和 Suresh Kumar 对印刷版版式提供了意见。Airlie Anderson 绘制了封面插画,Christy Nyberg 对设计和版式提出了建议。
- 还有许多人共同为本书在线草稿提交了超过 4000 条评论。正是由于他们的努力,无数错误才得以被发现并修正。