在 Coq 中验证数学命题
dove
2020-02-20 03:30:18
> **Reference.** *Software Foundations Volume 1: Logical Foundations (Chap. 1, 2)*, Benjamin C Pierce et al.
> **惯例的 Q&A**
> - 对 OI 有什么用?
> - (貌似)没用。只想看 OI 相关内容的人可以即刻退出。
> - 到底会讲啥?
> - 通过一系列简单的例子让读者对“有程序辅助的数学证明”有初步的了解。
> - 需要什么前置知识?
> - 皮亚诺公理,数学归纳法。.
> - 你个初学者还搁这班门弄斧。
> - 请给我提供具体的修改建议。
## 引入
> “非形式化的证明是算法;形式化的证明是程序。”
什么是证明?用白话说,证明是这样一种东西,它能够让读者无可否认的认识到一个命题是成立的。
**非形式化证明**就是给人看的证明,人们通过阅读证明里的自然语言(比如中文说明)和数学符号来认识到命题的正确性;而**形式化证明**是给**定理证明器**看的证明。它是一段程序,里面的语句代表了一些已经证明的逻辑定理,定理证明器通过执行这段程序将命题归约为平凡的结论。
我们平时写的证明都是非形式化的,这也就是说我们需要依靠人类读者(你的老师、你的同学、同行评审,etc)来验证你的证明是否正确。但是,我们也可以通过书写形式化证明,让机器帮忙验证定理的正确性。这带来了若干好处:
- 形式化证明是代码,这也就是说我们可以实现代码复用等,节省了许多重复操作
- 机器帮助你证明,减少了你脑子的负担
- 只要证出来了,结论(几乎)不可能是错的,因为机器(不考虑极端情况)不会出错
接下来就来认识一下形式化证明吧。
## 认识 Coq
> *前置准备:到[这里](https://github.com/coq/coq/releases/tag/V8.11.0) 下载并安装 Coq。*
Coq 是一个**交互式定理证明器**,[四色定理](https://en.wikipedia.org/wiki/Four_color_theorem)(任何地图都可以只用四种颜色上色)就是用它证明的。我们将借助它证明几个简单的定理。
CoqIde 是 Coq 自带的交互环境,~~我觉得它不太好用但是因为是自带的所以这里就用它了~~,我们将全程在这里编写证明。首先在开始菜单中(或者对于 macOS 用户,在 Launchpad 中;对于 linux 用户,自己鼓捣去)找到 `CoqIde` 并打开。
(CoqIde 的界面。)
![CoqIde 界面](https://cdn.luogu.com.cn/upload/image_hosting/7gxdwma1.png)
> 下文中的每一段代码,你都应该复制到 CoqIde 内左侧的输入框里。
>
> 提供的练习题应该直接写到 CoqIde 里。它们**不是**选做的。如果不做,可能会导致下面的源码无法正常编译。实在不想做可以在本文末尾找到参考答案。
### 类型
Coq 的一大特色是,它并没有所谓“内置的,原始的”类型(比如 C++ 里的 `int`、`bool`、`char` 等)。所有的类型都是通过一种统一的方式定义的。
来看一下在 Coq 中如何定义布尔值:
```coq
Inductive bool: Type :=
| true
| false.
```
这里,我们定义了一个 `bool` 类型,有两种构造方式,分别是 `true` 和 `false`,也就是说有两种可能的值。
其中:
- `Inductive <类型名>: Type` 代表了我们正在定义一个**类型**;
- `:=` 是 Coq 用的赋值符号;
- 接下来每一行,由 `|` 打头,后面是这个类型可能的**构造方式**(也就是说,可能的值);
- 每个 Coq 语句由 `.` 结尾。
复制前面那段代码到 CoqIde 中,点击上端第三个按钮(向下的箭头),右下角的 Log 中会出现一些信息,同时这段代码被染绿,这就说明这个类型被定义了。之后每复制一段代码到 CoqIde 中,都可以单击这个按钮执行,点一次执行一句(也就是执行到下一个句号 `.`)。
(定义类型后的 CoqIde。)
![定义 bool 类型后的 CoqIde](https://cdn.luogu.com.cn/upload/image_hosting/dj6imzvf.png)
**练习题. Ex 1.** 定义一个类型 `weekday`,让它代表一周内的七天。
```coq
Inductive weekday: Type :=
(* 把这段注释替换为你的答案 *).
```
### 函数
Coq 作为一门编程语言,当然支持定义函数。但是,正如其他函数式编程语言一样,Coq 只能定义纯函数;更进一步的是,与 Haskell 等语言相比,Coq 只能定义能够终止的函数(无限递归不被允许)。
> 对于看不懂上面一段在说什么的人,详细说明一下:
> - 纯函数是这样一类函数,它能够和数学上的函数等价。这也就是说,纯函数对于相同的输入总是得到相同的结果,并且没有副作用(不更改全局变量,不输出东西到屏幕上,等等)。
> - Coq 只能定义能够终止的函数,所以 Coq 中停机是必然的,所以 Coq 不是图灵完全的。
那么,我们来定义一下对于 `bool` 的操作:取反、与、或、异或。
首先来看一下取反的函数:
```coq
Definition negb (b:bool) : bool :=
match b with
| true => false
| false => true
end.
```
其中:
- `Definition <函数名> (<参数名>:<参数类型>) : <返回类型>` 是函数的声明;
- `:=` 依旧是赋值符号;
- `match <值> with <模式> end.` 是**模式匹配**。拿前面那段代码具体理解一下:
- `match b with`:我们要匹配的值是传入的参数 `b`。
- `| true => false`:如果 `b` 是通过 `true` 构造的,那么返回 `false`;
- `| false => true`:如果是通过 `false` 构造的,那么返回 `true`;
- `end.`:结束。
(定义函数后的 CoqIde。)
![添加函数定义后的 CoqIde](https://cdn.luogu.com.cn/upload/image_hosting/ja5i3aek.png)
同时,也可定义接受多个参数的函数。这里定义一下逻辑与($A \land B$):
```coq
Definition andb (b:bool) (c:bool) : bool :=
match b with
| true => c
| false => false
end.
```
**练习题. Ex 2.** 定义对于类型 `bool` 的逻辑或(命名为 `orb`)和异或(命名为 `xorb`)的函数。
```coq
Definition orb (b c: bool): bool :=
(* 把这段注释替换为你的答案 *).
Definition xorb (b c: bool): bool :=
(* 把这段注释替换为你的答案 *).
```
## 平凡的证明
先来解决一些平凡的命题。
**例. ${\rm true} \land {\rm true} = {\rm true}.$**
这个命题很平凡。我们来看一下,在 Coq 中如何表达它:`Theorem t_and_t_eq_t: (andb true true = true).`
其中:
- `Theorem <命题名>: <命题>.` 声明了一个命题。(注意,`Theorem` 可以替换成 `Example`、`Lemma`、`Remark`、`Fact`、`Property`、`Proposition`、`Definition`,效果完全一样,看你乐意用哪个。)
- 命题名怎么取随你喜欢,但最好是有意义的名字。
- 在 Coq(以及许多函数式编程语言)中,我们通过空格而不是括号调用函数。`andb true true` 可以理解为 `andb(true, true)` 一样的东西,而括号只是为了调整优先级。
再来看一下,我们如何证明它。
```coq
Theorem t_and_t_eq_t: (andb true true = true).
Proof.
simpl.
reflexivity.
Qed.
```
其中:
- 第一行是命题,刚才讲过了。
- `Proof.` 代表证明的开始。
- `simpl.` 将式子两端计算并化成最简。此时,两端都被计算为了 `true`。
- `reflexivity.` 声明,因为等号两端相同,所以命题成立。
- 另外 `reflexivity.` 其实也会化简。这里加上 `simpl.` 只是为了使步骤更清晰。
- `Qed.` 代表证明的结束。
在 CoqIde 里一步一步执行这段证明,观察右上角的证明区域如何变化。
(开始证明。)
![Proof. 时的 CoqIde](https://cdn.luogu.com.cn/upload/image_hosting/hgjkv7ga.png)
(化简。)
![simpl. 时的 CoqIde](https://cdn.luogu.com.cn/upload/image_hosting/lvzd73rs.png)
(结束证明。)
![refl. 时的 CoqIde](https://cdn.luogu.com.cn/upload/image_hosting/u8cy6pw5.png)
**练习题. Ex 3.** 给出下面几个关于 `andb` 性质的平凡命题的证明。
```coq
Theorem t_and_f_eq_f: (andb true false = false).
(* Proof. ... Qed. 用你的证明替换掉这段注释吧。 *)
Theorem f_and_t_eq_f: (andb false true = false).
(* Proof. ... Qed. 用你的证明替换掉这段注释吧。 *)
Theorem f_and_f_eq_f: (andb false false = false).
(* Proof. ... Qed. 用你的证明替换掉这段注释吧。 *)
```
## 自然数与递归
> 注意,这一段中的*类型*声明都不用复制到 CoqIde 中(但是函数和证明仍然需要),因为 Coq 预置了相同的类型。
你可能会好奇,如何在 Coq 的类型系统里定义自然数。这里,我们需要知道两点理论基础:
- Coq 类型的构造方式可以接受参数。
- 自然数是由后继关系刻画的(注意,这里我们把一个自然数 $n$ 的后继记为 $n^+$)。
来看一下自然数的定义:
```coq
Inductive nat: Type :=
| O
| S n.
```
它有两个构造方式。
- `O` 代表 $0$;
- `S n` 是接受一个参数的构造方式,它代表那个参数的后继。
这也就是说,`O` 是 $0$,`S O` 是 $1$,`S (S O)` 是 $2$,`S (S (S O))` 是 $3$ ......
同时,我们定义的类型都是不限制大小的。所以,除非我们耗尽内存,否则不会有溢出的情况。
自然数上的许多函数也需要递归定义。Coq 的递归函数使用关键字 `Fixpoint` 而非 `Definition`。
来看加法的定义。
```coq
Fixpoint plus (a: nat) (b: nat): nat :=
match a with
| O => b
| S a' => S (plus a' b)
end.
```
其中有两种模式:
- `O` 匹配 $0$;
- `S a'` 是带参数的匹配,匹配到任何不是 $0$ 的自然数,可以认为这里 `a'` 把 `a` 最外层的 `S` 剥掉了(也就是说减了 $1$)。
跟数学中自然数加法的定义对比一下,会发现是完全对应的,这里不再赘述。
再看减法(如果结果小于 $0$ 那么仍然得到 $0$):
```coq
Fixpoint minus (a b: nat): nat :=
match a with
| O => O
| S a' =>
match b with
| O => a
| S b' => minus a' b'
end
end.
```
这里可以看到,`match` 是能够嵌套的,并且只有最外层需要句号 `.`。
**练习题. Ex 4. 完成以下乘法函数与判等函数。记住 `match ... with ... end` 能够嵌套。**
```coq
Fixpoint mult (a b: nat): nat :=
(* 把这段注释替换为你的答案 *).
Fixpoint eq (a b: nat): bool :=
(* 把这段注释替换为你的答案 *).
```
## 使用重写来证明(`rewrite`)
来看一下这个命题。
**命题.** 对于任何自然数 $n,m$,如果 $n=m$,那么 $n+n=m+m$。
在 Coq 里,可以这样描述:`Theorem plus_id: forall (n m: nat), n = m -> plus n n = plus m m.`
其中:
- `forall (<名称>: <类型>),` 表达了“对于所有”的意思。也可以有多个参数,就如同书写数学表达式一样。
- `->` 代表可以从左边推出右边。
先考虑如何在数学中证明这个命题。
> **命题.** 对于任何自然数 $n,m$,如果 $n=m$,那么 $n+n=m+m$。
>
> **证明.**
> - 因为 $n = m$,所以将式中 $n$ 全部替换为 $m$。
> - 等式两边都为 $m + m$,所以命题成立。
而在 Coq 中,可以通过 `intros <空格分隔的名称列表>.` 来将 $n, m$ 这类“变量”与 $n = m$ 这种“前提”**导入**到证明当中。在这里,我们可以在证明中写 `intros x y H.`。Coq 会先引入 `forall` 中的变量 `n, m` 并且重命名为 `x, y`;再引入命题主体里的前提 `n = m` 并将它命名为 `H`。用其他名字也没关系,完全是习惯问题。
我们使用 `simpl.` 时,Coq 会默认化简当前的命题;但是,我们也可以指定化简某个等式。比如我们上面引入了 `H`,就可以通过 `simpl in H.` 化简等式 `H`。
而之前提到的“将一个东西替换为相等的另一个东西”,也就是**重写**,则可以通过 `rewrite -> 等式.` 进行。我们现在可以 `rewrite -> H.`,它会把命题中(`n + m = m + m` 中)所有这个等式(`n = m`)左边的值(`n`)替换为右边的值(`m`)。这样就可以完成我们的证明。
需要注意的是,还有另外一种重写方式 `rewrite <- 等式.`,这样会把等式**右边**的值全替换为**左边**的值,恰好颠倒过来。
```coq
Theorem plus_id:
forall (n m: nat),
n = m -> plus n n = plus m m.
Proof.
intros x y.
intros H.
rewrite -> H.
reflexivity.
Qed.
```
(`intros` 前与 `intros` 后,注意分割线上方出现的东西。)
![开始证明](https://cdn.luogu.com.cn/upload/image_hosting/eig4f08t.png)
![intros](https://cdn.luogu.com.cn/upload/image_hosting/omu0ldav.png)
(重写之后,等式两边相同了。)
![rewrite](https://cdn.luogu.com.cn/upload/image_hosting/uthiis06.png)
**练习题. Ex 5. 证明命题:对于所有自然数 $n,m$,如果 $m = n^+$,那么 $m \times (1 + n) = m \times m$。**
```coq
Theorem mult_S_1:
forall (m n: nat),
m = S n
-> mult m (plus 1 n) = mult m m.
(* Proof. ... Qed. 用你的证明替换掉这段注释吧。 *)
```
## 分类讨论(`destruct`)
在上一节,我们使用了 `simpl.`、 `reflexivity.`、`intros <...>.`、`rewrite -> <...>.` 这几个**策略**(tactics)进行了证明。然而,在遇到稍许复杂的命题时,单凭化简和重写可能没法起到很好的作用。
来看一个不是那么平凡的命题。
**命题.** 对于自然数 $n$,$n + 1 \neq 0.$
我们或许会想直接通过化简解决,然而在 Coq 中这行不通:
```coq
Theorem plus_1_neq_0_firsttry:
forall n: nat,
eq (plus n 1) 0 = false.
Proof. reflexivity. Qed. (* 行不通! *)
```
这是因为 Coq 对于 `n` 具体是什么一无所知。但我们可以分类讨论:
- 如果 `n` 是 `0` 那么命题显然成立;
- 如果 `n` 是某个后继 `m^+` 那么可以通过化简 `eq` 的定义得出成立。
要实现这样的分类讨论,我们可以使用 `destruct <名字> eqn: <名字>.`。前一个名字是我们要分类讨论的对象,后一个是我们给等式起的名字(也就是说叫什么都行),这个等式在不同情况中代表讨论对象的不同状态,比如在这里,它在两种情况中分别是 `n = 0` 和 `n = S n0`。
```coq
Theorem plus_1_neq_0:
forall n: nat,
eq (plus n 1) 0 = false.
Proof.
intros n.
destruct n eqn: E.
- reflexivity.
- reflexivity.
Qed.
```
逐步执行上面的代码,可以看到 `destruct n.` 将命题拆分成了两个分命题(subgoals)。这些命题中 Coq 有了更多的信息,证明起来也就更容易。在分命题中证明时,我们使用类似 Markdown 列表的语法来分别两个分命题。之后还会有分命题中的分命题,每层都需要使用不同的列表记号(可用的有 `-`、`+`、`*`)。
(`destruct` 将命题分解。)
![destruct](https://cdn.luogu.com.cn/upload/image_hosting/9lo1aiha.png)
(第一个分命题。)
![1st subgoal](https://cdn.luogu.com.cn/upload/image_hosting/3495j0th.png)
(完成第一个分命题时,Coq 提醒还存在未验证的分命题,同时展示第二个分命题。)
![2nd subgoal](https://cdn.luogu.com.cn/upload/image_hosting/ypis9wat.png)
**练习题. Ex 6.** 证明对于布尔值 $b, c$,如果 $b \land c = {\rm true}$ 那么 $c = {\rm true}$。(注意,分类讨论时,你可能会遇到一些荒谬的情况,它们拥有错误的前提。但是不用担心,这些情况下你仍然可以推出结论。记住,从错误的前提什么都可以推出。)
```coq
Theorem andb_true_elim2:
forall (b c: bool),
andb b c = true
-> c = true.
(* Proof. ... Qed. 用你的证明替换掉这段注释吧。 *)
```
## 归纳证明(`induction`)
有时候,分类讨论也不管用:
```coq
Theorem plus_n_O_firsttry:
forall n: nat,
n = plus n 0.
Proof.
intros n.
destruct n eqn: E.
- (* n = 0 *)
reflexivity.
- (* n = S n' *)
simpl. (* 这里我们卡住了 *)
Abort.
```
所以,我们需要借助数学归纳法。
> **数学归纳法.** 对于一个命题 $P(n)$ 如果 $P(0)$ 且 $P(m) \Rightarrow P(m^+)$,那么 $P(n)$ 对于全体自然数成立。
在 Coq 中,可以通过 `induction <目标>.` 应用数学归纳法,它将命题拆分为两个分命题,要求分别在目标为 $0$ 与 $n^+$ (此时命题对 $n$ 成立)时证明命题。
```coq
Theorem plus_n_O:
forall n: nat,
n = plus n 0.
Proof.
intros n.
induction n.
- reflexivity.
- simpl.
rewrite <- IHn.
reflexivity.
Qed.
```
特别注意那个 `IHn`,它是 Coq 为归纳的前提条件(即当目标为 $n^+$ 时 $n = n + 0$)自动起的名字。
(执行 `induction` 后分解为两个分命题)
![induction](https://cdn.luogu.com.cn/upload/image_hosting/ki1ftwka.png)
(基准条件)
![baseline](https://cdn.luogu.com.cn/upload/image_hosting/6y76kj7s.png)
(归纳条件)
![induction](https://cdn.luogu.com.cn/upload/image_hosting/dzthnmdi.png)
**练习题. Ex 7.** 证明一条引理:对于自然数 $n, m$,$(n+m)^+ = n + m^+$。
```coq
Theorem plus_n_Sm:
forall n m: nat,
S (plus n m) = plus n (S m).
(* Proof. ... Qed. 用你的证明替换掉这段注释吧。 *)
```
**加法交换律.** 对于自然数 $n, m$,$n + m = m + n.$
我们来借助 Coq 证明它。首先写下命题:
```coq
Theorem plus_comm:
forall n m: nat,
plus n m = plus m n.
```
尝试归纳:
```coq
Proof.
intros n m.
induction n.
- simpl.
```
看到 CoqIde 给出的第一个分命题:
![plus comm 1st subgoal](https://cdn.luogu.com.cn/upload/image_hosting/yxcynahe.png)
`m = m + 0`,正是之前我们证明过的命题 `plus_n_O`。在 Coq 中,我们可以使用已经证明的命题(也就是定理)进行重写。于是
```coq
rewrite <- plus_n_O.
reflexivity.
```
第一个分命题证明完毕。看第二个:
```coq
- simpl.
```
![plus comm 2nd subgoal](https://cdn.luogu.com.cn/upload/image_hosting/d12up0hc.png)
尝试重写:
```coq
rewrite -> IHn.
```
![plus comm 2nd subgoal after rewrite](https://cdn.luogu.com.cn/upload/image_hosting/6zkgpsim.png)
`S (m + n) = m + S n` 正是练习题中证明的引理 `plus_n_Sm`。重写,证毕。
```coq
rewrite -> plus_n_Sm.
reflexivity.
Qed.
```
完整的证明:
```coq
Theorem plus_comm:
forall n m: nat,
plus n m = plus m n.
Proof.
intros n m.
induction n.
- simpl.
rewrite <- plus_n_O.
reflexivity.
- simpl.
rewrite -> IHn.
rewrite -> plus_n_Sm.
reflexivity.
Qed.
```
**练习题. Ex 8.** 证明加法结合律:对于自然数 $n, m, p$,$n+m+p = n+(m+p)$。
```coq
Theorem plus_assoc:
forall n m p: nat,
plus (plus n m) p = plus n (plus m p).
(* Proof. ... Qed. 用你的证明替换掉这段注释吧。 *)
```
## 练习题答案
### Ex. 1
```coq
Inductive weekday: Type :=
| monday
| tuesday
| wednesday
| thursday
| friday
| saturday
| sunday.
```
### Ex. 2
```coq
Definition orb (b c: bool): bool :=
match b with
| true => true
| false => c
end.
Definition xorb (b c: bool): bool :=
match b with
| true => negb c
| false => c
end.
```
### Ex. 3
```coq
Theorem t_and_f_eq_f: (andb true false = false).
Proof. reflexivity. Qed.
Theorem f_and_t_eq_f: (andb false true = false).
Proof. reflexivity. Qed.
Theorem f_and_f_eq_f: (andb false false = false).
Proof. reflexivity. Qed.
```
### Ex. 4
```coq
Fixpoint mult (a b: nat): nat :=
match a with
| O => O
| S a' => plus b (mult a' b)
end.
Fixpoint eq (a b: nat): bool :=
match a with
| O =>
match b with
| O => true
| S b' => false
end
| S a' =>
match b with
| O => false
| S b' => eq a' b'
end
end.
```
### Ex. 5
```coq
Theorem mult_S_1:
forall (m n: nat),
m = S n
-> mult m (plus 1 n) = mult m m.
Proof.
intros m n H.
simpl.
rewrite <- H.
reflexivity.
Qed.
```
### Ex. 6
```coq
Theorem andb_true_elim2:
forall (b c: bool),
andb b c = true
-> c = true.
Proof.
intros b c H.
destruct b eqn: Eb.
- simpl in H.
rewrite <- H.
reflexivity.
- simpl in H.
destruct c.
+ reflexivity.
+ rewrite -> H.
reflexivity.
Qed.
```
### Ex. 7
```coq
Theorem plus_n_Sm:
forall n m: nat,
S (plus n m) = plus n (S m).
Proof.
intros n m.
induction n.
- reflexivity.
- simpl.
rewrite -> IHn.
reflexivity.
Qed.
```
### Ex. 8
```coq
Theorem plus_assoc:
forall n m p: nat,
plus (plus n m) p = plus n (plus m p).
Proof.
intros n m p.
induction n.
- reflexivity.
- simpl.
rewrite -> IHn.
reflexivity.
Qed.
```