Lean 4 定理证明

5. 证明策略🔗

在本章中,我们描述了另一种构建证明的方法,即使用 策略(Tactic) 。 一个证明项代表一个数学证明;策略是描述如何建立这样一个证明的命令或指令。 你可以在数学证明开始时非正式地说:“为了证明条件的必要性,展开定义,应用前面的定理,并进行简化。” 就像这些指令告诉读者如何构建证明一样,策略告诉 Lean 如何构建证明。 它们自然而然地支持增量式的证明书写,在这种写作方式中,你将分解一个证明,并一步步地实现目标。

我们将把由策略序列组成的证明描述为“策略式”(tactic-style)证明, 前几章的证明我们称为“项式”(term-style)证明。每种风格都有自己的优点和缺点。 例如,策略式证明可能更难读,因为它们要求读者预测或猜测每条指令的结果。 但它们一般更短,更容易写。此外,策略提供了一个使用 Lean 自动化的途径,因为自动化程序本身就是策略。

5.1. 进入策略模式🔗

从概念上讲,陈述一个定理或引入一个 have 声明会产生一个目标, 即构造一个具有预期类型的项的目标。例如,下面创建的目标是构建一个类型为 p q p 的项, 上下文中有常量 p q : Prophp : phq : q

theorem declaration uses 'sorry'test (p q : Prop) (hp : p) (hq : q) : p q p := p:Propq:Prophp:phq:qp q p All goals completed! 🐙

写成目标如下:

p:Propq:Prophp:phq:qp q p

事实上,如果你把上面的例子中的“sorry”换成下划线,Lean 会报告说,正是这个目标没有得到解决。

通常情况下,你会通过写一个明确的项来满足这样的目标。但在任何需要项的地方, Lean 允许我们插入一个 by <tactics> 块,其中 <tactics> 是一串命令, 用分号或换行符分开。你可以用下面这种方式来证明上面的定理:

theorem test (p q : Prop) (hp : p) (hq : q) : p q p := p:Propq:Prophp:phq:qp q p p:Propq:Prophp:phq:qpp:Propq:Prophp:phq:qq p p:Propq:Prophp:phq:qq p p:Propq:Prophp:phq:qqp:Propq:Prophp:phq:qp p:Propq:Prophp:phq:qp All goals completed! 🐙

我们经常将 by 关键字放在前一行,并将上面的例子写为:

theorem test (p q : Prop) (hp : p) (hq : q) : p q p := p:Propq:Prophp:phq:qp q p p:Propq:Prophp:phq:qpp:Propq:Prophp:phq:qq p p:Propq:Prophp:phq:qq p p:Propq:Prophp:phq:qqp:Propq:Prophp:phq:qp p:Propq:Prophp:phq:qp All goals completed! 🐙

apply 策略应用于一个表达式,被视为表示一个有零或多个参数的函数。 它将结论与当前目标中的表达式统一起来,并为剩余的参数创建新的目标, 只要后面的参数不依赖于它们。在上面的例子中,命令 apply And.intro 产生了两个子目标:

p:Propq:Prophp:phq:qpp:Propq:Prophp:phq:qq p

第一个目标是通过命令 exact hp 来实现的。exact 命令只是 apply 的一个变体, 它表示所给的表达式应该准确地填充目标。在策略证明中使用它很有益,因为它如果失败那么表明出了问题。 它也比 apply 更稳健,因为繁饰器在处理被应用的表达式时,会考虑到目标所预期的类型。 然而,在这种情况下,apply 也可以很好地工作。

你可以用 #print 命令查看所产生的证明项:

theorem test : (p q : Prop), p q p q p := fun p q hp hq => hp, hq, hp#print test
theorem test :  (p q : Prop), p  q  p  q  p :=
fun p q hp hq => hp, hq, hp

你可以循序渐进地写一个策略脚本。在 VS Code 中,你可以通过按 CtrlShiftEnter 打开一个窗口来显示信息, 然后只要光标在策略块中,该窗口就会显示当前的目标。如果证明是不完整的, 标记 by 会被装饰成一条红色的波浪线,错误信息中包含剩余的目标。

策略命令可以接受复合表达式,而不仅仅是单一标识符。下面是前面证明的一个简短版本:

theorem test (p q : Prop) (hp : p) (hq : q) : p q p := p:Propq:Prophp:phq:qp q p p:Propq:Prophp:phq:qq p All goals completed! 🐙

不出所料,它产生了完全相同的证明项:

theorem test : (p q : Prop), p q p q p := fun p q hp hq => hp, hq, hp#print test
theorem test :  (p q : Prop), p  q  p  q  p :=
fun p q hp hq => hp, hq, hp

应用多个策略可以通过用分号连接写在一行中。

theorem test (p q : Prop) (hp : p) (hq : q) : p q p := p:Propq:Prophp:phq:qp q p p:Propq:Prophp:phq:qq p; All goals completed! 🐙

可能产生多个子目标的策略通常对子目标进行标记。例如,策略 apply And.intro 将第一个子目标标记为 leftp:Propq:Prophp:phq:qp,将第二个标记为 rightp:Propq:Prophp:phq:qq p。在 apply 策略的情况下, 标签是从 And.intro 声明中使用的参数名称推断出来的。你可以使用符号 case <tag> => <tactics> 来结构化你的策略。下面是本章中第一个策略证明的结构化版本。

theorem test (p q : Prop) (hp : p) (hq : q) : p q p := p:Propq:Prophp:phq:qp q p p:Propq:Prophp:phq:qpp:Propq:Prophp:phq:qq p case left p:Propq:Prophp:phq:qp All goals completed! 🐙 case right p:Propq:Prophp:phq:qq p p:Propq:Prophp:phq:qqp:Propq:Prophp:phq:qp case left p:Propq:Prophp:phq:qq All goals completed! 🐙 case right p:Propq:Prophp:phq:qp All goals completed! 🐙

使用 case 标记,你也可以在 leftp:Propq:Prophp:phq:qp 之前先解决子目标 rightp:Propq:Prophp:phq:qq p

theorem test (p q : Prop) (hp : p) (hq : q) : p q p := p:Propq:Prophp:phq:qp q p p:Propq:Prophp:phq:qpp:Propq:Prophp:phq:qq p case right p:Propq:Prophp:phq:qq p p:Propq:Prophp:phq:qqp:Propq:Prophp:phq:qp case left p:Propq:Prophp:phq:qq All goals completed! 🐙 case right p:Propq:Prophp:phq:qp All goals completed! 🐙 case left p:Propq:Prophp:phq:qp All goals completed! 🐙

注意,Lean 将其他目标隐藏在 case 块内。在 case left => 之后,证明状态是:

p:Propq:Prophp:phq:qq

我们说 case 是“专注”于选定的目标。 此外,如果所选目标在 case 块的末尾没有完全解决, Lean 会标记一个错误。

对于简单的子目标,可能不值得使用其标签来选择一个子目标,但你可能仍然想要结构化证明。 Lean 还提供了“子弹”符号 . <tactics>(或 · <tactics>)来结构化证明:

theorem test (p q : Prop) (hp : p) (hq : q) : p q p := p:Propq:Prophp:phq:qp q p p:Propq:Prophp:phq:qpp:Propq:Prophp:phq:qq p p:Propq:Prophp:phq:qp All goals completed! 🐙 p:Propq:Prophp:phq:qq p p:Propq:Prophp:phq:qqp:Propq:Prophp:phq:qp p:Propq:Prophp:phq:qq All goals completed! 🐙 p:Propq:Prophp:phq:qp All goals completed! 🐙

5.2. 基本策略🔗

除了 applyexact 之外,另一个有用的策略是 intro, 它引入了一个假设。下面是我们在前一章中证明的命题逻辑中的一个等价性的例子,现在用策略来证明。

example (p q r : Prop) : p (q r) (p q) (p r) := p:Propq:Propr:Propp (q r) p q p r p:Propq:Propr:Propp (q r) p q p rp:Propq:Propr:Propp q p r p (q r) p:Propq:Propr:Propp (q r) p q p r p:Propq:Propr:Proph:p (q r)p q p r p:Propq:Propr:Proph:p (q r)q p q p rp:Propq:Propr:Proph:p (q r)r p q p r p:Propq:Propr:Proph:p (q r)q p q p r p:Propq:Propr:Proph:p (q r)hq:qp q p r p:Propq:Propr:Proph:p (q r)hq:qp q p:Propq:Propr:Proph:p (q r)hq:qpp:Propq:Propr:Proph:p (q r)hq:qq p:Propq:Propr:Proph:p (q r)hq:qp All goals completed! 🐙 p:Propq:Propr:Proph:p (q r)hq:qq All goals completed! 🐙 p:Propq:Propr:Proph:p (q r)r p q p r p:Propq:Propr:Proph:p (q r)hr:rp q p r p:Propq:Propr:Proph:p (q r)hr:rp r p:Propq:Propr:Proph:p (q r)hr:rpp:Propq:Propr:Proph:p (q r)hr:rr p:Propq:Propr:Proph:p (q r)hr:rp All goals completed! 🐙 p:Propq:Propr:Proph:p (q r)hr:rr All goals completed! 🐙 p:Propq:Propr:Propp q p r p (q r) p:Propq:Propr:Proph:p q p rp (q r) p:Propq:Propr:Proph:p q p rp q p (q r)p:Propq:Propr:Proph:p q p rp r p (q r) p:Propq:Propr:Proph:p q p rp q p (q r) p:Propq:Propr:Proph:p q p rhpq:p qp (q r) p:Propq:Propr:Proph:p q p rhpq:p qpp:Propq:Propr:Proph:p q p rhpq:p qq r p:Propq:Propr:Proph:p q p rhpq:p qp All goals completed! 🐙 p:Propq:Propr:Proph:p q p rhpq:p qq r p:Propq:Propr:Proph:p q p rhpq:p qq All goals completed! 🐙 p:Propq:Propr:Proph:p q p rp r p (q r) p:Propq:Propr:Proph:p q p rhpr:p rp (q r) p:Propq:Propr:Proph:p q p rhpr:p rpp:Propq:Propr:Proph:p q p rhpr:p rq r p:Propq:Propr:Proph:p q p rhpr:p rp All goals completed! 🐙 p:Propq:Propr:Proph:p q p rhpr:p rq r p:Propq:Propr:Proph:p q p rhpr:p rr All goals completed! 🐙

intro 命令可以更普遍地用于引入任何类型的变量:

example (α : Type) : α α := α:Typeα α α:Typea:αα All goals completed! 🐙 example (α : Type) : x : α, x = x := α:Type (x : α), x = x α:Typex:αx = x All goals completed! 🐙

你可以同时引入好几个变量:

example : a b c : Nat, a = b a = c c = b := (a b c : Nat), a = b a = c c = b intro a a:Natb:Nat (c : Nat), a = b a = c c = b a:Natb:Natc:Nata = b a = c c = b a:Natb:Natc:Nath₁:a = ba = c c = b a:Natb:Natc:Nath₁:a = bh₂:a = cc = b All goals completed! 🐙

由于 apply 策略是一个用于交互式构造函数应用的命令,intro 策略是一个用于交互式构造函数抽象的命令 (即 fun x => e 形式的项)。 与 lambda 抽象符号一样, intro 策略允许我们使用隐式的 match

example (p q : α Prop) : ( x, p x q x) x, q x p x := α:Sort u_1p:α Propq:α Prop( x, p x q x) x, q x p x α:Sort u_1p:α Propq:α Propw:αhpw:p whqw:q w x, q x p x All goals completed! 🐙

就像 match 表达式一样,你也可以提供多个选项。

example (p q : α Prop) : ( x, p x q x) x, q x p x := α:Sort u_1p:α Propq:α Prop( x, p x q x) x, q x p x intro α:Sort u_1p:α Propq:α Propx✝: x, p x q xw:αh:p w x, q x p x All goals completed! 🐙 α:Sort u_1p:α Propq:α Propx✝: x, p x q xw:αh:q w x, q x p x All goals completed! 🐙

intros 策略可以在没有任何参数的情况下使用,在这种情况下,它选择名字并尽可能多地引入变量。 稍后你会看到一个例子。

assumption 策略在当前目标的背景下查看假设,如果有一个与结论相匹配的假设,它就会应用这个假设。

variable (x y z w : Nat) example (h₁ : x = y) (h₂ : y = z) (h₃ : z = w) : x = w := x:Naty:Natz:Natw:Nath₁:x = yh₂:y = zh₃:z = wx = w x:Naty:Natz:Natw:Nath₁:x = yh₂:y = zh₃:z = wy = w x:Naty:Natz:Natw:Nath₁:x = yh₂:y = zh₃:z = wz = w All goals completed! 🐙 -- applied h₃

若有必要,它会在结论中统一元变量:

variable (x y z w : Nat) example (h₁ : x = y) (h₂ : y = z) (h₃ : z = w) : x = w := x:Naty:Natz:Natw:Nath₁:x = yh₂:y = zh₃:z = wx = w x:Naty:Natz:Natw:Nath₁:x = yh₂:y = zh₃:z = wx = ?bx:Naty:Natz:Natw:Nath₁:x = yh₂:y = zh₃:z = w?b = wx:Naty:Natz:Natw:Nath₁:x = yh₂:y = zh₃:z = wNat x:Naty:Natz:Natw:Nath₁:x = yh₂:y = zh₃:z = w?b = w -- solves x = ?b with h₁ x:Naty:Natz:Natw:Nath₁:x = yh₂:y = zh₃:z = wy = ?h₂.bx:Naty:Natz:Natw:Nath₁:x = yh₂:y = zh₃:z = w?h₂.b = wx:Naty:Natz:Natw:Nath₁:x = yh₂:y = zh₃:z = wNat x:Naty:Natz:Natw:Nath₁:x = yh₂:y = zh₃:z = w?h₂.b = w -- solves y = ?h₂.b with h₂ All goals completed! 🐙 -- solves z = w with h₃

下面的例子使用 intros 命令来自动引入三个变量和两个假设:

example : a b c : Nat, a = b a = c c = b := (a b c : Nat), a = b a = c c = b a✝²:Natb✝:Natc✝:Nata✝¹:a✝² = b✝a✝:a✝² = c✝c✝ = b✝ a✝²:Natb✝:Natc✝:Nata✝¹:a✝² = b✝a✝:a✝² = c✝c✝ = ?ba✝²:Natb✝:Natc✝:Nata✝¹:a✝² = b✝a✝:a✝² = c✝?b = b✝a✝²:Natb✝:Natc✝:Nata✝¹:a✝² = b✝a✝:a✝² = c✝Nat a✝²:Natb✝:Natc✝:Nata✝¹:a✝² = b✝a✝:a✝² = c✝?b = c✝a✝²:Natb✝:Natc✝:Nata✝¹:a✝² = b✝a✝:a✝² = c✝?b = b✝a✝²:Natb✝:Natc✝:Nata✝¹:a✝² = b✝a✝:a✝² = c✝Nat a✝²:Natb✝:Natc✝:Nata✝¹:a✝² = b✝a✝:a✝² = c✝?b = b✝ All goals completed! 🐙

请注意,由 Lean 自动生成的名称在默认情况下是不可访问的。其动机是为了确保你的策略证明不依赖于自动生成的名字, 并因此而更加稳健。然而,你可以使用组合器 unhygienic 来禁用这一限制。

example : a b c : Nat, a = b a = c c = b := (a b c : Nat), a = b a = c c = b unhygienic a:Natb:Natc:Nata_1:a = ba_2:a = cc = b a:Natb:Natc:Nata_1:a = ba_2:a = cc = ?ba:Natb:Natc:Nata_1:a = ba_2:a = c?b = ba:Natb:Natc:Nata_1:a = ba_2:a = cNat a:Natb:Natc:Nata_1:a = ba_2:a = c?b = ca:Natb:Natc:Nata_1:a = ba_2:a = c?b = ba:Natb:Natc:Nata_1:a = ba_2:a = cNat a:Natb:Natc:Nata_1:a = ba_2:a = c?b = b All goals completed! 🐙

你也可以使用 rename_i 策略来重命名你的上下文中最近的不能访问的名字。在下面的例子中, 策略 rename_i h1 _ h2 在你的上下文中重命名了三个假设中的两个。

example : a b c d : Nat, a = b a = d a = c c = b := (a b c d : Nat), a = b a = d a = c c = b a✝³:Natb✝:Natc✝:Natd✝:Nata✝²:a✝³ = b✝a✝¹:a✝³ = d✝a✝:a✝³ = c✝c✝ = b✝ a✝¹:Natb✝:Natc✝:Natd✝:Nath1:a✝³ = b✝a✝:a✝³ = d✝h2:a✝³ = c✝c✝ = b✝ a✝¹:Natb✝:Natc✝:Natd✝:Nath1:a✝³ = b✝a✝:a✝³ = d✝h2:a✝³ = c✝c✝ = ?ba✝¹:Natb✝:Natc✝:Natd✝:Nath1:a✝³ = b✝a✝:a✝³ = d✝h2:a✝³ = c✝?b = b✝a✝¹:Natb✝:Natc✝:Natd✝:Nath1:a✝³ = b✝a✝:a✝³ = d✝h2:a✝³ = c✝Nat a✝¹:Natb✝:Natc✝:Natd✝:Nath1:a✝³ = b✝a✝:a✝³ = d✝h2:a✝³ = c✝?b = c✝a✝¹:Natb✝:Natc✝:Natd✝:Nath1:a✝³ = b✝a✝:a✝³ = d✝h2:a✝³ = c✝?b = b✝a✝¹:Natb✝:Natc✝:Natd✝:Nath1:a✝³ = b✝a✝:a✝³ = d✝h2:a✝³ = c✝Nat a✝¹:Natb✝:Natc✝:Natd✝:Nath1:a✝³ = b✝a✝:a✝³ = d✝h2:a✝³ = c✝?b = b✝ All goals completed! 🐙

rfl 策略解决那些应用于定义上相等的参数的自反关系的目标。 相等是自反的:

example (y : Nat) : (fun unused variable `x` Note: This linter can be disabled with `set_option linter.unusedVariables false`x : Nat => 0) y = 0 := y:Nat(fun x => 0) y = 0 All goals completed! 🐙

repeat 组合器可以多次使用一个策略:

example : a b c : Nat, a = b a = c c = b := (a b c : Nat), a = b a = c c = b a✝²:Natb✝:Natc✝:Nata✝¹:a✝² = b✝a✝:a✝² = c✝c✝ = b✝ a✝²:Natb✝:Natc✝:Nata✝¹:a✝² = b✝a✝:a✝² = c✝c✝ = ?ba✝²:Natb✝:Natc✝:Nata✝¹:a✝² = b✝a✝:a✝² = c✝?b = b✝a✝²:Natb✝:Natc✝:Nata✝¹:a✝² = b✝a✝:a✝² = c✝Nat a✝²:Natb✝:Natc✝:Nata✝¹:a✝² = b✝a✝:a✝² = c✝?b = c✝a✝²:Natb✝:Natc✝:Nata✝¹:a✝² = b✝a✝:a✝² = c✝?b = b✝a✝²:Natb✝:Natc✝:Nata✝¹:a✝² = b✝a✝:a✝² = c✝Nat repeat All goals completed! 🐙

另一个有时很有用的策略是 revert 策略,从某种意义上说,它是对 intro 的逆:

example (x : Nat) : x = x := x:Natx = x (x : Nat), x = x y:Naty = y All goals completed! 🐙

revert x 之后,证明状态是:

(x : Nat), x = x

intro y 之后,它是:

y:Naty = y

将一个假设还原到目标中会产生一个蕴含:

example (x y : Nat) (h : x = y) : y = x := x:Naty:Nath:x = yy = x x:Naty:Natx = y y = x x:Naty:Nath₁:x = yy = x -- goal is x y : Nat, h₁ : x = y ⊢ y = x x:Naty:Nath₁:x = yx = y All goals completed! 🐙

revert h 之后,证明状态是:

x:Naty:Natx = y y = x

intro h₁ 之后,它是:

x:Naty:Nath₁:x = yy = x

但是 revert 更聪明,因为它不仅会还原上下文中的一个元素,还会还原上下文中所有依赖它的后续元素。 例如,在上面的例子中,还原 x 会带来 h

example (x y : Nat) (h : x = y) : y = x := x:Naty:Nath:x = yy = x y:Nat (x : Nat), x = y y = x y:Natx✝:Nath✝:x✝ = yy = x✝ y:Natx✝:Nath✝:x✝ = yx✝ = y All goals completed! 🐙

revert x 之后,目标是:

y:Nat (x : Nat), x = y y = x

你还可以一次性还原多个元素:

example (x y : Nat) (h : x = y) : y = x := x:Naty:Nath:x = yy = x (x y : Nat), x = y y = x x✝:Naty✝:Nath✝:x✝ = y✝y✝ = x✝ x✝:Naty✝:Nath✝:x✝ = y✝x✝ = y✝ All goals completed! 🐙

revert x y 之后,目标是:

(x y : Nat), x = y y = x

你只能 revert 局部环境中的一个元素,也就是一个局部变量或假设。 但是你可以使用 generalize 策略将目标中的任意表达式替换为新的变量:

example : 3 = 3 := 3 = 3 x:Natx = x (x : Nat), x = x y:Naty = y All goals completed! 🐙

特别地,在 generalize 之后,目标是

x:Natx = x

上述符号的记忆法是,你通过将 3 设定为任意变量 x 来泛化目标。 要注意:不是每一个泛化都能保留目标的有效性。这里,generalize 用一个无法证明的目标取代了一个可以用 rfl 证明的目标:

declaration uses 'sorry'example : 2 + 3 = 5 := 2 + 3 = 5 x:Nat2 + x = 5 All goals completed! 🐙

在这个例子中,sorry 策略是 sorry 证明项的类似物。它关闭了当前的目标, 产生了通常的警告:使用了 sorry。为了保持之前目标的有效性,generalize 策略允许我们记录 3 已经被 x 所取代的事实。你所需要做的就是提供一个标签, generalize 使用它来存储局部上下文中的赋值:

example : 2 + 3 = 5 := 2 + 3 = 5 x:Nath:3 = x2 + x = 5 All goals completed! 🐙

generalize h : 3 = x 之后,h3 = x 的证明:

x:Nath:3 = x2 + x = 5

这里重写策略 rw 使用 h 再次将 x 替换为 3rw 策略下文将继续讨论。

5.3. 更多策略🔗

一些额外的策略对于建构和析构命题以及数据很有用。例如,当应用于形式为 p q 的目标时, 你可以使用 apply Or.inlapply Or.inr 等策略。 反之,cases 策略可以用来分解一个析取:

example (p q : Prop) : p q q p := p:Propq:Propp q q p p:Propq:Proph:p qq p cases h with p:Propq:Prophp:pq p p:Propq:Prophp:pp; All goals completed! 🐙 p:Propq:Prophq:qq p p:Propq:Prophq:qq; All goals completed! 🐙

注意,该语法与 match 表达式中使用的语法相似。新的子目标可以按任何顺序解决:

example (p q : Prop) : p q q p := p:Propq:Propp q q p p:Propq:Proph:p qq p cases h with p:Propq:Prophq:qq p p:Propq:Prophq:qq; All goals completed! 🐙 p:Propq:Prophp:pq p p:Propq:Prophp:pp; All goals completed! 🐙

你也可以使用一个(非结构化的)cases,而不使用 with,并为每个备选情况制定一个策略:

example (p q : Prop) : p q q p := p:Propq:Propp q q p p:Propq:Proph:p qq p p:Propq:Proph✝:pq pp:Propq:Proph✝:qq p p:Propq:Proph✝:ppp:Propq:Proph✝:qq p p:Propq:Proph✝:qq p p:Propq:Proph✝:qq All goals completed! 🐙

(非结构化的)cases 在你可以用同一个策略来解决子任务时格外有用:

example (p : Prop) : p p p := p:Propp p p p:Proph:p pp p:Proph✝:ppp:Proph✝:pp repeat All goals completed! 🐙

你也可以使用组合器 tac1 <;> tac2,将 tac2 应用于策略 tac1 产生的每个子目标:

example (p : Prop) : p p p := p:Propp p p p:Proph:p pp p:Proph✝:ppp:Proph✝:pp p:Proph✝:ppp:Proph✝:pp All goals completed! 🐙

你可以与 . 符号相结合使用非结构化的 cases 策略:

example (p q : Prop) : p q q p := p:Propq:Propp q q p p:Propq:Proph:p qq p p:Propq:Proph✝:pq pp:Propq:Proph✝:qq p p:Propq:Proph✝:pq p p:Propq:Proph✝:pp All goals completed! 🐙 p:Propq:Proph✝:qq p p:Propq:Proph✝:qq All goals completed! 🐙 example (p q : Prop) : p q q p := p:Propq:Propp q q p p:Propq:Proph:p qq p p:Propq:Proph✝:pq pp:Propq:Proph✝:qq p case inr h p:Propq:Proph:qq p p:Propq:Proph:qq All goals completed! 🐙 case inl h p:Propq:Proph:pq p p:Propq:Proph:pp All goals completed! 🐙 example (p q : Prop) : p q q p := p:Propq:Propp q q p p:Propq:Proph:p qq p p:Propq:Proph✝:pq pp:Propq:Proph✝:qq p case inr h p:Propq:Proph:qq p p:Propq:Proph:qq All goals completed! 🐙 p:Propq:Proph✝:pq p p:Propq:Proph✝:pp All goals completed! 🐙

cases 策略也被用来分解一个合取:

example (p q : Prop) : p q q p := p:Propq:Propp q q p p:Propq:Proph:p qq p cases h with p:Propq:Prophp:phq:qq p p:Propq:Prophp:phq:qqp:Propq:Prophp:phq:qp; p:Propq:Prophp:phq:qp; All goals completed! 🐙

在这个例子中,应用 cases 策略后只有一个目标,h : p q 被一对假设取代, hp : phq : q

p:Propq:Prophp:phq:qq p

constructor 策略应用了唯一一个合取构造子 And.intro

有了这些策略,上一节的一个例子可以改写如下:

example (p q r : Prop) : p (q r) (p q) (p r) := p:Propq:Propr:Propp (q r) p q p r p:Propq:Propr:Propp (q r) p q p rp:Propq:Propr:Propp q p r p (q r) p:Propq:Propr:Propp (q r) p q p r p:Propq:Propr:Proph:p (q r)p q p r cases h with p:Propq:Propr:Prophp:phqr:q rp q p r p:Propq:Propr:Prophp:ph✝:qp q p rp:Propq:Propr:Prophp:ph✝:rp q p r p:Propq:Propr:Prophp:ph✝:qp q p r p:Propq:Propr:Prophp:ph✝:qp q; p:Propq:Propr:Prophp:ph✝:qpp:Propq:Propr:Prophp:ph✝:qq p:Propq:Propr:Prophp:ph✝:qpp:Propq:Propr:Prophp:ph✝:qq All goals completed! 🐙 p:Propq:Propr:Prophp:ph✝:rp q p r p:Propq:Propr:Prophp:ph✝:rp r; p:Propq:Propr:Prophp:ph✝:rpp:Propq:Propr:Prophp:ph✝:rr p:Propq:Propr:Prophp:ph✝:rpp:Propq:Propr:Prophp:ph✝:rr All goals completed! 🐙 p:Propq:Propr:Propp q p r p (q r) p:Propq:Propr:Proph:p q p rp (q r) cases h with p:Propq:Propr:Prophpq:p qp (q r) cases hpq with p:Propq:Propr:Prophp:phq:qp (q r) p:Propq:Propr:Prophp:phq:qpp:Propq:Propr:Prophp:phq:qq r; p:Propq:Propr:Prophp:phq:qq r; p:Propq:Propr:Prophp:phq:qq; All goals completed! 🐙 p:Propq:Propr:Prophpr:p rp (q r) cases hpr with p:Propq:Propr:Prophp:phr:rp (q r) p:Propq:Propr:Prophp:phr:rpp:Propq:Propr:Prophp:phr:rq r; p:Propq:Propr:Prophp:phr:rq r; p:Propq:Propr:Prophp:phr:rr; All goals completed! 🐙

你将在 归纳类型 一章中看到,这些策略是相当通用的。 cases 策略可以用来分解递归定义类型的任何元素;constructor 总是应用递归定义类型的第一个适用构造子。例如,你可以使用 casesconstructor 与一个存在量词:

example (p q : Nat Prop) : ( x, p x) x, p x q x := p:Nat Propq:Nat Prop( x, p x) x, p x q x p:Nat Propq:Nat Proph: x, p x x, p x q x cases h with p:Nat Propq:Nat Propx:Natpx:p x x, p x q x p:Nat Propq:Nat Propx:Natpx:p xp ?intro.w q ?intro.wp:Nat Propq:Nat Propx:Natpx:p xNat; p:Nat Propq:Nat Propx:Natpx:p xp ?intro.wp:Nat Propq:Nat Propx:Natpx:p xNat; All goals completed! 🐙

在这里,constructor 策略将存在性断言的第一个组成部分,即 x 的值,保留为隐式的。 它是由一个元变量表示的,这个元变量以后应该被实例化。在前面的例子中,元变量的正确值是由策略 exact px 决定的, 因为 px 的类型是 p x。如果你想明确指定存在量词的存在者,你可以使用 exists 策略来代替:

example (p q : Nat Prop) : ( x, p x) x, p x q x := p:Nat Propq:Nat Prop( x, p x) x, p x q x p:Nat Propq:Nat Proph: x, p x x, p x q x cases h with p:Nat Propq:Nat Propx:Natpx:p x x, p x q x p:Nat Propq:Nat Propx:Natpx:p xp x q x; p:Nat Propq:Nat Propx:Natpx:p xp x; All goals completed! 🐙

另一个例子:

example (p q : Nat Prop) : ( x, p x q x) x, q x p x := p:Nat Propq:Nat Prop( x, p x q x) x, q x p x p:Nat Propq:Nat Proph: x, p x q x x, q x p x cases h with p:Nat Propq:Nat Propx:Nathpq:p x q x x, q x p x cases hpq with p:Nat Propq:Nat Propx:Nathp:p xhq:q x x, q x p x All goals completed! 🐙

这些策略既可以用在命题上,也可以用在数据上。在下面的两个例子中,它们被用来定义交换乘法和加法类型组件的函数:

def swap_pair : α × β β × α := α:Type ?u.18β:Type ?u.17α × β β × α α:Type ?u.18β:Type ?u.17p:α × ββ × α α:Type ?u.18β:Type ?u.17fst✝:αsnd✝:ββ × α α:Type ?u.18β:Type ?u.17fst✝:αsnd✝:ββα:Type ?u.18β:Type ?u.17fst✝:αsnd✝:βα α:Type ?u.18β:Type ?u.17fst✝:αsnd✝:ββα:Type ?u.18β:Type ?u.17fst✝:αsnd✝:βα All goals completed! 🐙 def swap_sum : Sum α β Sum β α := α:Type ?u.196β:Type ?u.195α β β α α:Type ?u.196β:Type ?u.195p:α ββ α α:Type ?u.196β:Type ?u.195val✝:αβ αα:Type ?u.196β:Type ?u.195val✝:ββ α α:Type ?u.196β:Type ?u.195val✝:αβ α α:Type ?u.196β:Type ?u.195val✝:αα; All goals completed! 🐙 α:Type ?u.196β:Type ?u.195val✝:ββ α α:Type ?u.196β:Type ?u.195val✝:ββ; All goals completed! 🐙

在我们为变量选择的名称之前,它们的定义与有关合取和析取的类似命题的证明是相同的。 cases 策略也会对自然数进行逐情况区分:

open Nat example (P : Nat Prop) (h₀ : P 0) (h₁ : n, P (succ n)) (m : Nat) : P m := P:Nat Proph₀:P 0h₁: (n : Nat), P n.succm:NatP m cases m with P:Nat Proph₀:P 0h₁: (n : Nat), P n.succP 0 All goals completed! 🐙 P:Nat Proph₀:P 0h₁: (n : Nat), P n.succm':NatP (m' + 1) All goals completed! 🐙

cases 策略及其同伴 induction 策略将在 归纳类型的策略 一节中详述。

contradiction 策略搜索当前目标的假设中的矛盾:

example (p q : Prop) : p ¬ p q := p:Propq:Propp ¬p q p:Propq:Proph:p ¬pq p:Propq:Propleft✝:pright✝:¬pq All goals completed! 🐙

你也可以在策略块中使用 match

example (p q r : Prop) : p (q r) (p q) (p r) := p:Propq:Propr:Propp (q r) p q p r p:Propq:Propr:Propp (q r) p q p rp:Propq:Propr:Propp q p r p (q r) p:Propq:Propr:Propp (q r) p q p r p:Propq:Propr:Proph:p (q r)p q p r match h with p:Propq:Propr:Proph:p (q r)left✝:ph✝:qp q p r p:Propq:Propr:Proph:p (q r)left✝:ph✝:qp q; p:Propq:Propr:Proph:p (q r)left✝:ph✝:qpp:Propq:Propr:Proph:p (q r)left✝:ph✝:qq p:Propq:Propr:Proph:p (q r)left✝:ph✝:qpp:Propq:Propr:Proph:p (q r)left✝:ph✝:qq All goals completed! 🐙 p:Propq:Propr:Proph:p (q r)left✝:ph✝:rp q p r p:Propq:Propr:Proph:p (q r)left✝:ph✝:rp r; p:Propq:Propr:Proph:p (q r)left✝:ph✝:rpp:Propq:Propr:Proph:p (q r)left✝:ph✝:rr p:Propq:Propr:Proph:p (q r)left✝:ph✝:rpp:Propq:Propr:Proph:p (q r)left✝:ph✝:rr All goals completed! 🐙 p:Propq:Propr:Propp q p r p (q r) p:Propq:Propr:Proph:p q p rp (q r) match h with p:Propq:Propr:Proph:p q p rhp:phq:qp (q r) p:Propq:Propr:Proph:p q p rhp:phq:qpp:Propq:Propr:Proph:p q p rhp:phq:qq r; p:Propq:Propr:Proph:p q p rhp:phq:qq r; p:Propq:Propr:Proph:p q p rhp:phq:qq; All goals completed! 🐙 p:Propq:Propr:Proph:p q p rhp:phr:rp (q r) p:Propq:Propr:Proph:p q p rhp:phr:rpp:Propq:Propr:Proph:p q p rhp:phr:rq r; p:Propq:Propr:Proph:p q p rhp:phr:rq r; p:Propq:Propr:Proph:p q p rhp:phr:rr; All goals completed! 🐙

你可以将 intromatch 结合起来,然后上例就可以如下地写出:

example (p q r : Prop) : p (q r) (p q) (p r) := p:Propq:Propr:Propp (q r) p q p r p:Propq:Propr:Propp (q r) p q p rp:Propq:Propr:Propp q p r p (q r) p:Propq:Propr:Propp (q r) p q p r intro p:Propq:Propr:Propx✝:p (q r)hp:phq:qp q p r p:Propq:Propr:Propx✝:p (q r)hp:phq:qp q; p:Propq:Propr:Propx✝:p (q r)hp:phq:qpp:Propq:Propr:Propx✝:p (q r)hp:phq:qq p:Propq:Propr:Propx✝:p (q r)hp:phq:qpp:Propq:Propr:Propx✝:p (q r)hp:phq:qq All goals completed! 🐙 p:Propq:Propr:Propx✝:p (q r)hp:phr:rp q p r p:Propq:Propr:Propx✝:p (q r)hp:phr:rp r; p:Propq:Propr:Propx✝:p (q r)hp:phr:rpp:Propq:Propr:Propx✝:p (q r)hp:phr:rr p:Propq:Propr:Propx✝:p (q r)hp:phr:rpp:Propq:Propr:Propx✝:p (q r)hp:phr:rr All goals completed! 🐙 p:Propq:Propr:Propp q p r p (q r) intro p:Propq:Propr:Propx✝:p q p rhp:phq:qp (q r) p:Propq:Propr:Propx✝:p q p rhp:phq:qpp:Propq:Propr:Propx✝:p q p rhp:phq:qq r; p:Propq:Propr:Propx✝:p q p rhp:phq:qq r; p:Propq:Propr:Propx✝:p q p rhp:phq:qq; All goals completed! 🐙 p:Propq:Propr:Propx✝:p q p rhp:phr:rp (q r) p:Propq:Propr:Propx✝:p q p rhp:phr:rpp:Propq:Propr:Propx✝:p q p rhp:phr:rq r; p:Propq:Propr:Propx✝:p q p rhp:phr:rq r; p:Propq:Propr:Propx✝:p q p rhp:phr:rr; All goals completed! 🐙

5.4. 结构化策略证明🔗

策略通常提供了建立证明的有效方法,但一长串指令会掩盖论证的结构。在这一节中,我们将描述一些有助于为策略式证明提供结构的方法,使这种证明更易读,更稳健。

Lean 的证明写作语法的一个优点是,它可以混合项式和策略式证明,并在两者之间自由转换。 例如,策略 applyexact 可以传入任意的项,你可以用 haveshow 等等来写这些项。 反之,当写一个任意的 Lean 项时,你总是可以通过插入一个 by 块来调用策略模式。下面是一个简易例子:

example (p q r : Prop) : p (q r) (p q) (p r) := p:Propq:Propr:Propp (q r) p q p r p:Propq:Propr:Proph:p (q r)p q p r exact have hp : p := h.left have hqr : q r := h.right show (p q) (p r) p:Propq:Propr:Propp (q r) p q p r cases hqr with p:Propq:Propr:Proph:p (q r)hp:p := h.lefthq:qp q p r All goals completed! 🐙 p:Propq:Propr:Proph:p (q r)hp:p := h.lefthr:rp q p r All goals completed! 🐙

更自然一点:

example (p q r : Prop) : p (q r) (p q) (p r) := p:Propq:Propr:Propp (q r) p q p r p:Propq:Propr:Propp (q r) p q p rp:Propq:Propr:Propp q p r p (q r) p:Propq:Propr:Propp (q r) p q p r p:Propq:Propr:Proph:p (q r)p q p r cases h.right with p:Propq:Propr:Proph:p (q r)hq:qp q p r All goals completed! 🐙 p:Propq:Propr:Proph:p (q r)hr:rp q p r All goals completed! 🐙 p:Propq:Propr:Propp q p r p (q r) p:Propq:Propr:Proph:p q p rp (q r) cases h with p:Propq:Propr:Prophpq:p qp (q r) All goals completed! 🐙 p:Propq:Propr:Prophpr:p rp (q r) All goals completed! 🐙

事实上,有一个 show 策略,它类似于证明项中的 show 表达式。 它只是简单地声明即将被解决的目标的类型,同时保持策略模式。

example (p q r : Prop) : p (q r) (p q) (p r) := p:Propq:Propr:Propp (q r) p q p r p:Propq:Propr:Propp (q r) p q p rp:Propq:Propr:Propp q p r p (q r) p:Propq:Propr:Propp (q r) p q p r p:Propq:Propr:Proph:p (q r)p q p r cases h.right with p:Propq:Propr:Proph:p (q r)hq:qp q p r p:Propq:Propr:Proph:p (q r)hq:qp q p r All goals completed! 🐙 p:Propq:Propr:Proph:p (q r)hr:rp q p r p:Propq:Propr:Proph:p (q r)hr:rp q p r All goals completed! 🐙 p:Propq:Propr:Propp q p r p (q r) p:Propq:Propr:Proph:p q p rp (q r) cases h with p:Propq:Propr:Prophpq:p qp (q r) p:Propq:Propr:Prophpq:p qp (q r) All goals completed! 🐙 p:Propq:Propr:Prophpr:p rp (q r) p:Propq:Propr:Prophpr:p rp (q r) All goals completed! 🐙

show 策略其实可以被用来重写一些定义等价的目标:

example (n : Nat) : n + 1 = Nat.succ n := n:Natn + 1 = n.succ n:Natn.succ = n.succ All goals completed! 🐙

还有一个 have 策略,它引入了一个新的子目标,就像写证明项时一样:

example (p q r : Prop) : p (q r) (p q) (p r) := p:Propq:Propr:Propp (q r) p q p r p:Propq:Propr:Prophp:phqr:q rp q p r p:Propq:Propr:Prophp:phqr:q rp q p r cases hqr with p:Propq:Propr:Prophp:phq:qp q p r p:Propq:Propr:Prophp:phq:qhpq:p q := hp, hqp q p r p:Propq:Propr:Prophp:phq:qhpq:p q := hp, hqp q All goals completed! 🐙 p:Propq:Propr:Prophp:phr:rp q p r p:Propq:Propr:Prophp:phr:rhpr:p r := hp, hrp q p r p:Propq:Propr:Prophp:phr:rhpr:p r := hp, hrp r All goals completed! 🐙

与证明项一样,你可以省略 have 策略中的标签,在这种情况下,将使用默认标签 this

example (p q r : Prop) : p (q r) (p q) (p r) := p:Propq:Propr:Propp (q r) p q p r p:Propq:Propr:Prophp:phqr:q rp q p r p:Propq:Propr:Prophp:phqr:q rp q p r cases hqr with p:Propq:Propr:Prophp:phq:qp q p r p:Propq:Propr:Prophp:phq:qthis:p q := hp, hqp q p r p:Propq:Propr:Prophp:phq:qthis:p q := hp, hqp q All goals completed! 🐙 p:Propq:Propr:Prophp:phr:rp q p r p:Propq:Propr:Prophp:phr:rthis:p r := hp, hrp q p r p:Propq:Propr:Prophp:phr:rthis:p r := hp, hrp r All goals completed! 🐙

have 策略中的类型可以省略,所以你可以写 have hp := h.lefthave hqr := h.right。 事实上,使用这种符号,你甚至可以省略类型和标签,在这种情况下,新的事实是用标签 this 引入的:

example (p q r : Prop) : p (q r) (p q) (p r) := p:Propq:Propr:Propp (q r) p q p r p:Propq:Propr:Prophp:phqr:q rp q p r cases hqr with p:Propq:Propr:Prophp:phq:qp q p r p:Propq:Propr:Prophp:phq:qthis:p q := hp, hqp q p r p:Propq:Propr:Prophp:phq:qthis:p q := hp, hqp q; All goals completed! 🐙 p:Propq:Propr:Prophp:phr:rp q p r p:Propq:Propr:Prophp:phr:rthis:p r := hp, hrp q p r p:Propq:Propr:Prophp:phr:rthis:p r := hp, hrp r; All goals completed! 🐙

Lean 还有一个 let 策略,它类似于 have 策略,但用于引入局部定义而不是辅助事实。 它是证明项中 let 的策略模拟:

example : x, x + 2 = 8 := x, x + 2 = 8 a:Nat := 3 * 2 x, x + 2 = 8 All goals completed! 🐙

have 一样,你可以通过写 let a := 3 * 2 来让类型隐式。 lethave 的区别在于 let 在上下文中引入了一个局部定义, 这样局部声明的定义就可以在证明中展开。

我们已经使用 . 来创建嵌套的策略块。在嵌套块中,Lean 聚焦于第一个目标, 如果该目标在块结束时还没有被完全解决,就会产生一个错误。这有助于指示由一个策略引入的多个子目标的独立证明。 符号 . 是空格敏感的,并依靠缩进来检测策略块是否结束。或者,你可以使用大括号和分号来定义策略块:

example (p q r : Prop) : p (q r) (p q) (p r) := p:Propq:Propr:Propp (q r) p q p r p:Propq:Propr:Propp (q r) p q p rp:Propq:Propr:Propp q p r p (q r) p:Propq:Propr:Propp (q r) p q p r p:Propq:Propr:Proph:p (q r)p q p r; p:Propq:Propr:Proph:p (q r)h✝:qp q p rp:Propq:Propr:Proph:p (q r)h✝:rp q p r; p:Propq:Propr:Proph:p (q r)h✝:qp q p r p:Propq:Propr:Proph:p (q r)h✝:qp q p r; All goals completed! 🐙 } p:Propq:Propr:Proph:p (q r)h✝:rp q p r p:Propq:Propr:Proph:p (q r)h✝:rp q p r; All goals completed! 🐙 } } p:Propq:Propr:Propp q p r p (q r) p:Propq:Propr:Proph:p q p rp (q r); p:Propq:Propr:Proph✝:p qp (q r)p:Propq:Propr:Proph✝:p rp (q r); p:Propq:Propr:Proph✝:p qp (q r) p:Propq:Propr:Proph✝:p qp (q r); p:Propq:Propr:Prophpq:p qp (q r); All goals completed! 🐙 } p:Propq:Propr:Proph✝:p rp (q r) p:Propq:Propr:Proph✝:p rp (q r); p:Propq:Propr:Prophpr:p rp (q r); All goals completed! 🐙 } }

使用缩进来构造证明是很有用的:每当一个策略留下一个以上的子目标时,我们就通过将剩余的子目标封闭在块中并缩进来分离它们。 因此,如果将定理 foo 应用于一个单一的目标产生了四个子目标,人们会期望证明看起来像这样:

  apply foo
  . <proof of first goal>
  . <proof of second goal>
  . <proof of third goal>
  . <proof of final goal>

或者

  apply foo
  case <tag of first goal>  => <proof of first goal>
  case <tag of second goal> => <proof of second goal>
  case <tag of third goal>  => <proof of third goal>
  case <tag of final goal>  => <proof of final goal>

或者

  apply foo
  { <proof of first goal>  }
  { <proof of second goal> }
  { <proof of third goal>  }
  { <proof of final goal>  }

5.5. 策略组合子🔗

策略组合子(Tactic combinator) 是从旧策略形成新策略的操作。在 by 块中已经隐含了一个顺序组合子:

example (p q : Prop) (hp : p) : p q := p:Propq:Prophp:pp q p:Propq:Prophp:pp; All goals completed! 🐙

在这里,apply Or.inl; assumption 在功能上等同于一个单一的策略, 它首先应用 apply Or.inl,然后应用 assumption

t₁ <;> t₂ 中,<;> 操作符提供了顺序操作的一个 并行 版本: t₁ 被应用于当前的目标,然后 t₂ 被应用于 所有 产生的子目标:

example (p q : Prop) (hp : p) (hq : q) : p q := p:Propq:Prophp:phq:qp q p:Propq:Prophp:phq:qpp:Propq:Prophp:phq:qq p:Propq:Prophp:phq:qpp:Propq:Prophp:phq:qq All goals completed! 🐙

当产生的目标可以以统一的方式完成时,或者至少当可以在所有目标上统一取得进展时,这特别有用。

first | t₁ | t₂ | ... | tₙ 应用每个 tᵢ 直到其中一个成功,否则失败:

example (p q : Prop) (hp : p) : p q := p:Propq:Prophp:pp q first | p:Propq:Prophp:pp; All goals completed! 🐙 | apply Or.inr; assumption example (p q : Prop) (hq : q) : p q := p:Propq:Prophq:qp q first | p:Propq:Prophq:qp; p:Propq:Prophq:qp | p:Propq:Prophq:qq; All goals completed! 🐙

在第一个例子中,左边的分支成功了,而在第二个例子中,右边的分支成功了。在接下来的三个例子中,同样的复合策略在每种情况下都成功了:

example (p q r : Prop) (hp : p) : p q r := p:Propq:Propr:Prophp:pp q r repeat (first | p:Propq:Propr:Prophp:pp; All goals completed! 🐙 | apply Or.inr | assumption) example (p q r : Prop) (hq : q) : p q r := p:Propq:Propr:Prophq:qp q r repeat (first | p:Propq:Propr:Prophq:qq; All goals completed! 🐙 | p:Propq:Propr:Prophq:qq r | assumption) example (p q r : Prop) (hr : r) : p q r := p:Propq:Propr:Prophr:rp q r repeat (first | p:Propq:Propr:Prophr:rr; p:Propq:Propr:Prophr:rq | p:Propq:Propr:Prophr:rr | All goals completed! 🐙)

该策略尝试立即通过 assumption 解决左边的析取项;如果失败了,它尝试聚焦于右边的析取项;如果这也不起作用,它就调用 assumption 策略。

毫无疑问,你现在已经注意到策略会失败。事实上,正是“失败”状态导致了 first 组合子回溯并尝试下一个策略。 try 组合子建立了一个总是成功的策略,尽管可能是以一种平凡的方式: try t 执行 t 并报告成功,即使 t 失败。 它等同于 first| t |skip,其中 skip 是一个什么都不做的策略(并且成功地做到了)。 在下一个例子中,第二个 constructor 在右边的合取项 q r 上成功了(记住析取和合取是向右结合的), 但在第一个合取项上失败了。try 策略确保了顺序组合的成功:

example (p q r : Prop) (hp : p) (hq : q) (hr : r) : p q r := p:Propq:Propr:Prophp:phq:qhr:rp q r p:Propq:Propr:Prophp:phq:qhr:rpp:Propq:Propr:Prophp:phq:qhr:rq r p:Propq:Propr:Prophp:phq:qhr:rpp:Propq:Propr:Prophp:phq:qhr:rq r (try p:Propq:Propr:Prophp:phq:qhr:rqp:Propq:Propr:Prophp:phq:qhr:rr) p:Propq:Propr:Prophp:phq:qhr:rpp:Propq:Propr:Prophp:phq:qhr:rqp:Propq:Propr:Prophp:phq:qhr:rr All goals completed! 🐙

小心:repeat (try t) 将永远循环下去,因为内部策略永远不会失败。

在证明中,经常会有多个未完成的目标。并行顺序是安排将单一策略应用于多个目标的一种方式,但还有其他方式可以做到这一点。 例如,all_goals tt 应用于所有开启的目标:

example (p q r : Prop) (hp : p) (hq : q) (hr : r) : p q r := p:Propq:Propr:Prophp:phq:qhr:rp q r p:Propq:Propr:Prophp:phq:qhr:rpp:Propq:Propr:Prophp:phq:qhr:rq r all_goals (try p:Propq:Propr:Prophp:phq:qhr:rqp:Propq:Propr:Prophp:phq:qhr:rr) all_goals All goals completed! 🐙

在这种情况下,any_goals 策略提供了一个更稳健的解决方案。 它类似于 all_goals,只是如果它的参数在至少一个目标上成功,它就成功:

example (p q r : Prop) (hp : p) (hq : q) (hr : r) : p q r := p:Propq:Propr:Prophp:phq:qhr:rp q r p:Propq:Propr:Prophp:phq:qhr:rpp:Propq:Propr:Prophp:phq:qhr:rq r any_goals p:Propq:Propr:Prophp:phq:qhr:rqp:Propq:Propr:Prophp:phq:qhr:rr any_goals All goals completed! 🐙

下面 by 块中的第一个策略重复地拆分合取项:

example (p q r : Prop) (hp : p) (hq : q) (hr : r) : p ((p q) r) (q r p) := p:Propq:Propr:Prophp:phq:qhr:rp ((p q) r) q r p repeat (any_goals p:Propq:Propr:Prophp:phq:qhr:rp) all_goals All goals completed! 🐙

事实上,我们可以把完整的策略压缩到一行:

example (p q r : Prop) (hp : p) (hq : q) (hr : r) : p ((p q) r) (q r p) := p:Propq:Propr:Prophp:phq:qhr:rp ((p q) r) q r p repeat (any_goals (first | p:Propq:Propr:Prophp:phq:qhr:rp | All goals completed! 🐙))

组合子 focus t 确保 t 只影响当前的目标,暂时将其他目标从作用域中隐藏。 所以,如果 t 通常只影响当前的目标,focus (all_goals t)t 有同样的效果。

5.6. 重写🔗

rw 策略和 simp 策略在 计算式证明 中被简要介绍过。 在这一节和下一节中,我们将更详细地讨论它们。

rw 策略提供了一个对目标和假设应用替换的基本机制,提供了一个方便和有效的方法来处理等式。 该策略最基本的形式是 rw [t],其中 t 是一个其类型断言为等式的项。 例如,t 可以是上下文中的一个假设 h : x = y; 它可以是一个一般的引理,比如 add_comm : x y, x + y = y + x, 其中重写策略试图找到 xy 的合适实例; 或者它可以是任何断言具体或一般方程的复合项。在下面的例子中,我们使用这种基本形式,利用一个假设来重写目标。

variable (k : Nat) (f : Nat Nat) example (h₁ : f 0 = 0) (h₂ : k = 0) : f k = 0 := k:Natf:Nat Nath₁:f 0 = 0h₂:k = 0f k = 0 k:Natf:Nat Nath₁:f 0 = 0h₂:k = 0f 0 = 0 -- replace k with 0 All goals completed! 🐙 -- replace f 0 with 0

在上面的例子中,第一次使用 rw 将目标 f k = 0 中的 k 替换为 0。 然后,第二次将 f 0 替换为 0。该策略会自动关闭任何形式为 t = t 的目标。 下面是一个使用复合表达式重写的例子:

example (x y : Nat) (p : Nat Prop) (q : Prop) (h : q x = y) (h' : p y) (hq : q) : p x := x:Naty:Natp:Nat Propq:Proph:q x = yh':p yhq:qp x x:Naty:Natp:Nat Propq:Proph:q x = yh':p yhq:qp y; All goals completed! 🐙

在这里,h hq 建立了等式 x = y

多个重写可以用 rw [t_1, ..., t_n] 符号组合起来, 这只是 rw[t_1]; ...;rw [t_n] 的简写。前面的例子可以写成如下形式:

variable (k : Nat) (f : Nat Nat) example (h₁ : f 0 = 0) (h₂ : k = 0) : f k = 0 := k:Natf:Nat Nath₁:f 0 = 0h₂:k = 0f k = 0 All goals completed! 🐙

默认情况下,rw 使用正向的等式,将左手边与一个表达式匹配,并将其替换为右手边。 ←t 符号可以用来指示策略在反方向使用等式 t

variable (a b : Nat) (f : Nat Nat) example (h₁ : a = b) (h₂ : f a = 0) : f b = 0 := a:Natb:Natf:Nat Nath₁:a = bh₂:f a = 0f b = 0 All goals completed! 🐙

在这个例子中,项 h₁ 指示重写器将 b 替换为 a。 在编辑器中,你可以把向后的箭头输入为 \l。你也可以使用 ASCII 等效符号 <-

有时,恒等式的左手边可以匹配模式中的多个子项,在这种情况下,rw 策略会选择它在遍历该项时发现的第一个匹配。 如果那不是你想要的,你可以使用额外的参数来指定合适的子项。

example (a b c : Nat) : a + b + c = a + c + b := a:Natb:Natc:Nata + b + c = a + c + b All goals completed! 🐙 example (a b c : Nat) : a + b + c = a + c + b := a:Natb:Natc:Nata + b + c = a + c + b All goals completed! 🐙 example (a b c : Nat) : a + b + c = a + c + b := a:Natb:Natc:Nata + b + c = a + c + b All goals completed! 🐙

在上面的第一个例子中,第一步将 a + b + c 重写为 a + (b + c)。 下一步对 b + c 项应用交换律;如果不指定参数,该策略将把 a + (b + c) 重写为 (b + c) + a。 最后,最后一步在反方向应用结合律,将 a + (c + b) 重写为 a + c + b。 接下来的两个例子则是应用结合律,将两边的括号移到右边,然后交换 bc。 注意,最后一个例子通过指定 Nat.add_comm 的第二个参数,指定了重写应该发生在右手边。

注意,最后一个例子通过指定 Nat.add_comm 的第二个参数,指定了重写应该发生在右手边。

默认情况下,rw 策略只影响目标。rw[t]at h 符号将重写应用于假设 h

example (f : Nat Nat) (a : Nat) (h : a + 0 = 0) : f a = f 0 := f:Nat Nata:Nath:a + 0 = 0f a = f 0 f:Nat Nata:Nath:a = 0f a = f 0 All goals completed! 🐙

第一步,rw [Nat.add_zero] at h,将假设 a + 0 = 0 重写为 a = 0。 然后新的假设 a = 0 被用来将目标重写为 f 0=f 0

rw 策略并不局限于命题。在下面的例子中,我们使用 rw[h]at t 来将假设 t : Tuple α n 重写为 t : Tuple α 0

def Tuple (α : Type) (n : Nat) := { as : List α // as.length = n } example (n : Nat) (h : n = 0) (t : Tuple α n) : Tuple α 0 := α:Typen:Nath:n = 0t:Tuple α nTuple α 0 α:Typen:Nath:n = 0t:Tuple α 0Tuple α 0 All goals completed! 🐙

5.7. 使用简化器🔗

虽然 rw 被设计成操作目标的“手术刀”,但简化器(Simplifier)提供了一种更强大的自动化形式。 Lean 库中的许多恒等式都被标记了 [simp] 属性,而 simp 策略使用它们来迭代地重写表达式中的子项。

example (x y z : Nat) : (x + 0) * (0 + y * 1 + z * 0) = x * y := x:Naty:Natz:Nat(x + 0) * (0 + y * 1 + z * 0) = x * y All goals completed! 🐙 example (x y z : Nat) (p : Nat Prop) (h : p (x * y)) : p ((x + 0) * (0 + y * 1 + z * 0)) := x:Naty:Natz:Natp:Nat Proph:p (x * y)p ((x + 0) * (0 + y * 1 + z * 0)) x:Naty:Natz:Natp:Nat Proph:p (x * y)p (x * y); All goals completed! 🐙

在第一个例子中,目标中等式的左手边使用涉及 0 和 1 的常用恒等式进行简化,将目标归约为 x * y=x * y。 在那一点上,simp 应用自反性来完成它. 在第二个例子中,simp 将目标归约为 p (x * y),此时假设 h 完成了它。 下面是更多关于列表的例子:

open List example (xs : List Nat) : reverse (xs ++ [1, 2, 3]) = [3, 2, 1] ++ reverse xs := xs:List Nat(xs ++ [1, 2, 3]).reverse = [3, 2, 1] ++ xs.reverse All goals completed! 🐙 example (xs ys : List α) : length (reverse (xs ++ ys)) = length xs + length ys := α:Type u_1xs:List αys:List α(xs ++ ys).reverse.length = xs.length + ys.length All goals completed! 🐙

rw 一样,你可以使用关键字 at 来简化一个假设:

example (x y z : Nat) (p : Nat Prop) (h : p ((x + 0) * (0 + y * 1 + z * 0))) : p (x * y) := x:Naty:Natz:Natp:Nat Proph:p ((x + 0) * (0 + y * 1 + z * 0))p (x * y) x:Naty:Natz:Natp:Nat Proph:p (x * y)p (x * y); All goals completed! 🐙

此外,你可以使用“通配符”星号来简化所有的假设和目标:

attribute [local simp] Nat.mul_comm Nat.mul_assoc Nat.mul_left_comm attribute [local simp] Nat.add_assoc Nat.add_comm Nat.add_left_comm example (w x y z : Nat) (p : Nat Prop) (h : p (x * y + z * w * x)) : p (x * w * z + y * x) := w:Natx:Naty:Natz:Natp:Nat Proph:p (x * y + z * w * x)p (x * w * z + y * x) w:Natx:Naty:Natz:Natp:Nat Proph:p (x * y + w * (x * z))p (x * y + w * (x * z)); All goals completed! 🐙 example (x y z : Nat) (p : Nat Prop) (h₁ : p (1 * x + y)) (h₂ : p (x * z * 1)) : p (y + 0 + x) p (z * x) := x:Naty:Natz:Natp:Nat Proph₁:p (1 * x + y)h₂:p (x * z * 1)p (y + 0 + x) p (z * x) x:Naty:Natz:Natp:Nat Proph₁:p (x + y)h₂:p (x * z)p (x + y) p (x * z) x:Naty:Natz:Natp:Nat Proph₁:p (x + y)h₂:p (x * z)p (x + y) p (x * z) x:Naty:Natz:Natp:Nat Proph₁:p (x + y)h₂:p (x * z)p (x + y)x:Naty:Natz:Natp:Nat Proph₁:p (x + y)h₂:p (x * z)p (x * z) x:Naty:Natz:Natp:Nat Proph₁:p (x + y)h₂:p (x * z)p (x + y)x:Naty:Natz:Natp:Nat Proph₁:p (x + y)h₂:p (x * z)p (x * z) All goals completed! 🐙

对于可交换和可结合的操作,如自然数上的乘法,简化器使用这两个事实来重写表达式,以及 左交换律(Left commutativity)。 在乘法的情况下,后者表示如下:x * (y * z) = y * (x * z)local 修饰符告诉简化器在当前文件(或章节或命名空间,视情况而定)中使用这些规则。 看起来交换律和左交换律是有问题的,因为重复应用其中任何一个都会导致循环。 但简化器会检测置换其参数的恒等式,并使用一种被称为 有序重写(Ordered rewriting) 的技术。 这意味着系统维持着项的内部排序,并且只有在这样做会降低排序的情况下才应用恒等式。 有了上面提到的三个恒等式,其效果是表达式中的所有括号都向右结合,并且表达式以一种规范的(虽然有些随意)方式排序。 在结合律和交换律下等价的两个表达式会被重写为相同的规范形式。

example (w x y z : Nat) (unused variable `p` Note: This linter can be disabled with `set_option linter.unusedVariables false`p : Nat Prop) : x * y + z * w * x = x * w * z + y * x := w:Natx:Naty:Natz:Natp:Nat Propx * y + z * w * x = x * w * z + y * x All goals completed! 🐙 example (w x y z : Nat) (p : Nat Prop) (h : p (x * y + z * w * x)) : p (x * w * z + y * x) := w:Natx:Naty:Natz:Natp:Nat Proph:p (x * y + z * w * x)p (x * w * z + y * x) w:Natx:Naty:Natz:Natp:Nat Proph:p (x * y + z * w * x)p (x * y + w * (x * z)); w:Natx:Naty:Natz:Natp:Nat Proph:p (x * y + w * (x * z))p (x * y + w * (x * z)); All goals completed! 🐙

rw 一样,你可以向 simp 发送一个要使用的列表, 包括一般的引理、局部假设、要展开的定义和复合表达式。 simp 策略也能识别 rewrite 所能识别的 ←t 语法。 在任何情况下,额外的规则都会被添加到用于简化项的恒等式集合中。

def f (m n : Nat) : Nat := m + n + m example {m n : Nat} (h : n = 1) (h' : 0 = m) : (f m n) = n := m:Natn:Nath:n = 1h':0 = mf m n = n All goals completed! 🐙

一个常见的习惯用法是使用局部假设来简化目标:

variable (k : Nat) (f : Nat Nat) example (h₁ : f 0 = 0) (h₂ : k = 0) : f k = 0 := k:Natf:Nat Nath₁:f 0 = 0h₂:k = 0f k = 0 All goals completed! 🐙

为了在简化时使用局部上下文中存在的所有假设,我们可以使用通配符 *

variable (k : Nat) (f : Nat Nat) example (h₁ : f 0 = 0) (h₂ : k = 0) : f k = 0 := k:Natf:Nat Nath₁:f 0 = 0h₂:k = 0f k = 0 All goals completed! 🐙

这里有另一个例子:

example (u w x y z : Nat) (h₁ : x = y + z) (h₂ : w = u + x) : w = z + y + u := u:Natw:Natx:Naty:Natz:Nath₁:x = y + zh₂:w = u + xw = z + y + u All goals completed! 🐙

简化器也会做命题重写。例如,使用假设 p, 它将 p q 重写为 q, 将 p q 重写为 True,然后它平凡地证明了这一点。 迭代这样的重写会产生非平凡的命题推理。

example (p q : Prop) (hp : p) : p q q := p:Propq:Prophp:pp q q All goals completed! 🐙 example (p q : Prop) (hp : p) : p q := p:Propq:Prophp:pp q All goals completed! 🐙 example (p q r : Prop) (hp : p) (hq : q) : p (q r) := p:Propq:Propr:Prophp:phq:qp (q r) All goals completed! 🐙

下一个例子简化了所有的假设,然后用它们来证明目标。

example (u w x x' y y' z : Nat) (p : Nat Prop) (h₁ : x + 0 = x') (h₂ : y + 0 = y') : x + y + 0 = x' + y' := u:Natw:Natx:Natx':Naty:Naty':Natz:Natp:Nat Proph₁:x + 0 = x'h₂:y + 0 = y'x + y + 0 = x' + y' u:Natw:Natx:Natx':Naty:Naty':Natz:Natp:Nat Proph₁:x = x'h₂:y = y'x + y = x' + y' All goals completed! 🐙

使简化器特别有用的一点是,它的能力可以随着库的发展而增长。 例如,假设我们定义了一个列表操作,通过附加其反转来使其输入对称:

def mk_symm (xs : List α) := xs ++ xs.reverse

那么对于任何列表 xs(mk_symm xs).reverse 等于 mk_symm xs, 这可以通过展开定义轻松证明:

theorem reverse_mk_symm (xs : List α) : (mk_symm xs).reverse = mk_symm xs := α:Type u_1xs:List α(mk_symm xs).reverse = mk_symm xs All goals completed! 🐙

我们现在可以用这个定理来证明新的结果:

example (xs ys : List Nat) : (xs ++ mk_symm ys).reverse = mk_symm ys ++ xs.reverse := xs:List Natys:List Nat(xs ++ mk_symm ys).reverse = mk_symm ys ++ xs.reverse All goals completed! 🐙 example (xs ys : List Nat) (p : List Nat Prop) (h : p (xs ++ mk_symm ys).reverse) : p (mk_symm ys ++ xs.reverse) := xs:List Natys:List Natp:List Nat Proph:p (xs ++ mk_symm ys).reversep (mk_symm ys ++ xs.reverse) xs:List Natys:List Natp:List Nat Proph:p (mk_symm ys ++ xs.reverse)p (mk_symm ys ++ xs.reverse); All goals completed! 🐙

但是使用 reverse_mk_symm 通常是正确的做法,如果用户不必显式地调用它,那就更好了。 你可以在定义定理时将其标记为简化规则来实现:

@[simp] theorem reverse_mk_symm (xs : List α) : (mk_symm xs).reverse = mk_symm xs := α:Type u_1xs:List α(mk_symm xs).reverse = mk_symm xs All goals completed! 🐙 example (xs ys : List Nat) : (xs ++ mk_symm ys).reverse = mk_symm ys ++ xs.reverse := xs:List Natys:List Nat(xs ++ mk_symm ys).reverse = mk_symm ys ++ xs.reverse All goals completed! 🐙 example (xs ys : List Nat) (p : List Nat Prop) (h : p (xs ++ mk_symm ys).reverse) : p (mk_symm ys ++ xs.reverse) := xs:List Natys:List Natp:List Nat Proph:p (xs ++ mk_symm ys).reversep (mk_symm ys ++ xs.reverse) xs:List Natys:List Natp:List Nat Proph:p (mk_symm ys ++ xs.reverse)p (mk_symm ys ++ xs.reverse); All goals completed! 🐙

符号 @[simp] 声明 reverse_mk_symm 具有 [simp] 属性,可以更明确地拼写出来:

theorem reverse_mk_symm (xs : List α) : (mk_symm xs).reverse = mk_symm xs := α:Type u_1xs:List α(mk_symm xs).reverse = mk_symm xs All goals completed! 🐙 attribute [simp] reverse_mk_symm example (xs ys : List Nat) : (xs ++ mk_symm ys).reverse = mk_symm ys ++ xs.reverse := xs:List Natys:List Nat(xs ++ mk_symm ys).reverse = mk_symm ys ++ xs.reverse All goals completed! 🐙 example (xs ys : List Nat) (p : List Nat Prop) (h : p (xs ++ mk_symm ys).reverse) : p (mk_symm ys ++ xs.reverse) := xs:List Natys:List Natp:List Nat Proph:p (xs ++ mk_symm ys).reversep (mk_symm ys ++ xs.reverse) xs:List Natys:List Natp:List Nat Proph:p (mk_symm ys ++ xs.reverse)p (mk_symm ys ++ xs.reverse); All goals completed! 🐙

该属性也可以在定理声明后的任何时间应用:

theorem reverse_mk_symm (xs : List α) : (mk_symm xs).reverse = mk_symm xs := α:Type u_1xs:List α(mk_symm xs).reverse = mk_symm xs All goals completed! 🐙 example (xs ys : List Nat) : (xs ++ mk_symm ys).reverse = mk_symm ys ++ xs.reverse := xs:List Natys:List Nat(xs ++ mk_symm ys).reverse = mk_symm ys ++ xs.reverse All goals completed! 🐙 attribute [simp] reverse_mk_symm example (xs ys : List Nat) (p : List Nat Prop) (h : p (xs ++ mk_symm ys).reverse) : p (mk_symm ys ++ xs.reverse) := xs:List Natys:List Natp:List Nat Proph:p (xs ++ mk_symm ys).reversep (mk_symm ys ++ xs.reverse) xs:List Natys:List Natp:List Nat Proph:p (mk_symm ys ++ xs.reverse)p (mk_symm ys ++ xs.reverse); All goals completed! 🐙

然而,一旦应用了该属性,就没有办法永久地删除它;它在任何导入了分配该属性的文件中都会持续存在。 正如我们将在 属性 中进一步讨论的那样, 我们可以使用 local 修饰符将属性的范围限制在当前文件或章节中:

theorem reverse_mk_symm (xs : List α) : (mk_symm xs).reverse = mk_symm xs := α:Type u_1xs:List α(mk_symm xs).reverse = mk_symm xs All goals completed! 🐙 section attribute [local simp] reverse_mk_symm example (xs ys : List Nat) : (xs ++ mk_symm ys).reverse = mk_symm ys ++ xs.reverse := xs:List Natys:List Nat(xs ++ mk_symm ys).reverse = mk_symm ys ++ xs.reverse All goals completed! 🐙 example (xs ys : List Nat) (p : List Nat Prop) (h : p (xs ++ mk_symm ys).reverse) : p (mk_symm ys ++ xs.reverse) := xs:List Natys:List Natp:List Nat Proph:p (xs ++ mk_symm ys).reversep (mk_symm ys ++ xs.reverse) xs:List Natys:List Natp:List Nat Proph:p (mk_symm ys ++ xs.reverse)p (mk_symm ys ++ xs.reverse); All goals completed! 🐙 end

在 section 之外,化简器默认将不再使用 reverse_mk_symm

注意,我们讨论过的各种 simp 选项,即给出显式的规则列表,以及使用 at 指定位置,可以组合使用,但它们列出的顺序是固定的。 你可以在编辑器中通过将光标放在 simp 标识符上,查看与其关联的文档字符串,从而看到正确的顺序。

def mk_symm (xs : List α) := xs ++ xs.reverse @[simp] theorem reverse_mk_symm (xs : List α) : (mk_symm xs).reverse = mk_symm xs := α:Type u_1xs:List α(mk_symm xs).reverse = mk_symm xs All goals completed! 🐙 example (xs ys : List Nat) (p : List Nat Prop) (h : p (xs ++ mk_symm ys).reverse) : p (mk_symm ys ++ xs.reverse) := xs:List Natys:List Natp:List Nat Proph:p (xs ++ mk_symm ys).reversep (mk_symm ys ++ xs.reverse) xs:List Natys:List Natp:List Nat Proph:p (mk_symm ys ++ xs.reverse)p (mk_symm ys ++ xs.reverse); All goals completed! 🐙 example (xs ys : List Nat) (p : List Nat Prop) (h : p (xs ++ mk_symm ys).reverse) : p ((mk_symm ys).reverse ++ xs.reverse) := xs:List Natys:List Natp:List Nat Proph:p (xs ++ mk_symm ys).reversep ((mk_symm ys).reverse ++ xs.reverse) xs:List Natys:List Natp:List Nat Proph:p ((mk_symm ys).reverse ++ xs.reverse)p ((mk_symm ys).reverse ++ xs.reverse); All goals completed! 🐙 example (xs ys : List Nat) (p : List Nat Prop) (h : p (xs ++ mk_symm ys).reverse) : p ((mk_symm ys).reverse ++ xs.reverse) := xs:List Natys:List Natp:List Nat Proph:p (xs ++ mk_symm ys).reversep ((mk_symm ys).reverse ++ xs.reverse) xs:List Natys:List Natp:List Nat Proph:p ((mk_symm ys).reverse ++ xs.reverse)p ((mk_symm ys).reverse ++ xs.reverse); All goals completed! 🐙

simp 策略有许多配置选项。例如,我们可以按如下方式启用上下文相关的化简:

example : if x = 0 then y + x = y else x 0 := x:Naty:Natif x = 0 then y + x = y else x 0 All goals completed! 🐙

使用 +contextual 时,simp 策略在化简 y + x = y 时会使用 x = 0 这一事实, 而在化简另一个分支时会使用 x 0。这里有另一个例子:

example : (x : Nat) (unused variable `h` Note: This linter can be disabled with `set_option linter.unusedVariables false`h : x = 0), y + x = y := y:Nat (x : Nat), x = 0 y + x = y All goals completed! 🐙
example : 0 < 1 + x x + y + 2 y + 1 := x:Naty:Nat0 < 1 + x x + y + 2 y + 1 All goals completed! 🐙

5.8. 分解策略 (Split Tactic)🔗

def f (x y z : Nat) : Nat := match x, y, z with | 5, _, _ => y | _, 5, _ => y | _, _, 5 => y | _, _, _ => 1 example (x y z : Nat) : x 5 y 5 z 5 z = w f x y w = 1 := w:Natx:Naty:Natz:Natx 5 y 5 z 5 z = w f x y w = 1 w:Natx:Naty:Natz:Nata✝³:x 5a✝²:y 5a✝¹:z 5a✝:z = wf x y w = 1 w:Natx:Naty:Natz:Nata✝³:x 5a✝²:y 5a✝¹:z 5a✝:z = w(match x, y, w with | 5, x, x_1 => y | x, 5, x_1 => y | x, x_1, 5 => y | x, x_1, x_2 => 1) = 1 w:Naty:Natz:Nata✝³:y 5a✝²:z 5a✝¹:z = wx✝:Naty✝:Natz✝:Nata✝:5 5y = 1w:Natx:Natz:Nata✝³:x 5a✝²:z 5a✝¹:z = wx✝¹:Naty✝:Natz✝:Natx✝:x = 5 Falsea✝:5 55 = 1x:Naty:Natz:Nata✝³:x 5a✝²:y 5a✝¹:z 5x✝²:Naty✝:Natz✝:Natx✝¹:x = 5 Falsex✝:y = 5 Falsea✝:z = 5y = 1w:Natx:Naty:Natz:Nata✝³:x 5a✝²:y 5a✝¹:z 5a✝:z = wx✝³:Naty✝:Natz✝:Natx✝²:x = 5 Falsex✝¹:y = 5 Falsex✝:w = 5 False1 = 1 w:Naty:Natz:Nata✝³:y 5a✝²:z 5a✝¹:z = wx✝:Naty✝:Natz✝:Nata✝:5 5y = 1 All goals completed! 🐙 w:Natx:Natz:Nata✝³:x 5a✝²:z 5a✝¹:z = wx✝¹:Naty✝:Natz✝:Natx✝:x = 5 Falsea✝:5 55 = 1 All goals completed! 🐙 x:Naty:Natz:Nata✝³:x 5a✝²:y 5a✝¹:z 5x✝²:Naty✝:Natz✝:Natx✝¹:x = 5 Falsex✝:y = 5 Falsea✝:z = 5y = 1 All goals completed! 🐙 w:Natx:Naty:Natz:Nata✝³:x 5a✝²:y 5a✝¹:z 5a✝:z = wx✝³:Naty✝:Natz✝:Natx✝²:x = 5 Falsex✝¹:y = 5 Falsex✝:w = 5 False1 = 1 All goals completed! 🐙

我们可以将上面的策略证明压缩如下。

example (x y z : Nat) : x 5 y 5 z 5 z = w f x y w = 1 := w:Natx:Naty:Natz:Natx 5 y 5 z 5 z = w f x y w = 1 w:Natx:Naty:Natz:Nata✝³:x 5a✝²:y 5a✝¹:z 5a✝:z = wf x y w = 1; w:Natx:Naty:Natz:Nata✝³:x 5a✝²:y 5a✝¹:z 5a✝:z = w(match x, y, w with | 5, x, x_1 => y | x, 5, x_1 => y | x, x_1, 5 => y | x, x_1, x_2 => 1) = 1; w:Naty:Natz:Nata✝³:y 5a✝²:z 5a✝¹:z = wx✝:Naty✝:Natz✝:Nata✝:5 5y = 1w:Natx:Natz:Nata✝³:x 5a✝²:z 5a✝¹:z = wx✝¹:Naty✝:Natz✝:Natx✝:x = 5 Falsea✝:5 55 = 1x:Naty:Natz:Nata✝³:x 5a✝²:y 5a✝¹:z 5x✝²:Naty✝:Natz✝:Natx✝¹:x = 5 Falsex✝:y = 5 Falsea✝:z = 5y = 1w:Natx:Naty:Natz:Nata✝³:x 5a✝²:y 5a✝¹:z 5a✝:z = wx✝³:Naty✝:Natz✝:Natx✝²:x = 5 Falsex✝¹:y = 5 Falsex✝:w = 5 False1 = 1 w:Naty:Natz:Nata✝³:y 5a✝²:z 5a✝¹:z = wx✝:Naty✝:Natz✝:Nata✝:5 5y = 1w:Natx:Natz:Nata✝³:x 5a✝²:z 5a✝¹:z = wx✝¹:Naty✝:Natz✝:Natx✝:x = 5 Falsea✝:5 55 = 1x:Naty:Natz:Nata✝³:x 5a✝²:y 5a✝¹:z 5x✝²:Naty✝:Natz✝:Natx✝¹:x = 5 Falsex✝:y = 5 Falsea✝:z = 5y = 1w:Natx:Naty:Natz:Nata✝³:x 5a✝²:y 5a✝¹:z 5a✝:z = wx✝³:Naty✝:Natz✝:Natx✝²:x = 5 Falsex✝¹:y = 5 Falsex✝:w = 5 False1 = 1 first | w:Natx:Naty:Natz:Nata✝³:x 5a✝²:y 5a✝¹:z 5a✝:z = wx✝³:Naty✝:Natz✝:Natx✝²:x = 5 Falsex✝¹:y = 5 Falsex✝:w = 5 False1 = 1 | All goals completed! 🐙

策略 split <;> first | contradiction | rfl 首先应用 split 策略, 然后对于生成的每个目标,它尝试执行 contradiction,如果 contradiction 失败,则尝试执行 rfl。 与 simp 类似,我们可以将 split 应用于特定的假设:

def g (xs ys : List Nat) : Nat := match xs, ys with | [a, b], _ => a+b+1 | _, [b, _] => b+1 | _, _ => 1 example (xs ys : List Nat) (h : g xs ys = 0) : False := xs:List Natys:List Nath:g xs ys = 0False xs:List Natys:List Nath:(match xs, ys with | [a, b], x => a + b + 1 | x, [b, head] => b + 1 | x, x_1 => 1) = 0False; ys:List Natxs✝:List Natys✝:List Nata✝:Natb✝:Nath:a✝ + b✝ + 1 = 0Falsexs:List Natxs✝:List Natys✝:List Natb✝:Nathead✝:Natx✝: (a b : Nat), xs = [a, b] Falseh:b✝ + 1 = 0Falsexs:List Natys:List Natxs✝:List Natys✝:List Natx✝¹: (a b : Nat), xs = [a, b] Falsex✝: (b head : Nat), ys = [b, head] Falseh:1 = 0False ys:List Natxs✝:List Natys✝:List Nata✝:Natb✝:Nath:a✝ + b✝ + 1 = 0Falsexs:List Natxs✝:List Natys✝:List Natb✝:Nathead✝:Natx✝: (a b : Nat), xs = [a, b] Falseh:b✝ + 1 = 0Falsexs:List Natys:List Natxs✝:List Natys✝:List Natx✝¹: (a b : Nat), xs = [a, b] Falsex✝: (b head : Nat), ys = [b, head] Falseh:1 = 0False All goals completed! 🐙

5.9. 可扩展策略 (Extensible Tactics)🔗

-- Define a new tactic notation syntax "triv" : tactic macro_rules | `(tactic| triv) => `(tactic| assumption) example (h : p) : p := p:Sort ?u.2129h:pp All goals completed! 🐙 -- You cannot prove the following theorem using `triv` -- example (x : α) : x = x := by -- triv -- Let's extend `triv`. The tactic interpreter -- tries all possible macro extensions for `triv` until one succeeds macro_rules | `(tactic| triv) => `(tactic| rfl) example (x : α) : x = x := α:Sort u_1x:αx = x All goals completed! 🐙 example (x : α) (h : p) : x = x p := α:Sort u_1p:Propx:αh:px = x p α:Sort u_1p:Propx:αh:px = xα:Sort u_1p:Propx:αh:pp α:Sort u_1p:Propx:αh:px = xα:Sort u_1p:Propx:αh:pp All goals completed! 🐙 -- We now add a (recursive) extension macro_rules | `(tactic| triv) => `(tactic| apply And.intro <;> triv) example (x : α) (h : p) : x = x p := α:Sort u_1p:Propx:αh:px = x p All goals completed! 🐙

5.10. 练习🔗

  1. 回到 命题和证明全称量词和等式 中的练习, 现在尽可能多地使用策略证明来重做它们,并根据需要使用 rwsimp

  1. 使用策略组合器获得以下内容的一行证明:

declaration uses 'sorry'example (p q r : Prop) (hp : p) : (p q r) (q p r) (q r p) := p:Propq:Propr:Prophp:p(p q r) (q p r) (q r p) All goals completed! 🐙