翻译-TPIL-04-量词与等号关系

上一章介绍了如何对含有命题逻辑连词的断言构造证明。 这一章我们将扩充构造命题逻辑公式的命令,使其囊括 全称量词、存在量词以及等号关系。

4.1. 全称量词 The Universal Quantifier

对于任意类型 αα 上的一元谓词 p 可被视作类型为 α → Prop 的对象, 如此对于 x : αp x 就表示断言“px 上成立”;类似地, 对象 r : α → α → Prop 代表一个 α 上的二元关系, 给定 x y : αr x y 就表示断言“xy 相关联”。

全称量词 ∀ x : α, p x 表示的断言是 “对每一个 x : α 都会有 p x 成立”。 类似命题连词,“forall”在自然演绎系统中 也受引入和消去规则支配。非正式地, 引入规则宣称:

若已有 p x 的证明, 且语境中 x : α 都是任意的, 则可得关于 ∀ x : α, p x 的证明。

消去规则宣称:

对任何项 t : α, 若已有关于 ∀ x : α, p x 的证明, 则可得关于 p t 的证明。

正如处理蕴含时一样,“命题即类型”释义再次发挥作用。 回想一下依值箭头类型的引入和消去规则:

若已有类型为 β x 的项 t, 且语境中 x : α 都是任意的, 则可得 (fun x : α => t) : (x : α) → β x

消去规则宣称:

对任何项 t : α, 若已有 s : (x : α) → β x 则可得 s t : β t

对于 p x 具有 Prop 类型的情形下, 如果将 (x : α) → β x 替换为 ∀ x : α, p x, 那么我们就可以将它们解读为 用于构造涉及全称量词的证明的正确规则。

因此构造演算便以这种方式将依值箭头类型与全称表达式对应起来。 如果 p 为任意表达式,那么 ∀ x : α, p 不过是 (x : α) → p 的一种替代记法罢了, 并且在 p 为命题的情况下前者比后者更自然。 通常表达式 p 会依赖于 x : α回想一下,对于普通函数空间, 我们可以将函数类型 α → β 解释为 (x : α) → β 的一种特殊情形——即 β 不依赖于 x 的情况。类似地, 我们可以将命题蕴含 p → q 看作是 ∀ x : p, q 的一种特殊情形——即 q 不依赖于 x 的情况。

下面的例子演示了“命题即类型”是如何在实际应用中发挥作用的。

1
2
3
4
example (α : Type) (p q : α  Prop) : ( x : α, p x  q x)   y : α, p y :=
  fun h :  x : α, p x  q x =>   -- → 引入
  fun y : α =>                    -- ∀ 引入
  show p y from (h y).left        -- ∧ 消去 ∀ 消去

作为一种书写约定,我们赋予全称量词尽可能大的作用域; 因此对于上方例子中的假设,我们需要用括号限定 x 的量词的作用范围。 证明 ∀ y : α, p y 的典范做法是取任意 y 并证明 p y,这是引入规则;而 已知 h 的类型为 ∀ x : α, p x ∧ q x 并得知 h y 类型为 p y ∧ q y,这是消去规则; 取合取的左侧部分便可获得所需结论 p y

请记住,如果表达式之间的差异仅在于 其中一个是对另一个里所有约束变量的重命名, 那么它们就可以被视作是等价的。因此 我们就可以在假设和结论中使用相同的变量 x, 并在证明中用其他变量(比如 z)来实例化它,见下:

1
2
3
4
example (α : Type) (p q : α  Prop) : ( x : α, p x  q x)   x : α, p x :=
  fun h :  x : α, p x  q x =>   -- → 引入
  fun z : α =>                    -- ∀ 引入
  show p z from And.left (h z)    -- ∧ 消去 ∀ 消去

再举一个例子:下面展示了该 如何描述一个关系 r 具有传递性 Transitivity

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
variable (α : Type) (r : α  α  Prop)
variable (trans_r :  x y z, r x y  r y z  r x z)

variable (a b c : α)
variable (hab : r a b) (hbc : r b c)

#check trans_r
-- trans_r : ∀ (x y z : α), r x y → r y z → r x z
#check trans_r a b c
-- trans_r a b c : r a b → r b c → r a c
#check trans_r a b c hab
-- trans_r a b c hab : r b c → r a c
#check trans_r a b c hab hbc
-- trans_r a b c hab hbc : r a c

思考一下到底发生了什么。在用值 abc 实例化 trans_r 后 我们会得到 r a b → r b c → r a c 的证明;将此应用于“假设” hab : r a b 后 我们便得到 r b c → r a c 的一个证明;最后将其应用到假设 hbc 上 便会有结论 r a c 的证明。

在上例的情形中,显式地写出参数 abc 会很繁琐 ——如果它们可以从 habhbc 中推断出来的话。 因此它们通常会被设置为隐式参数:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
variable (α : Type) (r : α  α  Prop)
variable (trans_r :  {x y z}, r x y  r y z  r x z)

variable (a b c : α)
variable (hab : r a b) (hbc : r b c)

#check trans_r
-- trans_r : r ?m.4 ?m.5 → r ?m.5 ?m.6 → r ?m.4 ?m.6
#check trans_r hab
-- trans_r hab : r b ?m.6 → r a ?m.6
#check trans_r hab hbc
-- trans_r hab hbc : r a c

这样做的优点是我们可以简单地写下 trans_r hab hbc 作为 r a c 的证明。 但有一个缺点:Lean 没有足够的信息来推断表达式 trans_rtrans_r hab 中参数的类型: 第一个 #check 命令的输出是 r ?m.1 ?m.2 → r ?m.2 ?m.3 → r ?m.1 ?m.3, 说明在本例中的隐式参数未被指定。

下面的例子展示了如何使用等价关系进行基本推理:

1
2
3
4
5
6
7
8
variable (α : Type) (r : α  α  Prop)

variable (refl_r :  x, r x x)
variable (symm_r :  {x y}, r x y  r y x)
variable (trans_r :  {x y z}, r x y  r y z  r x z)

example (a b c d : α) (hab : r a b) (hcb : r c b) (hcd : r c d) : r a d :=
  trans_r (trans_r hab (symm_r hcb)) hcd

要想熟悉使用全称量词, 你应该试试本节末尾处的练习。

正是依值箭头类型(尤其是全称量词)的类型规则 体现了 Prop 类型与其他对象的类型的不同之处: 假设我们有 α : Sort iβ : Sort j —— 其中表达式 β 可能依赖于变量 x : α, 那么 (x : α) → β 便是 Sort (imax i j) 的一个元素。 这里 imax i jijj 不为 0 时的最大值,否则为 0。

其思路如下: 如果 j 不是 0, 那么 (x : α) → β 便是类型 Sort (max i j) 的一个元素,也就是说 类型为 αβ 的依值函数“活在”指数为 ij 两者最大值的宇宙中;然而, 假如 β 属于 Sort 0——即 Prop 的一个元素, 那么 (x : α) → β 也是 Sort 0 的一个元素—— 无论 α 生活在哪种类型的宇宙中;也就是说, 如果 β 是一个依赖于 α 的命题, 那么 ∀ x : α, β 仍然是一个命题。 这反映出 Prop 被解释为一种命题的类型而非数据类型,同时 这也使得 Prop 具有非直谓性 Impredicative”。

“直谓性”一词源于二十世纪初期的数学基础研究。 当时如庞加莱和罗素之类的逻辑学家 将集合论的悖论归咎于“恶性循环”——比如 某个属性是通过量化一个类来定义的, 而这个类又包含了待定义的属性。 会发现:如果 α 是任意类型, 那么我们就可以在 α 上构造类型 α → Prop 来囊括所有与 α 相关的谓词(Prop 的“幂”)。 Prop 的非直谓性意味着 我们可以通过量化 α → Prop 来形成命题。特别地, 我们可以通过量化所有关于 α 的谓词来定义 α 上的谓词, 而这正是曾被认为是有问题的循环类型。


下面是一些练习题:

1
2
3
4
5
variable (α : Type) (p q : α  Prop)

example : ( x, p x  q x)  ( x, p x)  ( x, q x) := sorry
example : ( x, p x  q x)  ( x, p x)  ( x, q x) := sorry
example : ( x, p x)  ( x, q x)   x, p x  q x := sorry
  1. (∀ x, p x ∧ q x) ↔ (∀ x, p x) ∧ (∀ x, q x)

    答:
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    
    variable 
    (α : Type) 
    (p q : α  Prop)
    
    example : ( x, p x  q x)  ( x, p x)  ( x, q x)
      :=Iff.intro
        (λ (h :  x, p x  q x) 
        ⟨λ (x : α)  (h x).left
        ,λ (x : α)  (h x).right)
        (λ (h : ( x, p x)  ( x, q x)) 
        λ (x : α)  h.left x,(h.right x))
  2. (∀ x, p x → q x) → (∀ x, p x) → (∀ x, q x)

    答:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    
    variable
    (α : Type)
    (p q : α  Prop)
    
    example : ( x, p x  q x)  ( x, p x)  ( x, q x)
      :=(λ (hpq :  x, p x  q x) 
         λ (hp  :  x, p x) 
         λ (x : α) 
         (hpq x) (hp x))
  3. (∀ x, p x) ∨ (∀ x, q x) → ∀ x, p x ∨ q x

    答:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    
    variable
    (α : Type)
    (p q : α  Prop)
    
    example : ( x, p x)  ( x, q x)   x, p x  q x
      :=(λ (hpq : ( x, p x)  ( x, q x)) 
         λ (x : α)  match hpq with
         | Or.inl hp => Or.inl (hp x)
         | Or.inr hq => Or.inr (hq x))

当公式的一个组件不依赖于量化变量时,通常可以将其移到全称量词之外。 尝试证明这些(其中第二个的一个方向需要经典逻辑)

1
2
3
4
5
6
7
8
variable 
(α : Type) 
(p q : α  Prop)
(r : Prop)

example : α  (( x : α, r)  r) := sorry
example : ( x, p x  r)  ( x, p x)  r := sorry
example : ( x, r  p x)  (r   x, p x) := sorry
  1. α → ((∀ x : α, r) ↔ r)

    答:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    
    variable
    (α : Type)
    (p q : α  Prop)
    (r : Prop)
    
    example : α  ( _ : α, r)  r
      : (x : α) 
        λ (h :  _ : α, r) 
        h x
  2. (∀ x, p x ∨ r) ↔ (∀ x, p x) ∨ r

    答:
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    
    variable
    (α : Type)
    (p q : α  Prop)
    (r : Prop)
    
    open Classical
    
    example : ( x, p x  r)  ( x, p x)  r
      :=Iff.intro
        (λ (h :  x, p x  r)  byCases
          (λ (hr  :  r)  Or.inr hr)
          (λ (hnr : ¬r)  Or.inl
            (λ (x : α)  match (h x) with
            | Or.inl hl => hl
            | Or.inr hr => absurd hr hnr)))
        (λ (h :( x, p x)  r)  match h with
        | Or.inl hl => (λ (x : α)  Or.inl (hl x))
        | Or.inr hr => (λ (_ : α)  Or.inr hr))
  3. (∀ x, r → p x) ↔ (r → ∀ x, p x)

    答:
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    
    variable
    (α : Type)
    (p q : α  Prop)
    (r : Prop)
    
    example : ( x, r  p x)  (r   x, p x)
      :=Iff.intro
        (λ (hrp :  x, r  p x) 
         λ (hr  : r)  
         λ (x : α) 
         hrp x hr)
        (λ (hrp : r   x, p x)  
         λ (x : α)  
         λ (hr  :  r) 
         hrp hr x)

考虑理发师悖论 Barber Paradox”: 小镇上有个男理发师,他只为所有不给自己刮胡子的男人刮胡子。请证明这是不可能的。

1
2
3
4
5
6
7
variable 
(men : Type) 
(barber : men)
(shaves : men  men  Prop)

example (h :  x : men, shaves barber x  ¬ shaves x x) : False :=
  sorry
答:借用 03-06-练习-17 的答案。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
variable
(α : Type)
(a : α)
(p : α  α  Prop)

theorem cache {p : Prop} : ¬(p  ¬p)
  : (hpnp : p  ¬p) 
    let hnp : p  False := λ (hp : p)  hpnp.mp hp hp
    let hp  : p         := hpnp.mpr hnp
    hpnp.mp hp hp
example : ( x : α, p a x  ¬p x x)  False
  : (h :  x : α, p a x  ¬p x x) 
    cache (h a)

4.2. 等号 Equality

现在来看看 Lean 库中定义的最基本的关系之一——等号关系。 在归纳类型一章中我们将解释等号在 Lean 的逻辑框架中是如何被定义的, 不过在此之前先介绍一下如何使用它。

显然,等号关系的基本性质之一 便是其构成一个等价关系:

1
2
3
4
5
6
#check Eq.refl
-- Eq.refl.{u_1} {α : Sort u_1} (a : α) : a = a
#check Eq.symm
-- Eq.symm.{u} {α : Sort u} {a b : α} (h : a = b) : b = a
#check Eq.trans
-- Eq.trans.{u} {α : Sort u} {a b c : α} (h₁ : a = b) (h₂ : b = c) : a = c

我们可以让输出变得更易读—— 只需告诉 Lean 不要插入隐式参数(这里显示为元变量)即可。

1
2
3
4
5
6
7
8
universe u

#check @Eq.refl.{u}
-- @Eq.refl : ∀ {α : Sort u} (a : α), a = a
#check @Eq.symm.{u}
-- @Eq.symm : ∀ {α : Sort u} {a b : α}, a = b → b = a
#check @Eq.trans.{u}
-- @Eq.trans : ∀ {α : Sort u} {a b c : α}, a = b → b = c → a = c

标记 .{u} 告诉 Lean 在宇宙 u 上实例化常量。

如此我们便可将上一节中的例子 套到等号关系上:

1
2
3
4
5
variable (α : Type) (a b c d : α)
variable (hab : a = b) (hcb : c = b) (hcd : c = d)

example : a = d :=
  Eq.trans (Eq.trans hab (Eq.symm hcb)) hcd

我们也可以使用字段表示法

1
2
3
4
5
variable (α : Type) (a b c d : α)
variable (hab : a = b) (hcb : c = b) (hcd : c = d)

example : a = d := 
  (hab.trans hcb.symm).trans hcd

自反性 Reflexivity 比表面上更强大。回想一下, 在构造演算中每个项都有一个计算释义, 而所有能被规约为相同形式的项 在逻辑框架内都会被视作是相同的。 正因如此,一些看似非平凡的恒等式 也可以通过自反性来证明:

1
2
3
4
5
variable (α β : Type)

example (f : α  β) (a : α) : (fun x => f x) a = f a := Eq.refl _
example (a : α) (b : β) : (a, b).1 = a := Eq.refl _
example : 2 + 3 = 5 := Eq.refl _

这个特性非常重要,以至于库中 为 Eq.refl _ 专门定义了一个符号 rfl

1
2
3
4
5
variable (α β : Type)

example (f : α  β) (a : α) : (fun x => f x) a = f a := rfl
example (a : α) (b : β) : (a, b).1 = a := rfl
example : 2 + 3 = 5 := rfl

然而等号不仅仅是一种等价关系,它还有一个重要的性质:每个断言都遵从等价性, 意味着可以在不改变真值的情况下对表达式做等价代换;也就是说, 给定 h1 : a = bh2 : p a 我们就可以构造一个 p b 的证明—— 使用代换 Eq.subst h1 h2 即可。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
example (α : Type) (a b : α) (p : α  Prop)
        (h1 : a = b) (h2 : p a) : p b :=
  Eq.subst h1 h2-- 根据等式 h1 对 h2 的内容进行替换
--h1.subst h2   -- 这样写也行

example (α : Type) (a b : α) (p : α  Prop)
  (h1 : a = b) (h2 : p a) : p b :=
  h1  h2

#print Eq.subst
-- theorem Eq.subst.{u} 
-- : ∀ {α : Sort u} {motive : α → Prop} {a b : α}, 
--   a = b → motive a → motive b
-- :=fun {α} {motive} {a b} h₁ h₂ ↦ h₁ ▸ h₂
-- 这里 h₁ : a = b , h₂ : motive a

第二个例子中的 三角形 是一个 建立在 Eq.substEq.symm 之上的宏, 你可以通过输入 \t 来打出它。

规则 Eq.subst 定义了一系列辅助规则以进行更显式的替换, 它们用于处理函数应用表达式(即形如 s t 的项);具体来说: congrArg 用于替换实参, congrFun 用于替换函数, congr 可同时替换两者。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
variable (α : Type)
variable (a b : α)
variable (f g : α  Nat)
variable (h₁ : a = b)
variable (h₂ : f = g)

example : f a = f b
  :=congrArg f h₁
--:=by rw [h₁]              -- 这样写也行 4.3 节会解释
--:=h₁ ▸ Eq.refl (f a)      -- 这样写也行 注意:这里用 subst 会报错,在高阶合一那里有解释

example : f a = g a
  :=congrFun h₂ a
--:=by rw [h₂]              -- 这样写也行
--:=h₂ ▸ Eq.refl (f a)      -- 这样写也行

example : f a = g b
  :=congr h₂ h₁
--:=by rw [h₂, h₁]          -- 这样写也行
--:=h₂ ▸ h₁ ▸ Eq.refl (f a) -- 这样写也行

#print congrArg
-- theorem congrArg.{u, v}
-- : ∀ {α : Sort u} {β : Sort v} {a₁ a₂ : α}
--   (f : α → β), a₁ = a₂ → f a₁ = f a₂
-- :=fun {α} {β} {a₁ a₂} f h ↦ h ▸ rfl
-- 这里 h : a₁ = a₂ , rfl : f a₁ = f a₁

#print congrFun
-- theorem congrFun.{u, v}
-- : ∀ {α : Sort u} {β : α → Sort v} {f g : (x : α) → β x},
--   f = g → ∀ (a : α), f a = g a
-- :=fun {α} {β} {f g} h a ↦ h ▸ rfl
-- 这里 h : f = g , rfl : f a = f a

#print congr
-- theorem congr.{u, v}
-- : ∀ {α : Sort u} {β : Sort v} {f₁ f₂ : α → β} {a₁ a₂ : α},
--   f₁ = f₂ → a₁ = a₂ → f₁ a₁ = f₂ a₂
-- :=fun {α} {β} {f₁ f₂} {a₁ a₂} h₁ h₂ ↦ h₁ ▸ h₂ ▸ rfl
-- 这里 h₁ : f₁ = f₂ , h₂ : a₁ = a₂ , rfl : f₁ a₁ = f₁ a₁

Lean 的库包含大量常用等式,比如:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
variable (a b c : Nat)

example : a + 0 = a := Nat.add_zero a
example : 0 + a = a := Nat.zero_add a
example : a * 1 = a := Nat.mul_one a
example : 1 * a = a := Nat.one_mul a
example : a + b = b + a := Nat.add_comm a b
example : a + b + c = a + (b + c) := Nat.add_assoc a b c
example : a * b = b * a := Nat.mul_comm a b
example : a * b * c = a * (b * c) := Nat.mul_assoc a b c
example : a * (b + c) = a * b + a * c := Nat.mul_add a b c
example : a * (b + c) = a * b + a * c := Nat.left_distrib a b c
example : (a + b) * c = a * c + b * c := Nat.add_mul a b c
example : (a + b) * c = a * c + b * c := Nat.right_distrib a b c

Nat.mul_addNat.add_mul 分别是 Nat.left_distribNat.right_distrib 的别名。 上面的属性都是针对自然数类型 Nat 的。

下面关于计算自然数的例子 使用了代换外加自然数的结合律、交换律及分配律。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
example (x y : Nat) : (x + y) * (x + y) = x * x + y * x + x * y + y * y
  :=have h1 : (x + y) * (x + y) = (x + y) * x + (x + y) * y
    :=-- (x + y)x + (x + y)y
      Nat.mul_add (x + y) x y -- ↑ , (x + y)(x + y) 被展开为 (x + y)x + (x + y)y
      -- (x + y)²
    have h2 : (x + y) * (x + y) = x * x + y * x + (x * y + y * y)
    :=-- (x² + yx) + (xy + y²)
      (Nat.add_mul x y x)    -- ↑ , (x + y)x       被展开为 x² + yx
      -- (x + y)x  + (xy + y²)
      (Nat.add_mul x y y)    -- ↑ , (x + y)y       被展开为 xy + y²
      -- (x + y)x  + (x + y)y
      h1                      -- ↑ , (x + y)(x + y) 被展开为 (x + y)x + (x + y)y
      -- (x + y)²

    -- (x + y)²
    h2.trans               -- ↓ , (x + y)(x + y)      被展开为 x² + yx + (xy + y²)
      (-- (x² + yx) + (xy + y²)
        (-- (x² + yx) + xy + y²
          Nat.add_assoc    -- ↓ , x² + yx + xy + y²   被展开为 x² + yx + (xy + y²)
          (x * x + y * x)
          (x * y)
          (y * y)
        -- (x² + yx) + (xy + y²)
        ).symm             -- ↓ , x² + yx + (xy + y²) 被展开为 x² + yx + xy + y²
      -- (x² + yx) + xy + y²
      )
    -- (x² + yx) + xy + y²

注意到 Eq.subst 的第二个隐式参数 提供了将要发生代换的语境,其类型为 α → Prop; 这意味着推断这个谓词需要一个 高阶合一 High-Order Unification 的实例。 一般来说高阶合一器的存在性问题是不可判定的, Lean 顶多只能提供不完美的和近似的解决方案, 因此 Eq.subst 并不总是能实现你想要的效果; 宏 h ▸ e 使用了更有效的启发式方法来计算这个隐式参数, 并且通常会在 Eq.subst 失效的情况下成功

由于等式推理是如此的普遍和重要, Lean 提供了许多机制以更有效地执行它。 下一节介绍的语法允许你以更自然和清晰的方式书写计算式证明。 但更重要的是,等式推理是由 项重写器 term rewriter、 简化器 simplifier 和 其他种类的自动化方法支持的。 项重写器和简化器将在下一节中简要描述, 而更多细节将在下一章中介绍。

4.3. 计算式证明 Calculational Proofs

计算式证明是一串中间结果, 这些结果通过如等式传递性这样的基本原理进行串联。 在 Lean 中计算式证明以 关键字 calc 开始,语法如下:

1
2
3
4
5
calc
  <expr>_0  'op_1'  <expr>_1  ':='  <proof>_1
  '_'       'op_2'  <expr>_2  ':='  <proof>_2
  
  '_'       'op_n'  <expr>_n  ':='  <proof>_n

注意到 calc 下的每一对等号关系都使用了相同的缩进。 每个 <proof>_i 都是对 <expr>_{i-1} op_i <expr>_i 的证明。

我们也可以在第一对等号关系中使用 _(就在 <expr>_0 正后方), 如此便可将关系对/证明对对齐:

1
2
3
4
5
calc <expr>_0
  '_' 'op_1' <expr>_1 ':=' <proof>_1
  '_' 'op_2' <expr>_2 ':=' <proof>_2
  
  '_' 'op_n' <expr>_n ':=' <proof>_n

下面是一个例子:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
variable (a b c d e : Nat)

theorem T
  (h1 : a = b)
  (h2 : b = c + 1)
  (h3 : c = d)
  (h4 : e = 1 + d) :
  a = e
  :=calc
    a = b      := h1
    -- ↓ , h2           : b = c + 1
    _ = c + 1  := h2
    -- ↓ , h3           : c = d
    _ = d + 1  := congrArg Nat.succ h3
    -- ↓ , Nat.add_comm : d + 1 = 1 + d
    _ = 1 + d  := Nat.add_comm d 1
    -- ↓ , h4.symm      : e = 1 + d
    _ = e      := Eq.symm h4

这种书写证明的风格在与 simprewrite 策略结合使用时最为有效。 这些策略将在下一章中详细讨论。例如:若用缩写 rw 表示重写, 那么刚才的证明便可写成如下形式:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
variable (a b c d e : Nat)

theorem T
  (h1 : a = b)
  (h2 : b = c + 1)
  (h3 : c = d)
  (h4 : e = 1 + d) :
  a = e
  :=calc
    a = b      := by rw [h1]
    -- ↓ , h2           : b = c + 1
    _ = c + 1  := by rw [h2]
    -- ↓ , h3           : c = d
    _ = d + 1  := by rw [h3]
    -- ↓ , Nat.add_comm : d + 1 = 1 + d
    _ = 1 + d  := by rw [Nat.add_comm]
    -- ↑ , h4           : e = 1 + d
    _ = e      := by rw [h4]

rw 策略本质上会利用一个给定的等式 (可以是一个假设、一个定理名或者是一个复杂的项)来“重写”目标; 如果这样做能将目标简化为一种等式 t = t, 那么该策略就会应用自反性来证明它。

重写可以应用到一连串等式上, 因此刚才的证明可以简化为如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
variable (a b c d e : Nat)

theorem T
  (h1 : a = b)
  (h2 : b = c + 1)
  (h3 : c = d)
  (h4 : e = 1 + d) :
  a = e :=
  calc
    a = d + 1  := by rw [
      -- a
      h1,          -- ↓ , a     被换成 b
      -- b
      h2,          -- ↓ , b     被换成 c + 1
      -- c + 1
      h3           -- ↓ , c     被换成 d
      -- d + 1
    ]
    _ = 1 + d  := by rw [
      -- d + 1
      Nat.add_comm -- ↓ , d + 1 被换成 1 + d
      -- 1 + d
    ]
    _ = e      := by rw [
      -- 1 + d
      h4           -- ↑ , 1 + d 被换成 e
      -- e
    ]

甚至还可以更简洁:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
variable (a b c d e : Nat)

theorem T
  (h1 : a = b)
  (h2 : b = c + 1)
  (h3 : c = d)
  (h4 : e = 1 + d) :
  a = e :=
  by rw [
    -- a
    h1,           -- ↓ , a     被换成 b
    -- b
    h2,           -- ↓ , b     被换成 c + 1
    -- c + 1
    h3,           -- ↓ , c     被换成 d
    -- d + 1
    Nat.add_comm, -- ↓ , d + 1 被换成 1 + d
    -- 1 + d
    h4            -- ↑ , 1 + d 被换成 e
    -- e
  ]

相反,simp 策略可以 以任意顺序 在项中任何适用的地方 重复应用给定的等式 来重写目标。它 还可以使用之前声明给系统的其他规则,且 还可以明智地应用交换性以避免陷入死循环。 如此我们便可如下证明定理:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
variable (a b c d e : Nat)

theorem T
  (h1 : a = b)
  (h2 : b = c + 1)
  (h3 : c = d)
  (h4 : e = 1 + d) :
  a = e :=
  by simp [h1, h2, h3, Nat.add_comm, h4]
--by simp [h3, h1, Nat.add_comm, h4, h2] -- 这样写也行

我们将在下一章讨论 rwsimp 的变体。

calc 命令可以针对任何支持某种形式传递性的关系进行配置。 它还可以组合不同的关系。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
variable (a b c d : Nat)

example
  (h1 : a = b)
  (h2 : b  c)
  (h3 : c + 1 < d) :
  a < d
  :=calc
    a = b     := h1
    -- ↓ , Nat.lt_succ_self b  : b     < b + 1
    _ < b + 1 := Nat.lt_succ_self b
    -- ↓ , Nat.succ_le_succ h2 : b + 1 ≤ c + 1
    _  c + 1 := Nat.succ_le_succ h2
    -- ↓ , h3                  : c + 1 < d
    _ < d     := h3

你甚至还可以“教会” calc 新的传递性定理—— 只需加入 Trans 类型类下的新实例即可。 类型类会在后面介绍。 下面的小例子将演示 如何使用新的 Trans 实例 扩展 calc 表示法。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
variable (x y z k : Nat)

def divides : Prop :=
   k, k * x = y

def divides_trans (h₁ : divides x y) (h₂ : divides y z) : divides x z :=
  let k₁, d₁ := h₁; let k₂, d₂ := h₂
  k₁ * k₂, by
    rw [
      -- (k₁ * k₂) * x
      Nat.mul_comm k₁ k₂, -- ↓ , (k₁ * k₂) * x 被换成 (k₂ * k₁) * x
      -- (k₂ * k₁) * x
      Nat.mul_assoc,      -- ↓ , (k₂ * k₁) * x 被换成 k₂ * (k₁ * x)
      -- k₂ * (k₁ * x) = z
      d₁,                 -- ↓ , k₁ * x        被换成 y
      -- k₂ * y
      d₂                  -- ↓ , k₂ * y        被换成 z
      -- z
  ]

def divides_mul : divides x (k*x) :=
  k, rfl

instance : Trans divides divides divides where
  trans := @divides_trans
--trans :=  divides_trans _ _ _ -- 这样写也行

#print Trans

example (h₁ : divides x y) (h₂ : y = z) : divides x (2*z) :=
  calc
    divides x y     := h₁
    _ = z           := h₂
    divides _ (2*z) := divides_mul ..

infix : 50 " | " => divides

example (h₁ : divides x y) (h₂ : y = z) : divides x (2*z) :=
  calc
    x | y   := h₁
    _ = z   := h₂
    _ | 2*z := divides_mul ..

上面的例子清楚地表明: 即便关系式中没有中缀符号, 你也依然可以使用 calc。 由于 Lean 已经包含了除法对应的标准 Unicode 符号 (即 ,可以通过输入 \dvd\mid 来打出它), 上述例子采用的是普通的竖线以避免冲突; 但在实际使用中这可不是个好主意, 因为它可能会和 match … with … 表达式中的 ASCII | 混淆。

有了 calc 我们就可以以一种 更自然、更清晰的方式写出上一节的证明

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
variable (x y : Nat)

example : (x + y) * (x + y) = x * x + y * x + x * y + y * y 
  :=calc 
    (x + y) * (x + y) = (x + y) * x + (x + y) * y  := by rw [Nat.mul_add] -- 获得 04-02-代码-10 里的 h1
    -- ↓ , Nat.add_mul               : 公式左子树进行模式匹配
    _ = (x * x + y * x) + (x + y) * y              := by rw [Nat.add_mul]
    -- ↓ , Nat.add_mul               : 公式右子树进行模式匹配
    _ = (x * x + y * x) + (x * y + y * y)          := by rw [Nat.add_mul] -- 获得 04-02-代码-10 里的 h2
    -- ↑ , Nat.add_assoc             : 公式整体进行模式匹配
    _ =((x * x + y * x) + x * y) + y * y           := by rw [Nat.add_assoc]

这里值得考虑另一种 calc 表示法。当第一个表达式占用这么多空间时, 在第一个关系中使用 _ 自然会对齐所有关系式:

还有一种 calc 记法值得考虑: 当第一个表达式占用很多空间时, 在第一对关系中使用 _ 便可以自然地对齐所有关系式:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
variable (x y : Nat)

example : (x + y) * (x + y) = x * x + y * x + x * y + y * y
  :=calc (x + y) * (x + y)
    -- ↓ , Nat.mul_add (x + y) x y : 公式整体进行模式匹配
    _ =  (x + y) * x + (x + y) * y         := Nat.mul_add (x + y) x y   -- 获得 04-02-代码-10 里的 h1
    -- ↓ , Nat.add_mul x y x       : 公式左子树进行模式匹配
    _ =  (x * x + y * x) + (x + y) * y     := by rw [Nat.add_mul x y x]
    -- ↓ , Nat.add_mul x y y       : 公式右子树进行模式匹配
    _ =  (x * x + y * x) + (x * y + y * y) := by rw [Nat.add_mul x y y] -- 获得 04-02-代码-10 里的 h2
    -- ↑ , Nat.add_assoc           : 公式整体进行模式匹配
    _ = ((x * x + y * x) +  x * y)+ y * y  := by rw [Nat.add_assoc] -- 相当于加了一个 Eq.symm

注:演示一下 rw[←Nat.add_assoc] 存在与否的区别
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
variable (x y : Nat)

example : (x + y) * (x + y) = x * x + (y * x + (x * y + y * y))
  :=calc (x + y) * (x + y)
    -- ↓ , Nat.mul_add (x + y) x y : 公式整体进行模式匹配
    _ =  (x + y) * x + (x + y) * y         := Nat.mul_add (x + y) x y   -- 获得 04-02-代码-10 里的 h1
    -- ↓ , Nat.add_mul x y x       : 公式左子树进行模式陪陪
    _ =  (x * x + y * x) + (x + y) * y     := by rw [Nat.add_mul x y x]
    -- ↓ , Nat.add_mul x y y       : 公式右子树进行模式陪陪
    _ =  (x * x + y * x) + (x * y + y * y) := by rw [Nat.add_mul x y y] -- 获得 04-02-代码-10 里的 h2
    -- ↓ , Nat.add_assoc           : 公式整体进行模式匹配
    _ =   x * x + (y * x + (x * y + y * y)):= by rw [Nat.add_assoc]

-- 这里是 Nat.add_assoc 的定义
-- Nat.add_assoc : (a + b) + c = a + (b + c)

这里 Nat.add_assoc 之前的左箭头(可以输入 \l 或者是对应的 ASCII 码 <-) 指示 rewrite以相反的方向应用该等式。如果追求简洁, 那么 rwsimp 各自都可以单独完成这项任务:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
variable (x y : Nat)

example : (x + y) * (x + y) = x * x + y * x + x * y + y * y := by
  rw [
    -- (x + y) * (x + y)
    Nat.mul_add,    -- ↓ , 公式整体进行模式匹配
    -- (x + y) * x + (x + y) * y
    Nat.add_mul,    -- ↓ , 公式左子树进行模式匹配
    -- (x * x + y * x) + (x + y) * y
    Nat.add_mul,    -- ↓ , 公式右子树进行模式匹配
    -- (x * x + y * x) + (x * y + y * y)
    Nat.add_assoc  -- ↑ , 公式整体进行模式匹配
    --((x * x + y * x) + x * y) + y * y
  ]

example : (x + y) * (x + y) = x * x + y * x + x * y + y * y := by
  simp [Nat.mul_add, 
        Nat.add_mul, 
        Nat.add_assoc]

下面是一些与等式有关的练习:

记住:在没有任何参数的情况下,Prop 类型的表达式只是一个断言。请 填写下面 primeprime_fermat 的定义并 构造每个给定的断言。举个例子:你可以通过声称 对每个自然数 n 都存在一个大于 n 的素数来表示素数有无穷多个。哥德巴赫弱猜想 Goldbach’s weak conjecture声称 每个大于 5 的奇数都可以表示为三个素数之和。如果有必要, 请查阅费马素数 Fermat prime的定义或任何其他陈述。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
def even (n : Nat) : Prop := sorry

def prime (n : Nat) : Prop := sorry

def prime_infinitely_many  : Prop := sorry

def prime_fermat  (n : Nat) : Prop := sorry

def prime_fermat_infinitely_many : Prop := sorry

def conjecture_goldbach : Prop := sorry

def conjecture_goldbach_weak  : Prop := sorry

def fermat_last_theorem : Prop := sorry
  1. divides : Nat → Nat → Prop

    答:
    1
    2
    3
    4
    
    def divides : Nat  Nat  Prop 
      : n 
        λ m 
         (k : Nat), k * n = m
  2. even : Nat → Prop

    答:
    1
    2
    3
    4
    5
    6
    7
    8
    
    def divides : Nat  Nat  Prop 
      : n 
        λ m 
         (k : Nat), k * n = m
    
    def even : Nat  Prop 
      : n  
        divides 2 n
  3. prime : Nat → Prop

    答:
    1
    2
    3
    4
    5
    6
    7
    8
    
    def divides : Nat  Nat  Prop
      : n 
        λ m 
         (k : Nat), k * n = m
    
    def prime : Nat  Prop
      : n 
         (k : Nat), (k != n)  (k != 1)  ¬(divides k n)
  4. prime_infinitely_many : Prop

    答:
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    
    def divides : Nat  Nat  Prop
      : n 
        λ m 
         (k : Nat), k * n = m
    
    def prime : Nat  Prop
      : n 
         (k : Nat), (k != n)  (k != 1)  ¬(divides k n)
    
    def prime_infinitely_many : Prop 
      :=∀ (n : Nat),  (k : Nat), (k > n)  (prime k)
  5. prime_fermat : Nat → Prop

    答:
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    
    def divides : Nat  Nat  Prop
      : n 
        λ m 
         (k : Nat), k * n = m
    
    def prime : Nat  Prop
    : n 
       (k : Nat), (k != n)  (k != 1)  ¬(divides k n)
    
    def prime_fermat : Nat  Prop 
      : n 
        prime n   (k : Nat), (2^k + 1 = n)
  6. prime_fermat_infinitely_many : Prop

    答:
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    
    def divides : Nat  Nat  Prop
      : n 
        λ m 
         (k : Nat), k * n = m
    
    def prime : Nat  Prop
    : n 
       (k : Nat), (k != n)  (k != 1)  ¬(divides k n)
    
    def prime_fermat : Nat  Prop 
      : n 
        prime n   (k : Nat), (2^k + 1 = n)
    
    def prime_fermat_infinitely_many : Prop 
      :=∀ (n : Nat),  (k : Nat), k > n  prime_fermat k
  7. conjecture_goldbach : Prop

    答:
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    
    def divides : Nat  Nat  Prop
      : n 
        λ m 
         (k : Nat), k * n = m
    
    def prime : Nat  Prop
      : n 
         (k : Nat), (k != n)  (k != 1)  ¬(divides k n)
    
    def conjecture_goldbach : Prop
      :=∀ (n : Nat), n > 2  even n 
         (p q : Nat), prime p  prime q  p + q = n
  8. conjecture_goldbach_weak : Prop

    答:
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    
    def divides : Nat  Nat  Prop
      : n 
        λ m 
         (k : Nat), k * n = m
    
    def even : Nat  Prop
      : n 
        divides 2 n
    
    def prime : Nat  Prop
    : n 
       (k : Nat), (k != n)  (k != 1)  ¬(divides k n)
    
    def conjecture_goldbach_weak : Prop
      :=∀ (n : Nat), n > 2  even n 
         (p q r : Nat),
        prime p  prime q  prime r  (p + q + r = n)
  9. theorem_fermat_last : Prop

    答:
    1
    2
    3
    
    def theorem_fermat_last : Prop 
      :=∀ (n : Nat), n > 2 
        ¬∃ (a b c : Nat), a^n + b^n = c^n

4.4. 存在量词 The Existential Quantifier

最后我们来看看存在量词:其可以写成 exists x : α, p x,也可以写成 ∃ x : α, p x。 这两种写法其实都是一个更冗长的表达式的缩写,即 Exists (fun x : α => p x),定义位于 Lean 库中。

如你所想的那样,Lean 库中包含了 一个引入规则和 一个消去规则。 引入规则很直白: 要想证明 ∃ x : α, p x, 只需提供一个合适的项 t 以及 p t 的证明即可。 下面是一些例子:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
example :  x : Nat, x > 0 :=
  have h : 1 > 0 := Nat.zero_lt_succ 0
  Exists.intro 1 h -- 1 对应 α 下的项 w ,h 对应 p w 下的证明

example (x : Nat) (h : x > 0) :  y, y < x :=
  Exists.intro 0 h

example (x y z : Nat) (hxy : x < y) (hyz : y < z) :  w, x < w  w < z :=
  Exists.intro y (And.intro hxy hyz)

#check @Exists.intro
-- @Exists.intro : ∀ {α : Sort u_1} {p : α → Prop} (w : α), p w → Exists p
#check Exists.intro
-- Exists.intro.{u} {α : Sort u} {p : α → Prop} (w : α) (h : p w) : Exists p

我们可以用匿名构造子表示法 ⟨t, h⟩ 代替 Exists.intro t h, 如果类型可从语境中推断出的话:

1
2
3
4
5
6
7
8
9
example :  x : Nat, x > 0 :=
  have h : 1 > 0 := Nat.zero_lt_succ 0
  1, h -- 1 对应 α 下的项 w ,h 对应 p w 下的证明

example (x : Nat) (h : x > 0) :  y, y < x :=
  0, h

example (x y z : Nat) (hxy : x < y) (hyz : y < z) :  w, x < w  w < z :=
  y, hxy, hyz

注意到 Exists.intro 有隐式参数: Lean 必须在结论 ∃ x, p x 中推断出谓词 p : α → Prop,而这并非易事;例如, 如果我们有 hg : g 0 0 = 0 并且写下 Exists.intro 0 hg, 那么谓词 p 就会有许多可能的取值,比如分别对应于命题 ∃ x, g x x = x∃ x, g x x = 0∃ x, g x 0 = x,等等。 Lean 会利用语境来推断出哪个是合适的, 下面的例子对此进行了说明:当中 我们将选项 pp.explicit 设置为 true 以要求 Lean 美观显示隐式参数。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
variable (g : Nat  Nat  Nat)

theorem gex1 (hg : g 0 0 = 0) :  x, g x x = x := 0, hg
theorem gex2 (hg : g 0 0 = 0) :  x, g x 0 = x := 0, hg
theorem gex3 (hg : g 0 0 = 0) :  x, g 0 0 = x := 0, hg
theorem gex4 (hg : g 0 0 = 0) :  x, g x x = 0 := 0, hg

set_option pp.explicit true  -- 打印隐式参数

-- 下面的 0 其实应该写成 
-- (@OfNat.ofNat Nat (nat_lit 0) (instOfNatNat (nat_lit 0)))
-- g 0 0 = 0 其实应该写成
-- @Eq Nat (g 0 0) 0
#print gex1
-- theorem gex1 
-- : ∀ (g : Nat → Nat → Nat), g 0 0 = 0 → 
--   @Exists Nat (fun x => @Eq Nat (g x x) x) 
-- :=fun g hg =>
--   @Exists.intro Nat (fun x => @Eq Nat (g x x) x) 0 hg
#print gex2
-- theorem gex2 
-- : ∀ (g : Nat → Nat → Nat), g 0 0 = 0 → 
--   @Exists Nat (fun x => @Eq Nat (g x 0) x) 
-- :=fun g hg =>
--   @Exists.intro Nat (fun x => @Eq Nat (g x 0) x) 0 hg
#print gex3
-- theorem gex3 
-- : ∀ (g : Nat → Nat → Nat), g 0 0 = 0 → 
--   @Exists Nat (fun x => @Eq Nat (g 0 0) x)
-- :=fun g hg =>
--   @Exists.intro Nat (fun x => @Eq Nat (g 0 0) x) 0 hg
#print gex4
-- theorem gex4 
-- : ∀ (g : Nat → Nat → Nat), g 0 0 = 0 → 
--   @Exists Nat fun x => @Eq Nat (g x x) 0 
-- :=fun g hg =>
--   @Exists.intro Nat (fun x => @Eq Nat (g x x) 0) 0 hg

我们可以将 Exists.intro 视为某种信息隐藏操作, 因为它将断言的具体实例隐藏起来并用存在量词代替。 存在量词消去规则 Exists.elim 则执行相反的操作, 它允许我们从 ∃ x : α, p x 证明一个命题 q; 这是通过展示“对任意 wp w 都能推出 q”来实现的。粗略地说, 既然知道存在一个 x 满足 p x, 那么我们可以给它起个名字,比如 w。 如果 q 没有提到 w, 那么展示“p w 能推出 q”就等同 于展示“q 可以从任何这样的 x 的存在性而推得”。 下面是一个例子:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
variable (α : Type) (p q : α  Prop)

example (h :  x, p x  q x) :  x, q x  p x :=
  Exists.elim h                                       -- ∃ 消去
    (fun w                                           -- ∀ 引入
     fun hw : p w  q w                               -- → 引入
     show  x, q x  p x from w, hw.right, hw.left)   -- ∃ 引入 ∧ 引入

#print Exists.elim
-- theorem Exists.elim.{u} :
-- ∀ {α : Sort u}
--   {p : α → Prop}
--   {b : Prop},
-- (∃ x, p x) → (∀ (a : α), p a → b) → b :=
-- fun {α} {p} {b} h₁ h₂ ↦
--  match h₁ with
--  | Exists.intro a h => h₂ a h

将存在量词消去规则和析取连词消去规则进行比较可能会带来一些帮助。 命题 ∃ x : α, p x 可以看成一个关于命题 p a 的大型析取, 其中 a 遍历所有 α 中的元素。注意到 匿名构造子 ⟨w, hw.right, hw.left⟩ 是 嵌套的构造子 ⟨w, ⟨hw.right, hw.left⟩⟩ 的缩写。

带存在量词的命题与 Sigma 类型 很相似,后者在依值类型一节中提到过。 区别在于前者是命题,后者是类型。 不然的话它们非常相似。 给定谓词 p : α → Prop 和一族类型 β : α → Type, 对于项 a : αh : p ah' : β a 而言 项 Exists.intro a h 的类型是 (∃ x : α, p x) : Prop,而 项 Sigma.mk a h' 的类型是 (Σ x : α, β x) : TypeΣ 之间的相似性是 Curry-Howard 同构的另一例子。

Lean 提供一个更加方便的消去存在量词的途径, 那就是通过 match 表达式:

1
2
3
4
5
variable (α : Type) (p q : α  Prop)

example (h :  x, p x  q x) :  x, q x  p x
  :=match h with
    | w, hw => w, hw.right, hw.left

match 表达式是 Lean 函数定义系统的一部分, 其提供了方便且富有表达力的方式来定义复杂函数。 Curry-Howard 同构又一次让我们能够采用这种机制来编写证明。 match 语句会将带有存在量词的命题“解构”到组件 whw 中, 后者可以在语句体中被引用以用于证明命题。 我们还可以对 match 添加类型注释以使代码变得更清晰:

1
2
3
4
5
variable (α : Type) (p q : α  Prop)

example (h :  x, p x  q x) :  x, q x  p x :=
  match h with
  | (w : α), (hw : p w  q w) => w, hw.right, hw.left

甚至还可以在 match 语句中把合取也顺便解构掉:

1
2
3
4
5
variable (α : Type) (p q : α  Prop)

example (h :  x, p x  q x) :  x, q x  p x :=
  match h with
  | w, hpw, hqw => w, hqw, hpw

Lean 还提供了一个支持模式匹配的 let 表达式:

1
2
3
4
5
variable (α : Type) (p q : α  Prop)

example (h :  x, p x  q x) :  x, q x  p x :=
  let w, hpw, hqw := h
  w, hqw, hpw

这实际上是上面的 match 结构的替代记法。 Lean 还允许我们在 fun 表达式中使用隐式的 match

1
2
3
4
variable (α : Type) (p q : α  Prop)

example : ( x, p x  q x)   x, q x  p x :=
  fun w, hpw, hqw => w, hqw, hpw

我们将在归纳和递归一章看到: 所有这些变体其实都是更一般的模式匹配构造的实例。

在下面的例子中,我们将 is_even a 定义为 ∃ a', a = 2 * a', 然后证明两个偶数的和是偶数。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
variable (a b : Nat)

def is_even (a : Nat) :=  a', a = 2 * a'

theorem even_plus_even 
  (ha : is_even a) 
  (hb : is_even b) : is_even (a + b)
  :=Exists.elim
      (show  a', a = 2 * a' from ha)
      (show  a', (a = 2 * a')  ( c', a + b = 2 * c') from fun
        (a' : Nat) =>
        (show (a = 2 * a')  ( c', a + b = 2 * c') from fun
          (hwa : a = 2 * a') =>
          (show  c', a + b = 2 * c' from Exists.elim
            (show  b', b = 2 * b' from hb)
            (show  b', (b = 2 * b')  ( c', a + b = 2 * c') from fun
              (b' : Nat) =>
              show (b = 2 * b')  ( c', a + b = 2 * c') from fun
                (hwb : b = 2 * b') =>
                (show  c', a + b = 2 * c' from Exists.intro
                  (a' + b' : Nat)
                  (show a + b = 2 * (a' + b') from calc
                        a + b = 2 * a' + 2 * b' := by rw [hwa, hwb]
                            _ = 2 * (a' + b')   := by rw [Nat.mul_add]))))))

使用本章提到的各种小工具—— match 语句、 匿名构造子和 rewrite 策略, 我们可以简洁地写出如下证明:

1
2
3
4
5
6
7
8
9
variable (a b : Nat)

def is_even (a : Nat) :=  a', a = 2 * a'

theorem even_plus_even 
  (h1 : is_even a) 
  (h2 : is_even b) : is_even (a + b)
  :=match h1, h2 with
      | a', hwa, b', hwb => a' + b', by rw [hwa, hwb, Nat.mul_add]

正如 构造主义的“或”比古典的“或”强, 构造主义的“存在”也会比古典的“存在”强。 例如下面的推论就需要经典推理: 因为从构造主义的角度来看, 知道“并不是每一个 x 都满足 ¬p”并不等同于 知道“有一个特定的 x 能满足 p”。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
open Classical
variable (p : α  Prop)

example (h : ¬  x, ¬ p x) :  x, p x
  :=(show  x, p x from byContradiction
      (show ¬( x, p x)  False from fun
        (h1 : ¬  x, p x) =>
        have h2 :=
          (show  x, ¬p x from fun
            (x) =>
            (show p x  False from fun
              (h3 : p x) =>
              have h4 :=
                (show  x, p x from x, h3)
              (show False from h1 h4)))
        show False from h h2))

下面是一些涉及到存在量词的常见等式。在下面的练习中, 我们鼓励你写出尽可能多的证明。 至于当中哪些是非构造主义的,需要你自己判断。 因此需要用到一些经典逻辑。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
variable 
(α : Type) 
(p q : α  Prop)
(r : Prop)

open Classical

example : ( x : α, r)  r := sorry
example (a : α) : r  ( x : α, r) := sorry
example : ( x, p x  r)  ( x, p x)  r := sorry
example : ( x, p x  q x)  ( x, p x)  ( x, q x) := sorry

example : ( x, p x)  ¬ ( x, ¬ p x) := sorry
example : ( x, p x)  ¬ ( x, ¬ p x) := sorry
example : (¬  x, p x)  ( x, ¬ p x) := sorry
example : (¬  x, p x)  ( x, ¬ p x) := sorry

example : ( x, p x  r)  ( x, p x)  r := sorry
example (a : α) : ( x, p x  r)  ( x, p x)  r := sorry
example (a : α) : ( x, r  p x)  (r   x, p x) := sorry

注意到 第二个例子和 最后两个例子 要求假设至少有一个类型为 α 的元素 a

以下是两个比较困难的问题的解:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
open Classical

variable 
(α : Type)
(p q : α  Prop)
(r : Prop)
(a : α)

example : ( x, p x  q x)  ( x, p x)  ( x, q x) :=
  Iff.intro
    (fun a, (h1 : p a  q a) =>
      Or.elim h1
        (fun hpa : p a => Or.inl a, hpa)
        (fun hqa : q a => Or.inr a, hqa))
    (fun h : ( x, p x)  ( x, q x) =>
      Or.elim h
        (fun a, hpa => a, (Or.inl hpa))
        (fun a, hqa => a, (Or.inr hqa)))

example : ( x, p x  r)  ( x, p x)  r :=
  Iff.intro
    (fun b, (hb : p b  r) =>
     fun h2 :  x, p x =>
     show r from hb (h2 b))
    (fun h1 : ( x, p x)  r =>
     show  x, p x  r from
       byCases
         (fun hap :  x, p x => a, λ h' => h1 hap)
         (fun hnap : ¬  x, p x =>
          byContradiction
            (fun hnex : ¬  x, p x  r =>
              have hap :  x, p x :=
                fun x =>
                byContradiction
                  (fun hnp : ¬ p x =>
                    have hex :  x, p x  r := x, (fun hp => absurd hp hnp)
                    show False from hnex hex)
              show False from hnap hap)))
  1. (∃ x : α, r) → r

    答:

    简单版:

    1
    2
    3
    4
    5
    6
    7
    8
    
    variable
    (α : Type)
    (p q : α  Prop)
    (r : Prop)
    
    example : ( _ : α, r)  r
      : (h :  _ : α, r)  match h with
        | Exists.intro _ hr => hr

    复杂版:

  2. α → r → (∃ x : α, r)

    答:

    简单版:

    1
    2
    3
    4
    5
    6
    7
    8
    
    variable
    (α : Type)
    (p q : α  Prop)
    (r : Prop)
    
    example (a : α) : r  ( _ : α, r)
      : (hr : r) 
        a, hr

    复杂版:

  3. (∃ x, p x ∧ r) ↔ (∃ x, p x) ∧ r

    答:

    简单版:

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    
    variable
    (α : Type)
    (p q : α  Prop)
    (r : Prop)
    
    example : ( x, p x  r)  ( x, p x)  r
      :=Iff.intro
        (λ (h :  x, p x  r)  match h with
          | w, hw => ⟨⟨w, hw.left, hw.right)
        (λ (h : ( x, p x)  r)  match h with
          | ⟨⟨w, hw, hr => w, hw, hr)

    复杂版:

  4. (∃ x, p x ∨ q x) ↔ (∃ x, p x) ∨ (∃ x, q x)

    答:

    简单版:

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    
    variable
    (α : Type)
    (p q : α  Prop)
    (r : Prop)
    
    example : ( x, p x  q x)  ( x, p x)  ( x, q x)
      :=Iff.intro
        (λ (h :  x, p x  q x)  match h with
        | x, Or.inl hp => Or.inl x, hp
        | x, Or.inr hq => Or.inr x, hq)
        (λ (h : ( x, p x)  ( x, q x))  match h with
        | Or.inl x, hp => x, Or.inl hp
        | Or.inr x, hq => x, Or.inr hq)

    复杂版:

  5. ¬(∃ x, ¬p x) ↔ (∀ x, p x)

    答:

    简单版:

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    
    variable
    (α : Type)
    (p q : α  Prop)
    (r : Prop)
    
    open Classical
    
    example : ( x, p x)  ¬( x, ¬p x)
      := Iff.intro
         (λ (hall :  x, p x) 
          λ (hext :  x, ¬p x)  match hext with
           | x, hn => hn (hall x))
         (λ (h : ¬ ( x, ¬p x)) 
          λ (a : α)  byContradiction
          λ (hn : ¬p a)  h a, hn)
    
    -- 这里附上 (p ∧ q) ↔ ¬(¬p ∨ ¬q) 的证明作为对比
    
    variable
    (p q : Prop)
    
    example : (p  q)  ¬(¬p  ¬q)
      :=Iff.intro
        (λ (hpq : p  q) 
         λ (hnpq : ¬p  ¬q)  match hnpq with
          | Or.inl hnp => hnp hpq.left
          | Or.inr hnq => hnq hpq.right)
        (λ (hnnpq : ¬(¬p  ¬q))  And.intro
         (byContradiction (λ (hnp : ¬p)  hnnpq (Or.inl hnp)))
         (byContradiction (λ (hnq : ¬q)  hnnpq (Or.inr hnq))))

    复杂版:

  6. ¬(∀ x, ¬p x) ↔ (∃ x, p x)

    答:

    简单版:

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    
    variable
    (α : Type)
    (p q : α  Prop)
    (r : Prop)
    
    open Classical
    
    example : ( x, p x)  ¬( x, ¬p x)
      :=Iff.intro
        (λ (hext :  x,  p x) 
         λ (hall :  x, ¬p x)  match hext with
          | x, hp => (hall x) hp)
        (λ (halln : ¬( x, ¬p x))  byContradiction
         λ (hextn : ¬( x,  p x))  halln
          (λ (a : α) 
           λ (hp : p a)  hextn a, hp))
    
    -- 这里附上 (p ∨ q) ↔ ¬(¬p ∧ ¬q) 的证明作为对比
    
    variable
    (p q : Prop)
    
    example : (p  q)  ¬(¬p  ¬q)
      :=Iff.intro
        (λ (hpq : p  q) 
         λ (hnpnq : ¬p  ¬q)  match hpq with
          | Or.inl hp => (hnpnq.left) hp
          | Or.inr hq => (hnpnq.right) hq)
        (λ (hnnpnq : ¬(¬p  ¬q))  byContradiction
         λ (hnpq : ¬(p  q))  hnnpnq (And.intro
          (λ (hp : p)  hnpq (Or.inl hp))
          (λ (hq : q)  hnpq (Or.inr hq))))

    复杂版:

  7. ¬(∃ x, p x) ↔ (∀ x, ¬p x)

    答:

    简单版:

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    
    variable
    (α : Type)
    (p q : α  Prop)
    (r : Prop)
    
    example : ¬( x, p x)  ( x, ¬p x)
      :=Iff.intro
        (λ (hextn : ¬∃ x, p x) 
         λ (x : α) 
         λ (hp : p x) 
         hextn x, hp)
        (λ (halln :  x, ¬p x) 
         λ (hext :  x, p x)  
         Exists.elim hext halln)
    
    -- 这里附上 ¬(p ∨ q) ↔ ¬p ∧ ¬q 的证明作为对比(见上一章练习第 9 题)
    
    variable
    (p q : Prop)
    
    example : ¬(p  q)  ¬p  ¬q
      :=Iff.intro
        (λ (hnpq : ¬(p  q)) 
         (λ (hp : p)  hnpq (Or.inl hp)),
          (λ (hq : q)  hnpq (Or.inr hq)))
        (λ (hnpnq : ¬p  ¬q) 
         λ (hpq : p  q)  match hpq with -- 对应 Or.elim
           | Or.inl hp => (hnpnq.left)  hp
           | Or.inr hq => (hnpnq.right) hq)

    复杂版:

  8. ¬(∀ x, p x) ↔ (∃ x, ¬p x)

    答:

    简单版:

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    
    variable
    (α : Type)
    (p q : α  Prop)
    (r : Prop)
    
    open Classical
    
    example : (¬∀ x, p x)  ( x, ¬p x)
      :=Iff.intro
        (λ (halln : ¬( x, p x))  byContradiction
          (λ (hextn : ¬( x, ¬p x))  halln
            (λ (a : α)  byContradiction
              (λ (hnp : ¬p a)  hextn a, hnp))))
        (λ (hext :  x, ¬p x) 
         λ (hall :  x,  p x)  match hext with
          | w, hnpw => hnpw (hall w))
    
    -- 这里附上 ¬(p ∧ q) ↔ (¬p ∨ ¬q) 的证明作为对比(见上一章练习第 20 题和第 10 题)
    
    variable
    (p q : Prop)
    
    example : ¬(p  q)  (¬p  ¬q)
      :=Iff.intro
        (λ (hnpq : ¬(p  q))  byCases
          (λ (hp : p)  Or.inr
            (λ (hq : q)  hnpq hp, hq))
          (λ (hnp : ¬p)  Or.inl hnp))
        (λ (hnpnq : ¬p  ¬q) 
         λ (hpq : p  q)  match hnpnq with -- 对应 Or.elim
          | Or.inl hnp  => hnp (And.left  hpq)
          | Or.inr hnq  => hnq (And.right hpq))

    复杂版:

  9. (∀ x, p x → r) ↔ (∃ x, p x) → r

    答:

    简单版:

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    
    variable 
    (α : Type) 
    (p q : α  Prop)
    (r : Prop)
    
    example : ( x, p x  r)  (( x, p x)  r)
      :=Iff.intro
        (λ (hallpr :  x, p x  r)  
         λ (hextp  :  x, p x)  match hextp with
          | x, hp => hallpr x hp)
        (λ (hextpr : ( x, p x)  r)  
         λ (x : α) 
         λ (hp : p x) 
         hextpr x , hp)

    复杂版:

    懒得写了。略。

  10. α → ((∃ x, p x → r) ↔ (∀ x, p x) → r)

    答:

    简单版:

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    
    variable
    (α : Type)
    (p q : α  Prop)
    (r : Prop)
    
    open Classical
    
    example : ( x, p x  r)  ( x, p x)  r
      : (hextpr :  x, p x  r) 
        λ (hallp  :  x, p x)  match hextpr with
        | x, hpr => hpr (hallp x)
    example : α  (( x, p x)  r)  ( x, p x  r)
      : (x : α) 
        λ (hallpr : ( x, p x)  r)  byContradiction
          (λ (hnex : ¬( x, p x  r)) 
            (let hallp :  x, p x
            : (x : α)  byContradiction
                (λ (hnpx : ¬p x) 
                 hnex x, λ (hp : p x)  absurd hp hnpx)
            hnex x, λ (_ : p x)  hallpr hallp))

    复杂版:

    懒得写了。略。

  11. α → ((∃ x, r → p x) ↔ (r → ∃ x, p x))

    答:

    简单版:

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    
    variable
    (α : Type)
    (p q : α  Prop)
    (r : Prop)
    
    open Classical
    
    example : ( x, r  p x)  (r   x, p x)
      :=(λ (h :  x, r  p x) 
         λ (hr : r)  match h with
         | x, hrp => x, hrp hr)
    example (x : α) : (r   x, p x)  ( x, r  p x)
      : (hrextp : r   x, p x)  byCases
        (λ (hr : r)  match (hrextp hr) with
          | x, hp => x, λ (_: r)  hp)
        (λ (hnr : ¬r) 
        x, (λ (hr : r)  absurd hr hnr))

    复杂版:

    懒得写了。略。

4.5. 再来些证明语法 More on the Proof Language

我们已经看到:像 funhaveshow 这样的关键字 可使得形式化的证明项能够反映出非正式数学证明的结构。 本节我们将讨论证明语言的一些附加特性,它们会在实际使用中带来便利。

首先我们可以使用 have 表达式引入匿名的辅助目标。 若是想引用最后一个以此方式引入的表达式, 则可以考虑使用关键字 this

1
2
3
4
5
6
7
variable (f : Nat  Nat)
variable (h :  x : Nat, f x  f (x + 1))

example : f 0  f 3 :=
  have : f 0  f 1 := h 0
  have : f 0  f 2 := Nat.le_trans this (h 1)
  show f 0  f 3 from Nat.le_trans this (h 2)

由于证明通常是从一个事实转移到另一个事实, 因此这可以有效地消除大量杂乱标签。

当目标是可被推断的时,我们也可以让 Lean 自己补充证明——只需写下 assumption

1
2
3
4
5
6
7
variable (f : Nat  Nat)
variable (h :  x : Nat, f x  f (x + 1))

example : f 0  f 3 :=
  have : f 0  f 1 := h 0
  have : f 0  f 2 := Nat.le_trans (by assumption) (h 1)
  show f 0  f 3 from Nat.le_trans (by assumption) (h 2)

这会让 Lean 使用 assumption 策略—— 即通过在局部语境中找到合适的假设来证明目标。 我们将在下一章中 学到更多关于 assumption 策略的内容。

我们也可以通过写下 ‹p› 来要求 Lean 填写证明: 其中 p 是命题,我们希望 Lean 能在语境中找到它的证明。 你可以分别使用 \f<\f> 来输入这些角引号。 字母“f”表示“French”,因为对应的 unicode 符号也可以作为法语引号。 这个符号在 Lean 中的定义其实是这样的:

1
notation "‹" p "›" => show p by assumption

这种方法比使用 by assumption 更稳健, 因为需要推断的假设类型是显式给出的。 它还使证明更具可读性。这里有一个更详细的例子:

1
2
3
4
5
6
7
8
9
variable (f : Nat  Nat)
variable (h :  x : Nat, f x  f (x + 1))

example : f 0  f 1  f 1  f 2  f 0 = f 2 :=
  fun _ : f 0  f 1 =>
  fun _ : f 1  f 2 =>
  have : f 0  f 2 := Nat.le_trans f 1  f 2 f 0  f 1
  have : f 0  f 2 := Nat.le_trans (h 0) (h 1)
  show f 0 = f 2 from Nat.le_antisymm this f 0  f 2

请留意:法语引号可用来指代语境中的“任何东西”, 而非仅局限于通过匿名方式引入的东西。 它的使用也并非局限于命题—— 尽管将其用于其他数据类型看起来可能会有点怪:

1
example (n : Nat) : Nat := Nat

接下来我们将展示如何使用 Lean 中的宏系统扩展证明语言。

索引 Index

术语

语法

表格

代码块

练习

  • 04-01-练习-01 全称量词对合取连词有分配律
    例子 (∀ x, p x ∧ q x) ↔ (∀ x, p x) ∧ (∀ x, q x)
  • 04-01-练习-02 全称量词与蕴含连词的关系
    例子 (∀ x, p x → q x) → (∀ x, p x) → (∀ x, q x)
  • 04-01-练习-03 全称量词与析取连词的关系
    例子 (∀ x, p x) ∨ (∀ x, q x) → ∀ x, p x ∨ q x
  • 04-01-练习-04 全称量词辖域收缩
    例子 α → ((∀ x : α, r) ↔ r)
  • 04-01-练习-05 全称量词辖域收缩
    例子 (∀ x, p x ∨ r) ↔ (∀ x, p x) ∨ r
  • 04-01-练习-06 全称量词辖域收缩
    例子 (∀ x, r → p x) ↔ (r → ∀ x, p x)
  • 04-01-练习-07 理发师悖论
    例子 ¬(∀ x : α, p a x ↔ ¬p x x),借用 03-06-练习-17 的答案
  • 04-03-练习-01 整除?
    关系 divides : Nat → Nat → Prop
  • 04-03-练习-02 偶数?
    谓词 even : Nat → Prop
  • 04-03-练习-03 素数?
    谓词 prime : Nat → Prop
  • 04-03-练习-04 素数无穷
    命题 prime_infinitely_many : Prop
  • 04-03-练习-05 费马素数?
    谓词 prime_fermat : Nat → Prop
  • 04-03-练习-06 费马素数无穷
    命题 prime_fermat_infinitely_many : Prop
  • 04-03-练习-07 哥德巴赫猜想
    命题 conjecture_goldbach : Prop
  • 04-03-练习-08 哥德巴赫弱猜想
    命题 conjecture_goldbach_weak : Prop
  • 04-03-练习-09 费马最后定理
    命题 theorem_fermat_last : Prop
  • 04-04-练习-01
    例子 (∃ x : α, r) → r
  • 04-04-练习-02
    例子 α → r → (∃ x : α, r)
  • 04-04-练习-03
    例子 (∃ x, p x ∧ r) ↔ (∃ x, p x) ∧ r
  • 04-04-练习-04
    例子 (∃ x, p x ∨ q x) ↔ (∃ x, p x) ∨ (∃ x, q x)
  • 04-04-练习-05 经典逻辑进入(存在量词的德摩根律)
    例子 ¬(∃ x, ¬p x) ↔ (∀ x, p x)
  • 04-04-练习-06 经典逻辑进入(全称量词的德摩根律)
    例子 ¬(∀ x, ¬p x) ↔ (∃ x, p x)
  • 04-04-练习-07 存在量词的德摩根律
    例子 ¬(∃ x, p x) ↔ (∀ x, ¬p x)
  • 04-04-练习-08 经典逻辑进入(全称量词的德摩根律)
    例子 ¬(∀ x, p x) ↔ (∃ x, ¬p x)
  • 04-04-练习-09
    例子 (∀ x, p x → r) ↔ (∃ x, p x) → r
  • 04-04-练习-10 经典逻辑进入()
    例子 α → ((∃ x, p x → r) ↔ (∀ x, p x) → r)
  • 04-04-练习-11 经典逻辑进入()
    例子 α → ((∃ x, r → p x) ↔ (r → ∃ x, p x))
给我买杯饮料!
Lean-zh 微信微信