更多内容见 https://github.com/january147/software_foundations_notes
导入经过编译的模块
From LF Require Export Basics.
LF
表示项目目录,Basics
是导入的模块名称(对应Basics.vo
文件)
First create a file named
_CoqProject
containing the following line (if you obtained the whole volume “Logical Foundations” as a single archive, a _CoqProject should already exist and you can skip this step):-Q . LF
This maps the current directory (".", which contains Basics.v, Induction.v, etc.) to the prefix (or “logical directory”) “LF”.
通过创建_CoqProject
文件,指定-Q . LF
可以将本地目录.
映射到逻辑目录LF
。
Proof by induction
对于无法逐个列举所有情况的命题(即使用destruct
无法完成证明),可以通过数学归纳法来证明,即对于命题
P
(
n
)
P(n)
P(n), 证明
P
(
0
)
P(0)
P(0)为真,并且
P
(
n
)
→
P
(
n
+
1
)
P(n) \to P(n+1)
P(n)→P(n+1)为真,根据数学归纳法则有
P
(
n
)
P(n)
P(n)为真。
Theorem plus_n_Sm : forall n m : nat,
S (n + m) = n + (S m).
Proof.
intros n m.
(* seems impossible to induction m? *)
induction n as [| n' Hn].
-simpl. reflexivity.
-simpl. rewrite <- Hn. reflexivity.
Qed.
tactic
induction
induction
和destruct
有点类似,都是将变量进行分类讨论。不同的是,induction
即采用数学归纳法进行证明,即将变量分为初始值和后续任意一个值两种情况进行讨论。
Theorem plus_n_O : forall n:nat, n = n + 0.
Proof.
intros n. induction n as [| n' IHn'].
(* n = 0 *)
- reflexivity.
(* n = S n', n' = n' + 0 *)
- simpl. rewrite <- IHn'. reflexivity. Qed.
assert
使用assert
可以在一个定理的证明过程中定义一个子定理(通常称为引理lemma
),通过证明这个子定理来证明最终的定理。通常很简单或者泛用性较差(不在其他定理证明中使用)的子定理才使用assert
在证明过程中定义,复杂或者通用的子定理通过使用Theorem
或者Lemma
直接定义。
Theorem mult_0_plus' : ∀ n m : nat,
(0 + n) × m = n × m.
Proof.
intros n m.
assert (H: 0 + n = n). { reflexivity. }
rewrite → H.
reflexivity. Qed.
rewrite
可以配合assert
实现指定具体的改写目标。例如在下面这个定理的证明过程中,存在一个等式中有多个可以rewrite
的地方,仅使用rewrite
无法指定特定的部分进行rewrite
,导致定理难以证明,通过assert
声明针对该证明过程中特定变量名称的子定理可以使得rewrite
仅改写我们所需的部分
Theorem mult_n_Sm: forall n m : nat, n * S m = n + n * m.
Proof.
intros n m. induction n as [|n' Hn].
-simpl. reflexivity.
-simpl. rewrite -> Hn. rewrite -> plus_assoc.
rewrite -> plus_assoc.
(* 这一步需要时证明目标为S (m + n' + n' * m) = S (n' + m + n' * m),可以看到
使用加法交换律改写m + n'即可得证,然而等式中存在多个加号,使用一般得加法交换律会导
致rewrite无法确定应该改写哪一个(例如改写等式左边第二个加号则会导致无法证明),因
此我们使用assert针对上述目标中的变量名称以及需要交换的部分(m + n')定义一个特定
的加法交换律,实现对所需特定位置的rewrite*)
assert(H: m + n' = n' + m).
(* 使用通用的交换律证明这里特定的交换律 *)
--rewrite -> plus_comm. reflexivity.
(* 使用特定的交换律实现目标中指定部分的rewrite从而证明目标 *)
--rewrite -> H. reflexivity.
Qed.
replace
上边提到在rewrite
无法确定正确的改写位置时可以使用assert
来定义一个针对目标中需要改写位置的子定理来实现控制rewrite
的改写位置。除了该方法外,还可以使用replace
来实现。使用replace
可以将目标中的某部分t
替换为q
,替换之后会生成一个t = q
的子目标,需要证明t=q
,相当于使用assert
定义了一个t=q
的子定理,然后使用该子定理rewrite
原目标。
replace (t) with (p)
下面是一个小例子,该例子使用加法交换律plus_comm
,然而在目标中存在多个可交换的地方,因此使用replace
对指定位置进行替换。
Theorem plus_swap' : forall n m p : nat,
n + (m + p) = m + (n + p).
Proof.
intros n m p. induction n as [|n' Hn].
-simpl. reflexivity.
(* 这里后续会补充当前目标,方便理解replace的使用 *)
(* 为了证明目标,使用S (n' + p) + m替换了目标中的m + S (n' + p)
另外生成了一个子目标m + S (n' + p) = S (n' + p) + m需要证明
*)
-simpl. replace (m + S (n' + p)) with (S (n' + p) + m).
--simpl. rewrite -> Hn. rewrite <- plus_comm. reflexivity.
--rewrite <- plus_comm. reflexivity.
Qed.
重点练习
定义unary
表示转换为binary
表示的函数
Fixpoint nat_to_bin (n:nat) : bin :=
match n with
(* 0到0的转换 *)
| O => Z
(* >= 1的数使用incr函数(第一章basics中定义)结合递归定义来实现 *)
| S n' => incr (nat_to_bin n')
end.
证明转换函数的正确性,即证明
Theorem nat_bin_nat : forall n, bin_to_nat (nat_to_bin n) = n.
在使用数学归纳法证明该定理的过程中,存在一个bin_to_nat (incr (nat_to_bin n')) = S n'
的中间目标,而归纳假设为bin_to_nat (nat_to_bin n') = n'
。为了使用归纳假设,我们需要将incr
函数转换到最外层去,因此,我们定义如下引理
Lemma bin_incr: forall n : bin, bin_to_nat (incr n) = S (bin_to_nat n).
Proof.
intros n. induction n as [|n1 Hn1 |n2 Hn2].
-simpl. reflexivity.
-simpl. reflexivity.
-simpl. rewrite -> Hn2. simpl. rewrite <- plus_comm. reflexivity.
Qed.
使用该引理即可证明转换函数的正确性定理。
Theorem nat_bin_nat : forall n, bin_to_nat (nat_to_bin n) = n.
Proof.
intros n. induction n as [|n' Hn].
-simpl. reflexivity.
-simpl. rewrite -> bin_incr. rewrite -> Hn. reflexivity.
Qed.