14. decide 与 native_decide:有限计算判定
本章目标:理解 Decidable 类型类如何同时携带判定结果与证据,
掌握 decide 与 native_decide 的执行路径、可信度差异与失败模式,
学会为自定义命题编写 Decidable 实例,在实际证明中做出合理选择。
simp 做符号重写,omega 解线性算术,norm_num 算大数——
当问题可以归约为有限计算时,decide 和 native_decide 是最直接的武器:
把命题变成布尔值,算出 true,证明完毕。
14.1. 13.1 Decidable 类型类
decide 系列 tactic 的根基是核心库中的 Decidable:
-- [定义] 来自 Init.Prelude -- class inductive Decidable (p : Prop) where -- | isFalse (h : ¬p) : Decidable p -- ❶ 判定为假,携带反证 -- | isTrue (h : p) : Decidable p -- ❷ 判定为真,携带正证
-
❶ 不只说"假",还给出
¬p的构造性证明。 -
❷ 不只说"真",还给出
p的构造性证明。
与 Bool 的本质区别:Bool 只记录结果,Decidable 同时记录结果与证据。
这使得判定结果可以被内核直接用作证明项。
Lean 核心库为大量命题注册了 Decidable 实例。
DecidableEq α 是 ∀ (a b : α), Decidable (a = b) 的缩写,
大多数具体数据类型(Nat、Int、String、List α)都自动拥有此实例。
#check (inferInstance : Decidable (3 = 5))
#check (inferInstance : Decidable (3 < 5))
#check (inferInstance : Decidable (5 ∈ [1,2,3]))14.1.1. Prop 与 Bool 的桥梁
Decidable 连接 Prop 世界和 Bool 世界:
-- [签名] 核心库关键定义
#check @Decidable.decide -- {p : Prop} → [Decidable p] → Bool -- ❶ 命题 → 布尔
-- of_decide_eq_true : decide p = true → p -- ❷ 布尔真 → 命题真
-
❶ 把命题"降级"为布尔值,供
if/match使用。 -
❷ 若内核规约
decide p到true,rfl即证decide p = true, 再经of_decide_eq_true rfl得到p的证明——这正是decidetactic 的核心。
14.2. 13.2 decide:内核规约证明
decide tactic 的执行路径分四步:
-
查找实例:对目标
P,typeclass search 搜索Decidable P。 -
构造项:生成
@Decidable.decide P inst。 -
内核规约:Lean kernel 对该项执行 δ/ι/ζ reduction。
-
提取证明:若结果为
true,构造of_decide_eq_true rfl作为证明项。
example : 2 + 2 = 4 := ⊢ 2 + 2 = 4 All goals completed! 🐙
example : ¬ (3 ∈ [1, 2, 5]) := ⊢ 3 ∉ [1, 2, 5] All goals completed! 🐙
example : Nat.Prime 7 := ⊢ Nat.Prime 7 All goals completed! 🐙
example : ∀ b : Bool, b || !b = true := ⊢ ∀ (b : Bool), (b || !decide (b = true)) = true All goals completed! 🐙
绝对可信:decide 的证明只依赖 Lean kernel——系统中最小的 trusted computing base,
不涉及编译器、运行时或任何外部 axiom。
14.3. 13.3 decide 的五种失败模式
14.3.1. 失败 1:缺少 Decidable 实例
-- [会报错] p 是任意命题,无法合成实例 -- example (p : Prop) : p ∨ ¬p := by decide -- -- ▸ failed to synthesize Decidable (p ∨ ¬p) -- -- ▸ 修复:用 Classical.em p,或为 p 提供 Decidable 实例
p ∨ ¬p 在经典逻辑中成立(Classical.em),但 decide 只处理构造性可判定的命题。
14.3.2. 失败 2:内核超时(计算量过大)
-- [会超时] 内核解释器需要试除到 √1000003 ≈ 1000 -- example : Nat.Prime 1000003 := by decide -- -- ▸ 修复:改用 native_decide 或 norm_num
经验法则:decide 不宜超过几万步 reduction,超过应换 tactic。
14.3.3. 失败 3:命题为假
-- [会报错] 4 不是质数 -- example : Nat.Prime 4 := by decide -- -- ▸ decide evaluated to false -- -- ▸ 这不是 tactic 的问题,而是命题本身不成立
decide 只能证明为真的可判定命题。报告 evaluated to false 本身是有用的诊断。
14.3.4. 失败 4:目标含自由变量
-- [会报错] n 不是字面量,无法规约 -- example (h : n = 3) : n + 1 = 4 := by decide -- -- ▸ decide 只做封闭项(closed term)的计算 -- -- ▸ 修复:先 subst h 消除 n,再 decide -- example (h : n = 3) : n + 1 = 4 := by subst h; decide -- ✓
14.3.5. 失败 5:实例存在但计算量不可行
-- [会超时] 实例存在 ≠ 计算可行 -- example : (Finset.range 10000).card = 10000 := by decide -- -- ▸ 修复:native_decide 或 simp [Finset.card_range]
14.4. 13.4 native_decide:编译到原生代码
native_decide 解决 decide 的性能瓶颈,但执行路径完全不同:
-
查找实例:同
decide,搜索Decidable P实例。 -
编译:将判定过程通过 Lean 编译器编译为原生机器码。
-
执行:运行编译后的代码,得到
Bool结果。 -
桥接:若结果为
true,通过 axiomLean.ofReduceBool构造证明。
example : Nat.Prime 104729 := ⊢ Nat.Prime 104729 All goals completed! 🐙
example : ∀ n : Fin 256, n.val < 256 := ⊢ ∀ (n : Fin 256), ↑n < 256 All goals completed! 🐙
example : ((List.range 100).filter Nat.Prime).length = 25 := ⊢ (List.filter (fun b => decide (Nat.Prime b)) (List.range 100)).length = 25 All goals completed! 🐙14.4.1. 可信度代价
decide 的证明完全在内核中完成,可信边界仅限于 Lean kernel 本身。
native_decide 则依赖编译器将判定过程编译为原生代码再执行——
如果编译器存在 bug(错误优化、代码生成缺陷),理论上可能把
实际为 false 的计算"证明"为 true。因此 native_decide 的可信边界
比 decide 更宽:它信任的不仅是内核,还有编译执行路径。
Mathlib 策略:能用 decide 就不用 native_decide,
能用 norm_num / omega 等专用 tactic 就优先使用。
14.4.2. native_decide 的失败模式
-- [会报错] 同样需要 Decidable 实例 -- example (p : Prop) : p ∨ ¬p := by native_decide -- -- ▸ failed to synthesize Decidable (p ∨ ¬p) -- [会报错] 命题为假 -- example : Nat.Prime 4 := by native_decide -- -- ▸ native_decide evaluated to false -- [会报错] 代码链中含 sorry 或 partial 导致编译失败 -- partial def foo : Nat → Bool := fun _ => true -- instance : Decidable P := if foo 0 then .isTrue sorry else .isFalse sorry -- example : P := by native_decide -- failed to compile
native_decide 不能绕过 Decidable 的要求,也不能证明假命题。
包含 sorry 或 partial 的代码链会导致编译失败。
14.5. 13.5 编写 Decidable 实例
14.5.1. 方式 1:deriving 自动派生
inductive Color where
| red | green | blue
deriving DecidableEq
example : Color.red ≠ Color.blue := ⊢ Color.red ≠ Color.blue All goals completed! 🐙-
❶ 结构体、枚举、带参数的归纳类型都支持
deriving DecidableEq。
14.5.2. 方式 2:dependent if 手动构造
instance (n m : Nat) : Decidable (n ∣ m) :=
if h : m % n = 0
then .isTrue (Nat.dvd_of_mod_eq_zero h)
else .isFalse fun ⟨k, hk⟩ =>
h (n:ℕm:ℕh:¬m % n = 0x✝:n ∣ mk:ℕhk:m = n * k⊢ m % n = 0 All goals completed! 🐙)-
❶
if h : cond是 dependent if——分支中h分别绑定cond和¬cond。 -
❷ 正分支利用库引理构造整除证明。❸ 负分支推出矛盾。
14.5.3. 方式 3:归约到已有可判定命题
def IsEven (n : Nat) : Prop := n % 2 = 0
instance (n : Nat) : Decidable (IsEven n) :=
inferInstanceAs (Decidable (n % 2 = 0))-
❶
inferInstanceAs告诉 typeclass searchIsEven n就是n % 2 = 0。
14.5.4. 方式 4:组合逻辑连接词
Decidable 对逻辑连接词封闭——p ∧ q、p ∨ q、¬p、p → q
在两侧可判定时自动可判定。对有限量词 ∀ x : α, P x 和 ∃ x : α, P x,
需要 α 是 Fintype 且 P 对每个 x 可判定:
example : Decidable (3 < 5 ∧ 7 ≠ 8) := inferInstance
example : Decidable (∀ i : Fin 5, i.val < 10) := inferInstance
example : Decidable (∃ i : Fin 5, i.val = 3) := inferInstance14.6. 13.6 与其他 tactic 的对比
example : 100 < 200 := ⊢ 100 < 200 All goals completed! 🐙
example : 100 < 200 := ⊢ 100 < 200 All goals completed! 🐙
example : Nat.Prime 104729 := ⊢ Nat.Prime 104729 All goals completed! 🐙
example : (2 : ℤ) ^ 10 = 1024 := ⊢ 2 ^ 10 = 1024 All goals completed! 🐙
example (n : Nat) : n + 0 = n := n:ℕ⊢ n + 0 = n All goals completed! 🐙
-- example (n : Nat) : n + 0 = n := by decide -- ✗ n 不是字面量14.6.1. 选择速查表
-
场景:小规模有限穷举(< 100 种) —— 推荐 tactic:
decide—— 原因:内核可信,无额外假设 -
场景:大规模有限穷举(> 100 种) —— 推荐 tactic:
native_decide—— 原因:编译执行,快几个数量级 -
场景:
Nat/Int线性算术 —— 推荐 tactic:omega—— 原因:专用 decision procedure -
场景:具体数值计算、素性、整除 —— 推荐 tactic:
norm_num—— 原因:meta 层高效归约 + 证书 -
场景:含变量的等式 / 不等式 —— 推荐 tactic:
simp/ring/linarith—— 原因:符号化简
14.7. 13.7 实战模式与调试
14.7.1. 模式 1:有限案例验证
example : ∀ a b : Fin 3, a + b = b + a := ⊢ ∀ (a b : Fin 3), a + b = b + a All goals completed! 🐙
example : ∀ a b : Fin 20, a + b = b + a := ⊢ ∀ (a b : Fin 20), a + b = b + a All goals completed! 🐙
14.7.2. 模式 2:interval_cases + decide 联用
example (n : ℕ) (h : n < 3) : n * n < 10 := n:ℕh:n < 3⊢ n * n < 10
n:ℕh:0 < 3⊢ 0 * 0 < 10n:ℕh:1 < 3⊢ 1 * 1 < 10n:ℕh:2 < 3⊢ 2 * 2 < 10
all_goals All goals completed! 🐙14.7.3. 模式 3:在 have 中建立局部事实
example : True := ⊢ True
have h₁ : Nat.Prime 7 := ⊢ True All goals completed! 🐙
have h₂ : 7 ∈ [2, 3, 5, 7] := ⊢ True All goals completed! 🐙
All goals completed! 🐙14.7.4. 调试技巧 1:用 #eval 预判
-- #eval Nat.Prime 7 -- true → decide 应能证明 -- #eval Nat.Prime 1000003 -- true → 但 decide 会超时,用 native_decide #eval (4 : Nat) ∣ 6 -- false → decide 会失败(命题为假)
先 #eval 确认真假和计算量,再选 tactic。
14.7.5. 调试技巧 2:检查实例 + 调整心跳
#check (inferInstance : Decidable (3 < 5)) -- OK #check (inferInstance : DecidableEq Color) -- 报错则需 deriving 或手写 -- set_option maxHeartbeats 400000 in -- example : Nat.Prime 997 := by decide -- -- ▸ 默认 200000,可临时提高;若需大幅增加,应换 tactic
14.8. 13.8 练习
14.8.1. 练习 1(基础):decide 能证什么
判断以下哪些能用 decide 证明,不能的说明原因并给出替代方案:
example : 10 * 10 = 100 := by sorry -- (a) 基础算术 -- example : ¬ Nat.Prime 15 := by sorry -- (b) 否定命题 example (n : Nat) : n + 0 = n := by sorry -- (c) 含自由变量 example : ∀ b : Bool, b && true = b := by sorry -- (d) Bool 穷举 -- example : Nat.Prime 104729 := by sorry -- (e) 大素数
提示:(a)(b)(d) 直接
decide;(c) 含变量用simp;(e) 超时用native_decide或norm_num。
14.8.2. 练习 2(编写实例):自定义 Decidable
def IsPalindrome (l : List Char) : Prop := l = l.reverse instance (l : List Char) : Decidable (IsPalindrome l) := sorry -- 验证 -- example : IsPalindrome ['a', 'b', 'a'] := by decide -- example : ¬ IsPalindrome ['a', 'b', 'c'] := by decide
提示:用
inferInstanceAs (Decidable (l = l.reverse))归约到DecidableEq。
14.8.3. 练习 3(性能选择):为每题选择最合适的 tactic
-- example : Nat.Prime 10007 := by sorry -- (a) 大素数 example : ∀ n : Nat, n < n + 1 := by sorry -- (b) 全称线性不等式 example : ∀ a b : Fin 4, a * b = b * a := by sorry -- (c) 有限穷举 example : (3 : ℤ) ^ 5 = 243 := by sorry -- (d) 数值幂运算
提示:(a)
norm_num;(b)omega;(c)decide;(d)norm_num。
14.8.4. 练习 4(调试):修复失败的 decide
-- (a) 为什么超时?给出两种修复方案。 -- example : Nat.Prime 1000003 := by decide -- (b) 为什么失败?只改 tactic 前面的代码。 -- example (h : n = 5) : n + 1 = 6 := by decide
提示:(a) 换
native_decide或norm_num;(b) 在decide前加subst h;。
14.8.5. 练习 5(进阶):组合判定实例
def InRange (lo hi n : Nat) : Prop := lo ≤ n ∧ n < hi instance (lo hi n : Nat) : Decidable (InRange lo hi n) := sorry -- 验证 -- example : InRange 3 10 5 := by decide -- example : ¬ InRange 3 10 2 := by decide
提示:展开后是
lo ≤ n ∧ n < hi,用inferInstanceAs归约到And的组合实例。
14.9. 13.9 小结
-
Decidable:类型类,编码"可计算的真假判定 + 证据",是Prop/Bool的桥梁 -
decide:内核规约,绝对可信(仅依赖 kernel),但速度受限于解释执行 -
native_decide:编译到原生代码,快几个数量级,但引入ofReduceBool公理 -
失败诊断:缺实例 → 检查Decidable;超时 → 换 tactic;含变量 → 先化简;假命题 → 检查逻辑 -
编写实例:deriving、dependent if、inferInstanceAs归约、逻辑组合 -
选择原则:小穷举decide,大穷举native_decide,算术omega/norm_num
decide 和 native_decide 是 Lean 自动化的"兜底手段"——
当问题可以归约为有限计算时,它们总能给出答案。
理解它们的能力边界与失败模式,才能在实战中高效组合各种 tactic。