5.2 代數結構層級與 typeclass#
本節目標#
- 理解 Lean 的 typeclass 機制與 instance resolution 的運作原理
- 以
class Group (G : Type)為例,學會定義與使用代數結構 - 掌握 Semigroup → Monoid → Group → Ring → Field 的繼承層級
- 認識 diamond 問題,以及 Mathlib 用
FunLike/Hom類別給出的解法 - 體會為什麼這套架構讓「泛型數學」成為可能:一個定理,所有 field 適用
Typeclass:以推理而非繼承取得性質#
Lean 的 typeclass 機制源自 Haskell,但在依值型別語言中被推到極致。核心思想:
class是一種特殊的 structure,用來描述「某型別具備某種結構」instance是具體的證據(evidence),宣告某型別滿足某 class- 使用
simp、ring等 tactic,或泛型函式時,Lean 透過 instance resolution(實例推理)自動搜尋所需的證據
與物件導向的繼承不同,typeclass 是編譯期自動證明搜尋:編譯器替你找出「ℤ 是一個 CommRing」這件事的證據。
第一個範例:class Group#
-- 群:一個型別配上一個二元運算,滿足結合律、單位元、反元素
class Group (G : Type) where
mul : G → G → G
mul_assoc : ∀ a b c : G, mul (mul a b) c = mul a (mul b c)
one : G
one_mul : ∀ a : G, mul one a = a
mul_one : ∀ a : G, mul a one = a
inv : G → G
inv_mul_cancel_left : ∀ a : G, mul (inv a) a = one
關鍵細節:運算與公理都放在同一個 class 裡——一個 Group G 的 instance 同時給出資料(mul、one、inv)與性質(mul_assoc 等證明)。在依值型別的世界裡,「證明也是資料」,這樣打包非常自然。寫一個泛型定理:
theorem inv_inv {G : Type} [Group G] (a : G) :
Group.inv (Group.inv a) = a := by
rw [← Group.one_mul (Group.inv (Group.inv a)),
← Group.mul_inv_cancel_right a, Group.inv_mul_cancel_left]
exact (Group.mul_one _).symm
注意簽名中的 [Group G]:方括號是 typeclass 約束,意思是「任何具備 Group 結構的型別 G」。這個證明寫一次,ℤ 的加法群、置换群、矩陣群——全部自動適用。
代數結構的層級#
Mathlib 的代數部分本質上是一張巨大的繼承圖。核心骨幹如下:
flowchart TD
SG["Semigroup<br/>mul + 結合律"] --> M["Monoid<br/>+ one 單位元"]
M --> G["Group<br/>+ inv 反元素"]
M --> CM["CommMonoid<br/>+ 交換律"]
G --> CG["CommGroup<br/>Abelian Group"]
R0["AddCommMonoid"] --> R1["AddCommGroup"]
R1 --> R2["Ring<br/>= AddCommGroup + Monoid + 分配律"]
R2 --> R3["CommRing<br/>+ 乘法交換"]
R3 --> F["Field<br/>+ inv, 0 ≠ 1 保證可逆"]Lean 用 extends 表達這種繼承:
class Semigroup (G : Type) extends Mul G where
mul_assoc : ∀ a b c : G, a * b * c = a * (b * c)
class Monoid (G : Type) extends Semigroup G where
one : G
one_mul : ∀ a : G, one * a = a
mul_one : ∀ a : G, a * one = a
class Ring (R : Type) extends AddCommGroup R, Monoid R where
left_distrib : ∀ a b c : R, a * (b + c) = a * b + a * c
right_distrib : ∀ a b c : R, (a + b) * c = a * c + b * c
有了層級,泛型定理的適用範圍自動最大化:```lean -- 對所有 commutative ring 成立,ℤ、ℚ、ℝ、多項式環全部適用 theorem sq_add_sq {R : Type} [CommRing R] (a b : R) : (a + b) * (a + b) = a * a + 2 * a * b + b * b := by ring
`ring` tactic 能自動完成證明,正是因為它在背後透過 instance resolution 找到所需的代數公理。
## Diamond 問題
多重繼承的代價是 **diamond 問題**:同一個事實經由兩條繼承路徑(如 `Ring` 同時繼承 `AddCommGroup` 與 `Monoid`)取得,得到兩個「不同」的 instance,導致證明對不上。
在 Lean 裡 diamond 有輕重之分:
- **可合併的 diamond**:兩條路徑給出的欄位**定義上相同**(definitional equal),Lean 的 defeq 檢查可以直接打通。大多數數學繼承屬於此類
- **真正的 diamond**:兩條路徑給出的結果**命題上相同但證明不同**,或資料真的不同,必須顯式補一條 `instance` 合併引理
Mathlib 為此發展出嚴格的設計守則:每個 class 盡量只包一個概念的核心資料,性質多靠額外 lemma class(如 `MulZeroClass`)分層提供。
## FunLike 與 Hom 類:把「映射保持結構」變成 class
數學中充滿「結構保持映射」:群同態、環同態、線性映射。如果每種都各自定義一個 structure,很快就會重複勞動。Mathlib 的解法是**泛型函式應用類**:
```lean
-- 泛型:凡是可以「像函式一樣套用」的東西都屬於 FunLike
class FunLike (F : Type) (α : outParam Type) (β : outParam Type) where
coe : F → α → β
-- 群同態:具備 FunLike 行為的 F,且映射保持乘法
structure MulHom (G H : Type) [Mul G] [Mul H] where
toFun : G → H
map_mul' : ∀ a b : G, toFun (a * b) = toFun a * toFun b
instance : FunLike (MulHom G H) G H where
coe f := f.toFun
這樣 f x 這個書寫形式對同態、同構、線性映射全部通用,而型別系統仍記得 f 保持什麼結構。Hom 家族(MulHom、RingHom、LinearMap……)共用 FunLike 的統一介面,是 Mathlib 泛化能力的重要支柱。
to_additive:一寫兩得#
數學中「乘法結構」與「加法結構」總是成對出現:群與加法群、乘法 monoid 與加法 monoid。重複寫兩遍違反 DRY。Mathlib 的 to_additive attribute 讓你只寫乘法版本,自動生成加法版本:
@[to_additive "指數律的加法版本:n • a + n • b = n • (a + b)"]
theorem mul_pow_of_mul_of_pow {M : Type} [CommMonoid M] (a b : M) (n : ℕ) :
(a * b) ^ n = a ^ n * b ^ n := by
induction n with
| zero => simp
| succ k ih => rw [pow_succ, pow_succ, ih, mul_assoc, mul_comm b (a ^ k), ← mul_assoc]
這個 attribute 會掃描證明本體、把 mul 換成 add、one 換成 zero、^ 換成 •,生成命名與文檔齊全的加法版定理。這是 Mathlib 用自動化對抗重複的代表性武器。
為什麼這個架構讓泛型數學可行#
回到本節的標題問題:為什麼 Lean + Mathlib 能做到「一個定理,所有 field 適用」?
- 結構即型別證據:
[Field F]不是註解,而是編譯器可檢查的前提 - instance resolution 自動供給:使用者寫
ℝ的版本時,不需要手動搬運「ℝ 是 field」的證明,編譯器自己找到 - 層級最大化適用範圍:定理寫在最弱足夠的結構上(能寫
CommRing就不寫Field),適用面自動擴大 - 單態化思維:與 Rust 泛型如出一轍,每個實例都是獨立檢查過的具體命題
一位物理學家可以在實數上工作,一位密碼學家在有限體上工作,兩人用的是同一條定理。這就是 Mathlib 單庫策略 + typeclass 架構合起來的威力。
小結與練習#
本節學會了 typeclass 的三個核心機制:class 定義結構、instance 提供證據、resolution 自動搜尋;Semigroup 到 Field 的層級如何用 extends 建構、diamond 問題如何被設計守則與 FunLike 緩解,以及 to_additive 如何消除乘法/加法的重複。
練習:
- 定義一個
class Loop(具有單位元、每個元素有左反元素與右反元素的非結合結構),並為ℤ的加法寫一個 instance。 - 寫一個對任意
[Monoid M]成立的定理:a ^ (m + n) = a ^ m * a ^ n,用induction完成。 - 在 Mathlib 中搜尋
to_additive的實際用法,找一個例子,閱讀其乘法版本與自動生成的加法版本,比對兩者的命名。