深入 Lean 03:Universe 与类型层级实战
Lean 4 的类型系统里有一个经常被初学者忽视的维度:每个类型本身也有类型,类型的类型称为 universe(宇宙)。忽视这一维度,往往导致一类难以直觉理解的报错——类型不匹配、universe 不一致、无法做大消除。本篇聚焦 universe 层级的实际操作,包括 Prop 与 Type u 的区别、universe 多态定义、以及常见 universe 错误的诊断方法。 前置阅读:本系列 01 Lake 项目结构与依赖管理 和 02 Term-mode 与 Tactic-mode 的切换心法;形式化方法系列 准备 Lean 4 实验环境。 Universe 层级 Lean 4 的类型层级是一个无穷塔(infinite tower),用 Sort 统一表示: 1Sort 0 : Sort 1 : Sort 2 : Sort 3 : ... Prop 是 Sort 0 的别名,Type 是 Type 0 = Sort 1 的别名,Type 1 是 Sort 2,以此类推。 12345#check Prop -- Prop : Type#check Type ...
深入 Lean 17:可执行代码与 FFI
Lean 4 同时承担定理证明器与通用程序设计语言两种角色。前十六篇聚焦逻辑与证明工程;本篇转向可执行端:从 #eval 求值,到 lake build 编译本地二进制,再到通过 FFI 调用 C/Rust 代码,最后讨论影响运行期性能的属性与编译选项。 Lean 4 作为编程语言:#eval 与 IO Lean 4 的类型论本身不区分"证明代码"与"程序代码"——两者都是项(term)。运行 Lean 代码最轻量的方式是 #eval,它在编译时对表达式求值并在 InfoView 中显示结果。 123#eval 1 + 1 -- InfoView 输出:2#eval "hello".length -- InfoView 输出:5#eval List.range 5 -- InfoView 输出:[0, 1, 2, 3, 4] 涉及副作用(文件读写、网络、标准输入输出)时,表达式类型变为 IO α: 12#eval IO.println "hello from Lean"-- 输...
深入 Lean 13:有限组合与数论片段
Mathlib4 提供了一套完整的有限集合与大算子(BigOperators)机制,使得组合数学和初等数论的形式化证明可以在接近数学原文的符号层面进行。Finset 表示有限集合,Fintype 约束类型的有限性,BigOperators 引入求和与连乘的紧凑记号,Nat.Prime 与 Nat.factors 覆盖自然数素数理论的基本引理。本篇在此基础上,进一步介绍 decide tactic 在有限域穷举中的作用,以及 Decidable 类型类的内部逻辑。 前置阅读:02 Term-mode 与 Tactic-mode 切换心法(tactic 基础);04 核心 tactic 精讲(rfl、ring、omega 等基础 tactic);05 simp 与 norm_num(自动化 tactic)。 Finset 与 Fintype Finset 的数据表示 Finset α 是 Mathlib4 中对有限集合的主要表示,定义于 Mathlib.Data.Finset.Basic: 123structure Finset (α : Type*) where val : M...
深入 Lean 12:拓扑与分析基础结构导览
Mathlib 的分析库以 Filter 为核心抽象,在此之上叠加拓扑、度量和赋范结构,统一表达极限、连续、收敛等概念。理解这套分层设计,是读懂 Mathlib 分析证明的前提。本篇梳理 Filter、TopologicalSpace、MetricSpace 和 NormedSpace 四层结构的定义方式和证明惯用法,并通过具体可运行代码展示各层之间的关联。 前置阅读:本系列 09 Mathlib 搜索技巧、10 类型类层次、11 在 Mathlib 上证一个小定理。 Filter:分析库的基础抽象 为什么不直接用 ε-δ 经典分析教材用 ε-δ 定义极限:对每个 ε > 0,存在 δ > 0,使得……这个定义能精确刻画单个极限,但在证明库里它有两个缺陷。第一,同一个"极限"概念在不同情形(趋向实数点、趋向无穷、序列极限、网极限)下需要写出形状不同的 ε-δ 公式,导致同一条定理(如极限的线性性)在每种情形下各需一个版本。第二,组合两个极限结论时,条件匹配需要手动传递 ε/2 技巧,证明文本冗长。 Mathlib 选择用 Filter 把&quo...
深入 Lean 11:在 Mathlib 上证一个小定理
向 Mathlib 贡献一条引理,从选题到合并大约需要经历六个阶段:确认缺口、搜索已有引理、写证明、通过本地 lint、跑通 CI、完成 PR 审核。本文以一个具体的 List 辅助引理为例,完整走一遍这条路径。 选题:找一个真实的缺口 Mathlib 的条目超过十五万条,盲目选题很容易踩在别人已经证过的结论上。有效的选题通常来自两个方向:一是在自己的项目里遇到 exact? 无法命中的目标;二是在 Zulip 的 #mathlib4 频道里找标了 easy 标签的 issue。 本文选用的定理是 List.length_zipWith,即: 12∀ (f : α → β → γ) (l₁ : List α) (l₂ : List β), (List.zipWith f l₁ l₂).length = min l₁.length l₂.length 在实际的 Mathlib 里,这条结论已经存在(List.length_zipWith),但选它作例子的原因是它足够小、结构清晰,可以完整演示所有步骤,而不会把证明本身的复杂度淹没流程细节。实际投稿时,需要找 Mathlib 确实...
深入 Lean 02:Term-mode 与 Tactic-mode 切换心法
Lean 4 提供两种写证明的语法面:term mode 直接给出构造该类型的项;tactic mode 通过一系列指令逐步缩减未完成的目标。两者在语义上等价——任何 tactic 证明最终都被 elaboration 翻译成一个 term,类型检查器只检查 term 层。选择哪种写法是风格决策,但这个决策在实践中有明确的倾向规则。 本篇是"深入 Lean"系列第 02 篇。系列前置知识见《在 Lean 4 中证明经典命题》,该篇已展示了两种模式的基本对照;本篇专注于切换判断本身:什么情况下 term mode 更短、更清晰,什么情况下 tactic mode 更易读、更可维护,以及两种模式如何在同一个证明里混合使用。 Term mode 基础 Term mode 把证明写成一个表达式,该表达式的类型就是命题。Lean 4 的类型论里,命题是类型,证明是居民(inhabitant),“证明成立"等价于"该类型有居民”。 fun、⟨⟩、点投影 12345678-- 蕴含:函数抽象theorem impl_intro (A B : Prop)...
深入 Lean 10:类型类层次:从 Monoid 到 Field
Lean 4 的类型类机制是整个 Mathlib 代数层次的基础设施。从 Monoid 到 Field 的继承链、instance 搜索的 backtracking 算法、以及钻石问题的处理方式,这些设计决策共同决定了 Mathlib 4 能否在百万行规模下保持一致性。本篇专注于类型类层次本身:class/instance/extends 三个关键字的语义、Mathlib 代数 hierarchy 的组织方式、instance 搜索的调试手段,以及如何编写自定义类型类并纳入现有层次。 class 与 instance:基础机制 Lean 4 的类型类是一种特殊的 structure,由 class 关键字声明。编译器对它的处理与普通 structure 的差异在于:class 的实例由 instance 合成器(synthesis engine)自动查找,而不需要显式传入。 12345678910-- 声明一个类型类:具有幺元class HasOne (α : Type*) where one : α-- 为 Nat 注册实例instance : HasOne Nat whe...
深入 Lean 09:Mathlib 搜索技巧
Mathlib4 截至 2024 年末已收录超过 16 万条定理、引理和定义。面对这个规模,手工翻阅文档或凭经验猜名字的效率极低。Lean 4 工具链和 Mathlib 生态提供了一套完整的搜索机制:从编辑器内的 exact?、apply?、rw?、simp?,到浏览器端的 Loogle 和 Moogle——每种工具针对不同的查询意图。掌握这套机制是高效使用 Mathlib 的基本工作方式。 本篇是"深入 Lean"系列第 09 篇。前置阅读:04 核心 tactic 精讲(exact/apply/rw 的语义)、05 simp 与 norm_num(simp 引擎工作原理)、02 Term-mode 与 Tactic-mode 切换心法(两种证明模式的区别)。 问题规模 Mathlib4 的 GitHub 仓库目前约有 70 万行 Lean 代码,模块数量接近 4000。定理覆盖了代数、数论、分析、拓扑、组合、范畴论等数十个领域。一条具体的引理,如 List.length_append 或 Nat.add_comm,隐藏在数百个文件中的某一处。 这个规模带...
深入 Lean 08:自定义 tactic 与宏
Lean 4 的 tactic 框架不是封闭的黑盒——整个 tactic 集合本身就是用 Lean 4 写成的,对用户完全开放扩展。syntax、macro、elab 三个关键字构成了这套元编程机制的核心,分别对应语法解析层、宏展开层、精化(elaboration)层。理解这三层的职责边界,就能在合适的抽象层次上定义自己的 tactic,而不是绕道求助 sorry 或重复编写机械的组合步骤。 本篇是"深入 Lean"系列第 08 篇。前置阅读:02 Term-mode 与 Tactic-mode 切换心法(tactic 基本操作),03 Universe 与类型层级实战(Sort/Type/Prop 区分)。 三层架构 Lean 4 的元编程体系在编译管线里分三层处理用户写下的语法片段: 1234567用户源码 ↓ 解析(parser)语法树(Syntax) ↓ 宏展开(macro expansion)展开后的语法树(Syntax) ↓ 精化(elaboration)核心项(Expr) 解析层由 syntax 关键字负责,扩充 Lean 的文法,告诉解...
深入 Lean 07:conv 与 calc
rw 按从左到右的顺序匹配第一个符合的子项并替换,simp 则对所有可匹配位置做化简。两者在精度上处于两个极端:一个只打第一枪,一个全部扫射。实际证明中经常需要介于两者之间的操作——精确指定重写位置。Lean 4 提供了 conv 和 calc 两套工具:conv 在目标的语法树上导航到具体子表达式再执行重写,calc 把一串等式或不等式推导组织成可读的链式结构。 本篇的前置知识是 rw、simp 的基本用法(见第 04 篇:核心 tactic 精讲和第 05 篇:simp 与 norm_num)。所有代码在 Lean 4 v4.12.0 + Mathlib4 下通过 lake build 验证。 conv 的基本结构 conv 开启一个子目标编辑会话。在这个会话内,当前目标被视为一棵语法树,通过导航组合子定位到具体子表达式后再执行操作。 最简单的例子:目标是 ⊢ 0 + n = n,想用 Nat.zero_add 重写左侧。在更复杂的情况下等号两侧可能都包含相同的子项,此时需要限定重写范围。 12example (n : ℕ) : 0 + n = n := by conv_l...
