From Mathematics to Generic Programming
“计算机科学与数学的分离极大地 impoverish 了两者。” —— Alexander Stepanov
核心观点
泛型编程(Generic Programming)不是一种语法特性,而是一种编程态度:将算法抽象到最通用的情境,同时不损失效率。这种态度直接源自抽象代数——数学家们两千年来不断推广定理适用范围的过程,与程序员泛化算法的过程本质相同。
本书由 C++ STL 设计者 Alexander Stepanov 与 Daniel Rose 合著,起源于 Stepanov 在亚马逊 A9.com 的 “Algorithmic Journeys” 课程。全书以历史叙事串联数学与编程:从埃及乘法到 RSA 密码学,从欧几里得到埃米·诺特,展示了一条清晰的脉络——数论 → 抽象代数 → 泛型编程。
全书结构(14章 + 3附录)
第一部分:古代数学基础(第1-5章)
| 章节 | 主题 | 核心内容 |
|---|---|---|
| 1 | 本书简介 | 泛型编程态度的来源;抽象代数与编程的关系;历史视角的学习方法 |
| 2 | 第一个算法 | 埃及乘法(俄罗斯农民乘法):对数时间乘法,通过加倍和减半实现;算法改进的思维方式 |
| 3 | 古希腊数论 | 形数(多边形数)、埃拉托斯特尼筛法、完全数、毕达哥拉斯学派及其致命缺陷(无理数的发现) |
| 4 | 欧几里得算法 | 最大公度量→最大公约数(GCD);余数算法;零的历史;算法正确性证明(终止性 + 不变式) |
| 5 | 现代数论的兴起 | 梅森素数、费马素数;费马小定理;欧拉定理与 φ 函数;模运算的应用 |
第5章核心洞察:古希腊人研究完全数等”无用”的纯数学问题,两千年后却催生了现代密码学中最实用的定理。这是纯数学与应用关系的经典案例。
第二部分:抽象代数与泛化(第6-9章)
| 章节 | 主题 | 核心内容 |
|---|---|---|
| 6 | 数学中的抽象 | 群(Group):封闭性、结合律、单位元、逆元;幺半群、半群;子群与循环群;拉格朗日定理;理论与模型 |
| 7 | 推导泛型算法 | 从埃及乘法出发,逐步解耦算法对类型的要求:加法半群 A + 整数类型 N → 泛化的幂运算(power_semigroup);斐波那契数计算作为应用 |
| 8 | 更多代数结构 | 环(Ring)、半环、欧几里得整环、域;矩阵乘法与半环;社交网络与最短路径应用;埃米·诺特与抽象代数的诞生 |
| 9 | 组织数学知识 | 证明的社会属性;泰勒斯定理;欧几里得公理系统;皮亚诺公理;公理解释而非定义 |
第6-7章是全书的核心:展示了如何从一个具体算法(埃及乘法)出发,通过识别操作所需的最少性质(仅需结合律 + 单位元 = 半群/幺半群),得到可应用于无数场景的泛型算法。
第三部分:泛型编程实践(第10-12章)
| 章节 | 主题 | 核心内容 |
|---|---|---|
| 10 | 基本编程概念 | 亚里士多德与抽象;概念(Concepts):类型的需求规范;迭代器类别(输入/输出/前向/双向/随机访问);值类型与正则类型 |
| 11 | 置换与算法 | 置换群;reverse、rotate 算法的多种实现;就地(in-place)vs 非就地;内存自适应算法 |
| 12 | GCD 的扩展 | Stein 算法(二进制GCD)及其泛化(多项式、高斯整数、艾森斯坦整数);贝祖恒等式;扩展 GCD;乘法逆元 |
第10章核心:概念(Concepts)是泛型编程的核心工具——正如数学公理定义了”什么是群”,概念定义了”什么类型可以用于这个算法”。选择正确的概念至关重要:要求太多限制应用范围,要求太少则无法定义有用的算法。
第四部分:综合应用(第13-14章)
| 章节 | 主题 | 核心内容 |
|---|---|---|
| 13 | 实际应用:密码学 | 公钥密码系统原理;费马素性测试;卡迈克尔数;Miller-Rabin 素性测试;RSA 算法原理与实现(基于前12章所有成果) |
| 14 | 结论 | 全书总结:泛型编程的态度、效率的重要性、接口设计、类型与概念的关系;数学传统的传承 |
关键概念详解
1. 泛型编程的定义
“泛型编程是一种编程态度,专注于将算法抽象到最通用的情境,同时不损失效率。”
- 抽象来自抽象代数:数学家推广定理的过程 = 程序员泛化算法的过程
- 效率是定义的一部分:比类型特化版本慢的泛型算法不会被使用
- 从具体到抽象:抽象不是凭空产生的,必须从大量具体实例中发现正确的抽象
2. 代数结构层次(从弱到强)
半群 (Semigroup) 结合律 + 封闭性
↓
幺半群 (Monoid) 半群 + 单位元
↓
群 (Group) 幺半群 + 逆元
↓
阿贝尔群 (Abelian) 群 + 交换律
↓
环 (Ring) 加法阿贝尔群 + 乘法半群 + 分配律
↓
欧几里得整环 (Euclidean Domain) 环 + 欧几里得函数(可做GCD)
↓
域 (Field) 环 + 非零元乘法群(有除法)
对应编程中的概念:每一层代数结构都对应一个编程概念,算法只依赖它实际需要的最弱结构。
3. 幂算法的泛化路径(全书主线)
埃及乘法(正整数加法)
→ 乘法-加法半群(任意加法半群 + 整数倍乘)
→ 幂运算(任意半群运算 + 整数次幂)
→ 应用:
· 斐波那契数(矩阵乘法半群)
· 图最短路径(min-plus半群 / 热带半群)
· RSA加密(模乘半群 + 快速幂)
这就是泛型编程的力量:一个算法,无数应用。
4. 概念(Concepts)
- 定义:对类型的需求规范(语法需求 + 语义需求)
- 作用:告诉编译器(和程序员)什么类型可以用于某个算法
- 设计原则:
- 需求越少 → 适用范围越广
- 但需求不能太少,否则算法无法做有用的事
- 正确的概念需要在实现和使用中反复探索
5. 几个重要的编程原则
| 原则 | 含义 |
|---|---|
| 有用返回律(Law of Useful Return) | 函数应返回计算过程中得到的所有相关结果,避免调用方重复计算 |
| 类型分离 | 不要假设多个参数必须是同一类型(如幂函数中底数类型和指数类型可以不同) |
| 完备性 | 接口应覆盖合理的使用场景(如 find 应返回位置而非布尔值) |
| 接口精炼 | 接口需要多次迭代才能正确,就像代码一样 |
| 内存自适应 | 算法应适应可用内存量,而非假设固定的内存约束 |
重要算法整理
埃及乘法 / 快速幂(O(log n))
核心思想:通过反复加倍运算对象和减半次数,将线性复杂度降为对数。
// 泛型版本:半群幂运算
template <Semigroup A, Integer N>
A power_semigroup(A a, N n) {
// n > 0
while (!odd(n)) { a = a * a; n = half(n); }
if (n == 1) return a;
return multiply_accumulate_semigroup(a, half(n-1), a*a);
}
欧几里得 GCD 算法
gcd(a, b) = gcd(b, a mod b) // 直至 b == 0,返回 a
- 正确性证明:每一步保持 GCD 不变 + 余数严格递减 → 有限步终止
- 泛化路径:线段 → 整数 → 多项式 → 高斯整数 → 欧几里得整环
Miller-Rabin 素性测试
- 概率性测试,随机 witness 有 75% 正确率
- 100 个 witness → 错误概率 < 1/2
- 基于费马小定理 + 自消去律(x² ≡ 1 mod p ⇒ x ≡ ±1 mod p)
Stein 算法(二进制 GCD)
- 利用 2 是最小素数的性质,通过移位加速
- 可泛化到多项式(x 对应 2 的角色)、高斯整数(1+i 对应 2)
- 存在 Stein 算法工作但不是欧几里得整环的环——开放问题
RSA 密码学(全书数学的终极应用)
原理
-
密钥生成:
- 选两个大素数 p₁, p₂(用 Miller-Rabin 测试)
- 计算 n = p₁p₂,φ(n) = (p₁-1)(p₂-1)
- 选公钥 pub 与 φ(n) 互素
- 计算私钥 prv = pub⁻¹ mod φ(n)(用扩展 GCD)
- 销毁 p₁, p₂
-
加密:c = m^pub mod n
-
解密:m = c^prv mod n
-
安全性:基于大整数分解的困难性
最实用的互联网安全技术,建立在最”无用”的纯数学(数论)之上——这是贯穿全书的主题。
历史人物脉络
本书特色之一是将数学思想与人的故事结合:
| 人物 | 时代 | 贡献 |
|---|---|---|
| 泰勒斯 | 公元前6世纪 | 第一个定理;西方哲学与科学之父 |
| 毕达哥拉斯学派 | 公元前6-5世纪 | 形数、完全数;发现无理数的危机 |
| 欧几里得 | 公元前3世纪 | 《几何原本》;公理方法;GCD算法 |
| 埃拉托斯特尼 | 公元前3世纪 | 素数筛法 |
| 费马 | 17世纪 | 费马小定理;数论奠基人 |
| 欧拉 | 18世纪 | 欧拉定理、φ函数;推广费马结果 |
| 高斯 | 18-19世纪 | 《算术研究》;高斯整数;素数分布猜想 |
| 伽罗瓦 | 19世纪初 | 群论创始人(20岁死于决斗) |
| 埃米·诺特 | 20世纪初 | 抽象代数奠基人;环论;“诺特环”以她命名 |
| Stevin | 16世纪 | 多项式GCD;将GCD从整数推广到多项式 |
与其他知识的关联
- 《SICP》-计算机程序的构造和解释-第二版:SICP 讲抽象的过程,本书讲抽象的数学基础——从半群到群到环的代数结构抽象是更底层的思维工具
- 《结构化计算机组成》-Structured-Computer-Organization-第6版-Tanenbaum:Tanenbaum 的多层抽象思想与本书的代数结构层次异曲同工
- 《实现模式》-Implementation Patterns-Kent Beck:Kent Beck 讲实现层面的模式,Stepanov 讲算法层面的泛化——都是从具体实例中提炼模式
- 《代码整洁之道》-Clean Code-Robert-C-Martin:Clean Code 讲代码可读的原则,本书讲算法通用的原则——关注点不同但都追求”好的设计”
- 《密码学的乐趣》-The Joy of Cryptography-Mike Rosulek:Rosulek 的书是密码学入门,本书第13章是密码学作为数论应用的一个实例——视角互补
- STL(标准模板库):本书作者是 STL 的设计者,书中思想直接指导了 STL 的设计(迭代器类别、算法的概念约束、容器与算法分离)
个人收获与行动点
- 学习算法时问:这个算法可以在什么代数结构上工作?——找到最弱的前提条件
- 设计接口时遵守有用返回律——不要让调用者重复你已经算过的东西
- 概念设计是泛型编程的核心技能——类型需求要恰到好处,不多不少
- 纯数学不是没用的——今天的纯理论可能是明天的核心技术(如数论→密码学)
- 从具体到抽象,而不是反过来——好的抽象来自对大量实例的深刻理解,不是凭空设计的
- 读《Elements of Programming》——Stepanov & McJones 更正式的姊妹篇,适合深度学习
附录
- 附录A:数学符号
- 附录B:常见证明技巧(反证法、归纳法、鸽巢原理)
- 附录C:非C++程序员的C++指南(模板、概念、函数对象、STL算法与迭代器、C++11新特性)
- 参考文献 + 索引
“下次你写程序时,试着采用泛型编程的态度。从函数的具体实现开始,然后反复修改和提炼,让它们更高效、更通用……记住你是悠久数学算法思想传统的继承者。遵循泛型编程的原则,你已经在受益于前人的工作——从欧几里得到 Stevin 到诺特。通过设计优美、通用的算法,你也在为他们的工作添上自己的小小贡献。” —— 第14章结语