深入 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、⟨⟩、点投影
1 | |
从假设里取出合取分量用点投影:
1 | |
show 可在 term 里插入局部类型注解:
1 | |
show 在 elaboration 上不做任何事,只起文档作用。在嵌套项里,它是告诉读者"此处期望类型是 T"的最轻量标记。
解构绑定
Lean 4 允许在 fun 参数里直接解构:
1 | |
嵌套解构同样合法:
1 | |
匿名构造子 ⟨ha, hb, hc⟩ 对 A ∧ (B ∧ C) 自动右结合,等同于 ⟨ha, ⟨hb, hc⟩⟩。
#check 验证类型
在任意 term 上调用 #check 可确认类型,无需跑完整的 lake build:
1 | |
Tactic mode 基础
by 关键字打开一个 tactic 块。进入 tactic mode 后,编辑器右栏(InfoView)显示当前待证的目标(⊢),每一步 tactic 消费或转换目标,直到 No goals。
核心 tactic 速查
| tactic | 典型用途 |
|---|---|
intro h |
把 ⊢ A → B 里的前件 A 引入上下文为 h : A |
exact e |
用 term e 关闭当前目标 |
apply f |
用 f : X → ⊢-goal 反推,把目标替换为 X |
constructor |
对单构造子目标(And、Iff 等)拆成多个子目标 |
cases h |
对多构造子归纳类型分情况 |
induction n |
对归纳类型做归纳 |
simp |
自动用一组等式归约目标 |
rw [h] |
用等式 h 改写目标 |
InfoView 的 goal 状态跟踪
and_comm 的 tactic 版本,逐步展示 InfoView 的变化:
1 | |
每一行 tactic 之后光标悬停,InfoView 刷新;Lean 4 的标准工作方式是写一步、看一步目标,不是盲写整段再回头检查。
#print axioms 可查看一个已证定理依赖了哪些公理,确认证明没有悄悄引入不期望的假设:
1 | |
两种模式的直接对照
同一个定理 A ∧ B → B ∧ A,三种写法并排:
1 | |
三者被 elaboration 处理后产生的 term 完全相同。区别纯粹在于书写时的脚手架。
蕴含链的例子展示 term mode 更简洁的场景:
1 | |
(A → B) → (B → C) → (A → C) 的证明结构就是函数复合,没有任何分支;term mode 一行表达完毕,tactic mode 反而引入了多余的语法噪声。
何时选 term mode,何时选 tactic mode
term mode 适合的情形
- 证明结构与类型签名直接对应:看到类型就知道该写什么
- 单一路径的构造(无分支、无归纳):
fun、⟨⟩、点投影足够 - 引理组合:已有若干引理,证明只是把它们串起来
tactic mode 适合的情形
- 证明路径不直接可见:需要 InfoView 辅助探索
- 需要分情况讨论(
cases)或归纳(induction) simp或omega等自动化 tactic 能处理的算术/等式目标- 多步推理时 tactic 的"一步一目标"比 term 嵌套更清楚
自然数归纳是 tactic mode 明显更优的场景:
1 | |
term mode 写同一个证明需要显式 Nat.rec:
1 | |
Nat.rec 调用里的 ih 参数在 term 层是一个普通的函数参数,没有 tactic 的"在上下文里看到 ih"这种形式——因此 term mode 在归纳证明里几乎总是更难读。
混合写法
exact (term) 在 tactic 块里
一旦目标的 term 构造已知,直接 exact 提供 term,无需继续展开 tactic:
1 | |
(by tactic) 在 term 里
term 的某个分量难以直接给出时,可在 term 里局部切换到 tactic:
1 | |
by exact h.2.2 和直接写 h.2.2 效果完全相同;真正有用的场景是 by 里有多步推理,比如需要 rw 或 simp 才能关掉的分量。
have 的两种形式
have 在两种模式里都可用,且可以跨模式赋值:
1 | |
tactic 里的 have 对应 term 里的 let(非递归绑定)。两者在 elaboration 后是同一个 let 节点。
决策框架
| 情况 | 推荐模式 | 原因 |
|---|---|---|
| 类型签名已完全确定证明结构 | term | 无需脚手架 |
| 单一构造,无分支 | term | fun/⟨⟩ 比多行 tactic 短 |
需要分情况(Or、自定义归纳类型) |
tactic | cases 语法更清晰 |
| 需要归纳 | tactic | InfoView 能看到每步归纳假设 |
| 目标涉及算术等式 | tactic | simp/omega 自动化 |
| 多步推导,需要可读性 | tactic | 步骤可逐行读懂 |
| 已知引理直接串联 | term 或混合 | exact (f ∘ g) 等风格 |
| 某一分量难以直接写出 | 混合 | 主体 term,局部 by |
Lean/Mathlib 社区的惯例:简单的单步或双步命题倾向于 term mode;需要 simp、ring、linarith、omega 等自动化策略的几乎都是 tactic mode;库代码里大量出现 by simp [...] 或 by rw [...]; simp,极少见纯 term 写的归纳证明。
练习
练习 1
用 term mode 证明:
1 | |
提示:答案是一个字面的函数复合表达式。
练习 2
用 tactic mode 证明,并在关键步骤注释 InfoView 的 ⊢ 状态:
1 | |
提示:需要 intro、cases、Or.inl/Or.inr。
练习 3
将下面的 tactic 证明改写为等价的 term mode 版本,使用 fun 和解构绑定:
1 | |
参考资料
- Theorem Proving in Lean 4, §3–§5:term mode 与 tactic mode 的系统介绍 https://leanprover.github.io/theorem_proving_in_lean4/
- Functional Programming in Lean, §2:term 语法全览 https://leanprover.github.io/functional_programming_in_lean/
- Mathlib4 贡献风格指南:https://leanprover-community.github.io/contribute/style.html
- 系列前篇:《在 Lean 4 中证明经典命题》(term/tactic 对照表)
- 系列前篇:《准备 Lean 4 实验环境》(
lake工程结构、InfoView 配置)
