更多内容见 https://github.com/january147/software_foundations_notes
原始章节见 software foudations
使用
Theorem
,Lemma
和Example
定义的命题(目标)在coq中拥有一个相同的类型Prop
, 即命题(目标)本身也是一种变量,其类型为Prop
. 因此可以用于执行各种运算,详见[Programming with Propositions](#Programming with Propositions)
注意完整的定理包含了命题
Prop
以及其证明过程(当然通常说的定理仅指命题部分),因此定理本身的类型并不是Prop
(即使用Check
检查Theorem
,Lemma
以及Example
并不会得到Prop
而是得到定理中的命题部分,这类似于使用Check
检查函数时返回了函数的参数和返回值类型(在一般编程语言中称为函数原型)),详见[Applying Theorems to Arguments](#Applying Theorems to Arguments)
Prop
是proposition的简化表示
相等(equality)
在coq中使用=
表示,=
是coq
标准库定义的eq
函数通过Notation
定义的一种表示形式。=
接收任何相同类型的两个变量,并返回一个Prop
.
逻辑连接符
蕴含(implication)
在coq中使用->
表示由->
左边的假设可以得到右边的结论,使用intros
可以将->
左边的内容加入coq证明上下文的假设中。
逻辑与(logical and)
逻辑与(logical and, conjunction)用于连接两个Prop
,仅当两个Prop
均为真时使用逻辑与连接得到的Prop
为真。
在coq中,逻辑与使用/\
表示,使用split
可以将逻辑与连接的两个Prop
构成的单个目标分解为两个子目标。
如下例子中,使用split
将目标n = 0 /\ m = 0
分解为两个子目标n = 0
和m = 0
.
Example and_exercise :
forall n m : nat, n + m = 0 -> n = 0 /\ m = 0.
Proof.
intros n m. split.
-destruct n.
--reflexivity.
--discriminate.
-destruct m.
--reflexivity.
--rewrite <- plus_n_Sm in H. discriminate.
Qed.
使用destruct
可以将假设中的由逻辑与连接的两个Prop
分解成两个假设. 如下例子中,使用destruct
将假设H: P /\ Q
分解为H1 : P
和H2 : Q
.
对逻辑与连接的假设使用
destruct
不会生成新的子目标
Lemma proj2 : forall P Q : Prop,
P /\ Q -> Q.
Proof.
intros P Q H.
destruct H as [H1 H2].
apply H2.
Qed.
逻辑或(logic or)
逻辑或(logical or, disjunction)用于连接两个Prop
,当两个Prop
任意一个为真时使用逻辑或连接得到的Prop
为真。
在coq中,使用\/
表示逻辑或。 当目标中存在逻辑或时,使用left
或者right
可以选择\/
左边或者右边的Prop
替换当前的证明目标进行证明。
Lemma or_intro_l : forall A B : Prop, A -> A \/ B.
Proof.
intros A B HA.
(* 此步骤目标是A \/ B *)
left.
(* 此步骤目标变为A *)
apply HA.
Qed.
同样,使用destruct
也可以将假设中的由逻辑或连接的两个Prop
分解成两个假设, 但和上述逻辑与不同的是,这里分解为两个假设会将原目标变为两个相同的子目标,并且每个子目标分别对应一个分解后的假设。
在如下的例子中destruct H as [Hn | Hm]
将n = 0 \/ m = 0
分解为n = 0
并生成一个子目标n * m = 0
,再分解出m = 0
并生成另一个相同的子目标n * m = 0
,后续需要分别在n = 0
成立以及m=0
成立的情况下证明n * m = 0
.
注意这里
destruct
的语法也和分解逻辑与连接的Prop
不同,这里的Hn
和Hm
之间使用|
分隔,而在逻辑与中使用空格分隔
Lemma eq_mult_0 :
forall n m : nat, n = 0 \/ m = 0 -> n * m = 0.
Proof.
intros n m H.
(* 分解假设,生成两个子目标 *)
destruct H as [Hn | Hm].
- (* Here, [n = 0] *)
rewrite Hn. reflexivity.
- (* Here, [m = 0] *)
rewrite Hm. rewrite <- mult_n_O.
reflexivity.
Qed.
子目标1
n, m : nat
Hn : n = 0
______________________________________(1/1)
n * m = 0
子目标2
n, m : nat
Hm : m = 0
______________________________________(1/1)
n * m = 0
逻辑非
False
coq中有一个内置的不成立的Prop
——False
. 当某个假设为False
, 可以使用destruct
来结束当前目标的证明,类似使用discriminate
来处理矛盾的假设.
discriminate
并不处理某个假设直接为False
的情况,其基于假设中等号两边的变量由同一个类型的不同constructor
生成(只有相同的constructor
可能生成相同的值)来判断该假设的矛盾性。
关于
destruct
在处理False假设的具体用法我目前还没有完全弄明白
Theorem ex_falso_quodlibet : forall (P:Prop), False -> P. Proof. (* WORKED IN CLASS *) intros P contra. destruct contra. Qed.
Since reasoning with [ex_falso_quodlibet] is quite common, Coq
provides a built-in tactic, [exfalso], for applying it.
ex_falso_quodlibet
又称the principle of explosion
, 指如果在假设本身不成立的情况下可以有该假设得出任何结论
逻辑非
使用False
可以定义逻辑非的运算。
Definition not (P:Prop) := P -> False.
Notation "~ x" := (not x) : type_scope.
逻辑等价 (logical equivalence)
Definition iff (P Q : Prop) := (P -> Q) /\ (Q -> P).
Notation "P <-> Q" := (iff P Q)
(at level 95, no associativity)
: type_scope.
使用逻辑等价进行证明
通过引入Setoids.Setoid
可以让coq的rewrite
以及reflexivity
可以直接使用逻辑等价关系进行替换和证明。
From Coq Require Import Setoids.Setoid.
A “setoid” is a set equipped with an equivalence relation,
that is, a relation that is reflexive, symmetric, and transitive.
When two elements of a set are equivalent according to the
relation, [rewrite] can be used to replace one element with the
other. We’ve seen that already with the equality relation [=] in
Coq: when [x = y], we can use [rewrite] to replace [x] with [y],
or vice-versa.Similarly, the logical equivalence relation [<->] is reflexive,
symmetric, and transitive, so we can use it to replace one part of
a proposition with another: if [P <-> Q], then we can use
[rewrite] to replace [P] with [Q], or vice-versa.
存在量词
相对于forall
,另外一个关于范围的修饰词是exists
,forall
表示目标对于某种类型的全部变量均成立,而exists
表示目标对于某种类型的变量中有至少一个具体的变量成立。证明包含exists
修饰的变量的定理时可以使用exists
来指定具体的变量完成证明。
Definition even x := exists n : nat, x = double n.
Lemma four_is_even : even 4.
Proof.
unfold even. exists 2. reflexivity.
Qed.
对于假设中存在exists
的情况,使用destruct
可以引入一个具体的变量x
并且消去假设中的exists
.
Theorem exists_example_2 : forall n,
(exists m, n = 4 + m) ->
(exists o, n = 2 + o).
Proof.
(* WORKED IN CLASS *)
intros n H.
(* destruct消去原命题的存在量词,并引入原命题的存在量词修饰的变量,将其命名为m *)
destruct H as [m H].
exists (2 + m).
apply Hm. Qed.
上述例子中destruct
执行之前的证明过程上下文
n : nat
H : exists m : nat, n = 4 + m
______________________________________(1/1)
exists o : nat, n = 2 + o
上述例子中destruct
执行之后的证明过程上下文
n, m : nat
H : n = 4 + m
______________________________________(1/1)
exists o : nat, n = 2 + o
Programming with Propositions
Prop
类型和其他基本的数据类型一样,也可以被用作函数的参数,返回值以及执行一些运算(上述逻辑连接符就是作用于Prop
的一些基本的运算)。
相等
equality
和逻辑等价logical equivalence
存在一些相同的地方,例如均可以使用rewrite
替换,均可以使用reflexivity
进行证明,但相等可以作用于任何类型而不仅仅是Prop
, 逻辑等价仅能作用于Prop
即对于相等
=
,其类型为forall (A : Type), A -> A -> Prop
对于逻辑等价
<->
,其类型为Prop -> Prop -> Prop
利用函数和逻辑连接符,可以定义如下逻辑函数In
,该函数可以直接生成一个Prop
来表示x
包含在l
中
Fixpoint In {A : Type} (x : A) (l : list A) : Prop :=
match l with
| [] => False
| x' :: l' => x' = x \/ In x l'
end.
当然,我们也能定义返bool
类型的inb
达到类似的效果,不过inb
返回的是一个bool
类型的值,而不是一个Prop
,不能直接用于证明,需要通过=
号转化为Prop
才能用于证明(即inb x l = true
),而通过上述In
可以直接定义一个Prop
; 另外,由于我们仅定义了对于自然数nat
的返回值为bool
类型的相等判断函数=?
,因此,目前仅能处理元素类型为nat
的list
.
Fixpoint inb (x : nat) (l : list nat) : bool :=
match l with
| [] => false
| x' :: l' => orb (x' =? x) (inb x l')
end.
Applying Theorems to Arguments
定理和其证明过程本身可以被理解为一种函数,其中定理本身是函数原型,其证明过程是函数体,我们可以通过apply
来调用该定理将一个Prop
转换为另外一个Prop
。定理中也存在一些参数,这些参数可以在apply
的时候显式的指定(如果coq无法自动匹配正确的参数)。
下边的例子给出了一个定理以及其证明过程
Theorem in_not_nil :
forall A (x : A) (l : list A), In x l -> l <> [].
Proof.
intros A x l H. unfold not. intro Hl.
rewrite Hl in H.
simpl in H.
apply H.
Qed.
我们可以使用上述定理来证明其他的定理
(** Explicitly apply the lemma to the value for [x]. *)
Lemma in_not_nil_42_take4 :
forall l : list nat, In 42 l -> l <> [].
Proof.
intros l H.
(* 这里目标为l <> [], 我们尝试应用in_not_nil将其转换为In x l, 但coq无法确定x对应的证明过程
中的具体变量,我们可以使用apply in_not_nil with (x := 42)来指定x的具体值,也可以通过如下
方式来给in_not_nil提供具体参数*)
(* in_not_nil共有三个general的参数, A : Type, x : A, l : list A,给出这三个具体参数可以生成
一个针对具体值的定理In x l -> l <> []。这里按照顺序指定了前两个最后一个由coq根据当前目标
l <> []自动匹配证明上下文中的l)
apply (in_not_nil nat 42).
apply H.
Qed.
(** Explicitly apply the lemma to a hypothesis. *)
Lemma in_not_nil_42_take5 :
forall l : list nat, In 42 l -> l <> [].
Proof.
intros l H.
(* 这里目标为l <> [] *)
(* 这里为in_not_nil提供了三个参数(由coq指定自动推导)和一个假设前提,从而将假设H转换为一个另外
一个假设l <> [], 通过apply该假设即完成了证明 *)
apply (in_not_nil _ _ _ H).
Qed.
Tactics
left, right
用于证明使用\/
(逻辑或)连接的命题。详见[逻辑或(logical or)](#逻辑或(logical or))。
split
用于证明使用/\
(逻辑或)连接的命题。详见[逻辑与(logical and)](#逻辑与(logical and))。
exfalso
等同于对目标应用定理如下定理(apply ex_falso_quodlibet
),目标将转换为False
. 详见逻辑非。
Theorem ex_falso_quodlibet : forall (P:Prop),
False -> P.
Proof.
(* WORKED IN CLASS *)
intros P contra.
destruct contra. Qed.
exists
用于证明包含存在量词的定理。详见存在量词。
Command
Axiom
使用Axiom
可以定义一个公理(即无需证明被认为成立的定理),注意引入一个公理时必须要非常小心,因为引入的公理可能同已有的公理系统冲突导致整个逻辑体系出现问题。
Print Assumption
使用Print Assumption
可以打印一个被证明的定理中所依赖的额外公理(即使用Axiom
定义的公理)。
Check
对一个定理使用Check
命令可以查看定理的内容(不包括证明)
重要练习
使用尾递归实现的rev的正确性证明
Fixpoint rev_append {X} (l1 l2 : list X) : list X :=
match l1 with
| [] => l2
| x :: l1' => rev_append l1' (x :: l2)
end.
Definition tr_rev {X} (l : list X) : list X :=
rev_append l [].
(* 需要通过该引理来证明下述tr_rev_correct*)
Lemma rev_append_split : forall T (l l1 l2: list T), rev_append l (l1 ++ l2) = rev_append l l1 ++ l2.
Proof.
intros T l.
induction l as [|h t Hl].
-reflexivity.
-simpl. intros l1 l2. replace (h :: l1 ++ l2) with ((h :: l1) ++ l2).
--rewrite -> Hl. reflexivity.
--simpl. reflexivity.
Qed.
(* 上述引理不能定义为rev_append l1 l2 = rev_append l1 [] ++ l2,因为这个引理较为特定,其存在一个常量[],
从而导致其归纳假设过于特定,难以进行证明,我们需要证明上述更为general的定理 *)
(* 按照一般的过程进行证明,会卡在rev_append t [h] = rev_append t [] ++ [h],该目标不能直接证明,
必须要通过一个更具有一般性的引理来证明(上边的引理rev_append_split) *)
Theorem tr_rev_correct : forall X, @tr_rev X = @rev X.
Proof.
intros X. apply functional_extensionality. intros l.
induction l as [|h t Hl].
-simpl. reflexivity.
-simpl. rewrite <- Hl. unfold tr_rev. simpl. replace (rev_append t [h]) with (rev_append t ([] ++ [h])).
--rewrite -> rev_append_split. reflexivity.
--simpl. reflexivity.
Qed.
从上述证明过程可以看到,定义足够一般化的引理对于某些定理的证明是至关重要的。引理是否足够一般化,可以通过引理中特定值(常量)的数量来判断。
evenb_double_conv
Lemma evenb_double_conv : forall n, exists k,
n = if evenb n then double k else S (double k).
Proof.
intros n.
(* 对n进行归纳证明 *)
induction n as [|n' Hn].
-simpl. exists 0. simpl. reflexivity.
(* 使用定理evenb_S将evenb S n'转换为negb (evenb n') *)
-destruct Hn as [k Hn]. rewrite -> evenb_S. destruct (evenb n').
--simpl. exists k. f_equal. apply Hn.
(* 注意这里需要使用exists (S k)而不是exists k *)
--simpl. exists (S k). simpl. rewrite -> Hn. f_equal.
Qed.