IndProp归纳定义的命题
Set Warnings "-notation-overridden,-parsing".
From LF Require Export Logic.
Require Coq.omega.Omega.
归纳定义的命题
- 规则 ev_0: 0 是偶数。
- 规则 ev_SS: 如果 n 是偶数, 那么 S (S n) 也是偶数。
(ev_0) | |
ev 0 |
ev n | (ev_SS) |
ev (S (S n)) |
-------- (ev_0)
ev 0
-------- (ev_SS)
ev 2
-------- (ev_SS)
ev 4
偶数性的归纳定义
基于上述,可将偶数性质的定义翻译为在 Coq 中使用 Inductive 声明的定义, 声明中每一个构造子对应一个推断规则:
这个定义与之前 Inductive 定义的用法有一个有趣的区别:一方面,
我们定义的并不是一个 Type(如 nat),而是一个将 nat 映射到 Prop
的函数——即关于数的性质。然而真正要关注的是,由于 ev 中的 nat
参数出现在冒号'右侧',这允许在不同的构造子类型中使用不同的值:例如
ev_0 类型中的 0 以及 ev_SS 类型中的 S (S n)。与此相应,
每个构造子的类型必须在冒号后显式指定,并且对于某个自然数 n
来说,每个构造子的类型都必须有 ev n 的形式。
相反,回忆 list 的定义:
Inductive list (X:Type) : Type :=
| nil
| cons (x : X) (l : list X).
它以'全局的方式'在冒号'左侧'引入了参数 X,
强迫 nil 和 cons 的结果为同一个类型(list X)。
如果在定义 ev 时将 nat 置于冒号左侧,就会得到如下错误:
Inductive list (X:Type) : Type :=
| nil
| cons (x : X) (l : list X).
Fail Inductive wrong_ev (n : nat) : Prop :=
| wrong_ev_0 : wrong_ev 0
| wrong_ev_SS (H: wrong_ev n) : wrong_ev (S (S n)).
(* ===> Error: Last occurrence of "wrong_ev" must have "n"
as 1st argument in "wrong_ev 0". *)
在 Inductive 定义中,类型构造子冒号左侧的参数叫做形参(Parameter),
而右侧的叫做索引(Index)或注解(Annotation)。
例如,在 Inductive list (X : Type) := ... 中,X 是一个形参;而在
Inductive ev : nat → Prop := ... 中,未命名的 nat 参数是一个索引。
在 Coq 中,我们可以认为 ev 定义了一个性质 ev : nat → Prop,其包括
“证据构造子” ev_0 : ev 0 和 ev_SS : ∀ n, ev n → ev (S (S n))。
这些 “证据构造子” 等同于已经证明过的定理。
具体来说,我们可以使用 Coq 中的 apply 策略和规则名称来证明某个数的 ev 性质……
……或使用函数应用的语法:
我们同样可以对前提中使用到 ev 的定理进行证明。
Theorem ev_plus4 : ∀ n, ev n → ev (4 + n).
Proof.
intros n. simpl. intros Hn.
apply ev_SS. apply ev_SS. apply Hn.
Qed.
在证明中使用证据
- E 是 ev_0(且 n 为 O),或
- E 是 ev_SS n' E'(且 n 为 S (S n'),E' 为 ev n' 的证据).
对证据进行反演
Theorem ev_inversion :
∀ (n : nat), ev n →
(n = 0) ∨ (∃ n', n = S (S n') ∧ ev n').
Proof.
intros n E.
destruct E as [ | n' E'].
- (* E = ev_0 : ev 0 *)
left. reflexivity.
- (* E = ev_SS n' E' : ev (S (S n')) *)
right. ∃ n'. split. reflexivity. apply E'.
Qed.
用 destruct 解构证据即可证明下述定理:
Theorem ev_minus2 : ∀ n,
ev n → ev (pred (pred n)).
Proof.
intros n E.
destruct E as [| n' E'].
- (* E = ev_0 *) simpl. apply ev_0.
- (* E = ev_SS n' E' *) simpl. apply E'.
Qed.
However, this variation cannot easily be handled with just
destruct.
直观来说,我们知道支撑前提的证据不会由 ev_0 组成,因为 0 和 S 是
nat 类型不同的构造子;由此 ev_SS 是唯一需要应对的情况(译注:ev_0 无条件成立)。
不幸的是,destruct 并没有如此智能,它仍然为我们生成两个子目标。
更坏的是,于此同时最终目标没有改变,也无法为完成证明提供任何有用的信息。
Proof.
intros n E.
destruct E as [| n' E'] eqn:EE.
- (* E = ev_0. *)
(* 我们须证明 n 是偶数,但没有任何有用的假设信息可以使用! *)
Abort.
究竟发生了什么?应用 destruct 把性质的参数替换为对应于构造子的值。
这对于证明 ev_minus2' 是有帮助的,因为在最终目标中直接使用到了参数 n。
然而,这对于 evSS_ev 并没有帮助,因为被替换掉的 S (S n) 并没有在其他地方被使用。
If we remember that term S (S n), the proof goes
through. (We'll discuss remember in more detail below.)
Theorem evSS_ev_remember : ∀ n,
ev (S (S n)) → ev n.
Proof.
intros n H. remember (S (S n)) as k. destruct H as [|n' E'].
- (* E = ev_0 *)
(* Now we do have an assumption, in which k = S (S n) has been
rewritten as 0 = S (S n) by destruct. That assumption
gives us a contradiction. *)
discriminate Heqk.
- (* E = ev_S n' E' *)
(* This time k = S (S n) has been rewritten as S (S n') = S (S n). *)
injection Heqk as Heq. rewrite Heq in E'. apply E'.
Qed.
Alternatively, the proof is straightforward using our inversion
lemma.
Theorem evSS_ev : ∀ n, ev (S (S n)) → ev n.
Proof. intros n H. apply ev_inversion in H. destruct H.
- discriminate H.
- destruct H as [n' [Hnm Hev]]. injection Hnm as Heq.
rewrite Heq. apply Hev.
Qed.
Note how both proofs produce two subgoals, which correspond
to the two ways of proving ev. The first subgoal is a
contradiction that is discharged with discriminate. The second
subgoal makes use of injection and rewrite. Coq provides a
handy tactic called inversion that factors out that common
pattern.
The inversion tactic can detect (1) that the first case (n =
0) does not apply and (2) that the n' that appears in the
ev_SS case must be the same as n. It has an "as" variant
similar to destruct, allowing us to assign names rather than
have Coq choose them.
Theorem evSS_ev' : ∀ n,
ev (S (S n)) → ev n.
Proof.
intros n E.
inversion E as [| n' E' EQ].
(* We are in the E = ev_SS n' E' case now. *)
apply E'.
Qed.
The inversion tactic can apply the principle of explosion to
"obviously contradictory" hypotheses involving inductively defined
properties, something that takes a bit more work using our
inversion lemma. For example:
Theorem one_not_even : ¬ ev 1.
Proof.
intros H. apply ev_inversion in H.
destruct H as [ | [m [Hm _]]].
- discriminate H.
- discriminate Hm.
Qed.
Theorem one_not_even' : ¬ ev 1.
intros H. inversion H. Qed.
Theorem inversion_ex1 : ∀ (n m o : nat),
[n; m] = [o; o] →
[n] = [m].
Proof.
intros n m o H. inversion H. reflexivity. Qed.
Theorem inversion_ex2 : ∀ (n : nat),
S n = O →
2 + 2 = 5.
Proof.
intros n contra. inversion contra. Qed.
inversion 的工作原理大致如下:假设 H 指代上下文中的假设 P,
且 P 由 Inductive 归纳定义,则对于 P 每一种可能的构造,inversion H
各为其生成子目标。子目标中自相矛盾者被忽略,证明其余子命题即可得证原命题。
在证明子目标时,上下文中的 H 会替换为 P 的构造条件,
即其构造子所需参数以及必要的等式关系。例如:倘若 ev n 由 evSS 构造,
上下文中会引入参数 n'、ev n',以及等式 S (S n') = n。
上面的 ev_double 练习展示了偶数性质的一种新记法,其被之前的两种记法所蕴含。
(因为,由 Logic 一章中的 even_bool_prop,我们已经知道
他们是互相等价的。)
为了展示这三种方式的一致性,我们需要下面的引理:
我们可以尝试使用分类讨论或对 n 进行归纳。但由于 ev
在前提中出现,所以和前面章节的一些例子一样,这种策略行不通,
因为如前面提到的,归纳法则会讨论 n-1,而它并不是偶数!
如此我们似乎可以先试着对 ev 的证据进行反演。
确实,第一个分类可以被平凡地证明。
intros n E. inversion E as [EQ' | n' E' EQ'].
- (* E = ev_0 *)
∃ 0. reflexivity.
- (* E = ev_SS n' E' *) simpl.
然而第二个分类要困难一些。我们需要证明 ∃ k, S (S n') = double k,
但唯一可用的假设是 E',也即 ev n' 成立。但这对证明并没有帮助,
我们似乎被卡住了,而对 E 进行分类讨论是徒劳的。
如果仔细观察第二个(子)目标,我们可以发现一些有意思的事情:
对 E 进行分类讨论,我们可以把要证明的原始目标归约到另一个上,
其涉及到另一个 ev 的证据: E'。
形式化地说,我们可以通过展示如下证据来完成证明:
∃ k', n' = double k',
这同原始的命题是等价的,只是 n' 被替换为 n。确实,通过这个中间结果完成证明
并不困难。
∃ k', n' = double k',
assert (I : (∃ k', n' = double k') →
(∃ k, S (S n') = double k)).
{ intros [k' Hk']. rewrite Hk'. ∃ (S k'). reflexivity. }
apply I. (* 将原始目标归约到新目标上 *)
(* However, at this point we can go no further. *)
Abort.
对证据进行归纳
Lemma ev_even : ∀ n,
ev n → even n.
Proof.
intros n E.
induction E as [|n' E' IH].
- (* E = ev_0 *)
∃ 0. reflexivity.
- (* E = ev_SS n' E'
同时 IH : exists k', n' = double k' *)
destruct IH as [k' Hk'].
rewrite Hk'. ∃ (S k'). reflexivity.
Qed.
这里我们看到 Coq 对 E' 产生了 IH,而 E' 是唯一递归出现的
ev 命题。 由于 E' 中涉及到 n',这个归纳假设是关于 n' 的,
而非关于 n 或其他数字的。
关于偶数性质的第二个和第三个定义的等价关系如下:
Theorem ev_even_iff : ∀ n,
ev n ↔ even n.
Proof.
intros n. split.
- (* -> *) apply ev_even.
- (* <- *) intros [k Hk]. rewrite Hk. apply ev_double.
Qed.
我们会在后面的章节中看到,对证据进行归纳在很多领域里是一种常用的技术,
特别是在形式化程序语言的语义时,由于其中很多有趣的性质都是归纳定义的。
下面的练习提供了一些简单的例子,来帮助你熟悉这项技术。
练习:2 星, standard (ev_sum)
Inductive ev' : nat → Prop :=
| ev'_0 : ev' 0
| ev'_2 : ev' 2
| ev'_sum n m (Hn : ev' n) (Hm : ev' m) : ev' (n + m).
请证明这个定义在逻辑上等同于前述定义。为了精简证明,请使用 Logic
一章中将定理应用到参数的技术,注意同样的技术也可用于归纳定义的命题的构造子。
归纳关系
和命题一样,关系也可以归纳地定义。一个很有用的例子是整数的“小于等于”关系。
下面的定义应当是比较直观的。它提供了两种方法来描述一个数小于等于另一个数的证据:
要么可观察到两个数相等,或提供证据显示第一个数小于等于第二个数的前继。
Inductive le : nat → nat → Prop :=
| le_n (n : nat) : le n n
| le_S (n m : nat) (H : le n m) : le n (S m).
Notation "m <= n" := (le m n).
类似于证明 ev 这样的性质,使用 le_n 和 le_S 构造子来证明关于 ≤
的事实遵循了同样的模式。我们可以对构造子使用 apply 策略来证明 ≤ 目标
(比如证明 3<=3 或 3<=6),也可以使用 inversion 策略来从上下文中 ≤
的假设里抽取信息(比如证明 (2<=1) → 2+2=5)。
这里提供一些完备性检查。(请注意,尽管这同我们在开始课程时编写的
函数“单元测试”类似,但我们在这里必须明确地写下他们的证明——simpl 和
reflexivity 并不会有效果,因为这些证明不仅仅是对表达式进行简化。)
Theorem test_le1 :
3 ≤ 3.
Proof.
(* 课上已完成 *)
apply le_n. Qed.
Theorem test_le2 :
3 ≤ 6.
Proof.
(* 课上已完成 *)
apply le_S. apply le_S. apply le_S. apply le_n. Qed.
Theorem test_le3 :
(2 ≤ 1) → 2 + 2 = 5.
Proof.
(* 课上已完成 *)
intros H. inversion H. inversion H2. Qed.
现在“严格小于”关系 n < m 可以使用 le 来定义。
这里展示了一些定义于自然数上的关系:
Inductive square_of : nat → nat → Prop :=
| sq n : square_of n (n × n).
Inductive next_nat : nat → nat → Prop :=
| nn n : next_nat n (S n).
Inductive next_ev : nat → nat → Prop :=
| ne_1 n (H: ev (S n)) : next_ev n (S n)
| ne_2 n (H: ev (S (S n))) : next_ev n (S (S n)).
(* 请在此处解答 *)
☐
(* 请在此处解答 *)
☐
练习:3 星, standard, optional (le_exercises)
这里展示一些 ≤ 和 < 关系的事实,我们在接下来的课程中将会用到他们。 证明他们将会是非常有益的练习。Lemma le_trans : ∀ m n o, m ≤ n → n ≤ o → m ≤ o.
Proof.
(* 请在此处解答 *) Admitted.
Theorem O_le_n : ∀ n,
0 ≤ n.
Proof.
(* 请在此处解答 *) Admitted.
Theorem n_le_m__Sn_le_Sm : ∀ n m,
n ≤ m → S n ≤ S m.
Proof.
(* 请在此处解答 *) Admitted.
Theorem Sn_le_Sm__n_le_m : ∀ n m,
S n ≤ S m → n ≤ m.
Proof.
(* 请在此处解答 *) Admitted.
Theorem le_plus_l : ∀ a b,
a ≤ a + b.
Proof.
(* 请在此处解答 *) Admitted.
Theorem plus_le : ∀ n1 n2 m,
n1 + n2 ≤ m →
n1 ≤ m ∧ n2 ≤ m.
Proof.
(* 请在此处解答 *) Admitted.
Hint: the next one may be easiest to prove by induction on n.
Theorem add_le_cases : ∀ n m p q,
n + m ≤ p + q → n ≤ p ∨ m ≤ q.
Proof.
(* 请在此处解答 *) Admitted.
Theorem lt_S : ∀ n m,
n < m →
n < S m.
Proof.
(* 请在此处解答 *) Admitted.
Theorem plus_lt : ∀ n1 n2 m,
n1 + n2 < m →
n1 < m ∧ n2 < m.
Proof.
(* 请在此处解答 *) Admitted.
Theorem leb_complete : ∀ n m,
n <=? m = true → n ≤ m.
Proof.
(* 请在此处解答 *) Admitted.
提示:在下面的问题中,对 m 进行归纳会使证明容易一些。
提示:以下定理可以不使用 induction 而证明。
Theorem leb_true_trans : ∀ n m o,
n <=? m = true → m <=? o = true → n <=? o = true.
Proof.
(* 请在此处解答 *) Admitted.
☐
Inductive R : nat → nat → nat → Prop :=
| c1 : R 0 0 0
| c2 m n o (H : R m n o) : R (S m) n (S o)
| c3 m n o (H : R m n o) : R m (S n) (S o)
| c4 m n o (H : R (S m) (S n) (S (S o))) : R m n o
| c5 m n o (H : R m n o) : R n m o.
- 下列哪个命题是可以被证明的?
- R 1 1 2
- R 2 2 6
- 如果在 R 的定义中我们丢弃 c5 构造子,可被证明的集合会发生变化吗?
简要(一句话)解释你的答案。
- 如果在 R 的定义中我们丢弃 c4 构造子,可被证明的集合会发生变化吗? 简要(一句话)解释你的答案。
(* 请在此处解答 *)
(* 请勿修改下面这一行: *)
Definition manual_grade_for_R_provability : option (nat×string) := None.
☐
Definition fR : nat → nat → nat
(* 将本行替换成 ":= _你的_定义_ ." *). Admitted.
Theorem R_equiv_fR : ∀ m n o, R m n o ↔ fR m n = o.
Proof.
(* 请在此处解答 *) Admitted.
☐
练习:2 星, advanced (subsequence)
如果一个列表的所有元素以相同的顺序出现在另一个列表之中(但允许其中出现其他额外的元素), 我们把第一个列表称作第二个列表的'子序列'。 例如:[1;2;3]
[1;2;3]
[1;1;1;2;2;3]
[1;2;7;3]
[5;6;1;9;9;2;7;3;8]
[1;2]
[1;3]
[5;6;2;1;7;3;8].
- 在 list nat 上定一个归纳命题 subseq,其表达了子序列的涵义。
(提示:你需要三个分类。)
- 证明子序列的自反关系 subseq_refl,也即任何列表是它自身的子序列。
- 证明关系 subseq_app 对任意列表 l1,l2 和 l3,如果 l1 是 l2
的子序列,那么 l1 也是 l2 ++ l3 的子序列。
- (可选的,困难)证明子序列的传递关系 subseq_trans——也即,如果 l1 是 l2 的子序列,且 l2 是 l3 的子序列,那么 l1 是 l3 的子序列。 (提示:仔细选择进行归纳的项!)
Inductive subseq : list nat → list nat → Prop :=
(* 请在此处解答 *)
.
Theorem subseq_refl : ∀ (l : list nat), subseq l l.
Proof.
(* 请在此处解答 *) Admitted.
Theorem subseq_app : ∀ (l1 l2 l3 : list nat),
subseq l1 l2 →
subseq l1 (l2 ++ l3).
Proof.
(* 请在此处解答 *) Admitted.
Theorem subseq_trans : ∀ (l1 l2 l3 : list nat),
subseq l1 l2 →
subseq l2 l3 →
subseq l1 l3.
Proof.
(* 请在此处解答 *) Admitted.
☐
练习:2 星, standard, optional (R_provability2)
假设我们在 Coq 中有如下定义:Inductive R : nat → list nat → Prop :=
| c1 : R 0 []
| c2 n l (H: R n l) : R (S n) (n :: l)
| c3 n l (H: R (S n) l) : R n l.
- R 2 [1;0]
- R 1 [1;2;1;0]
- R 6 [3;2;1;0]
(* 请在此处解答 *)
☐
案例学习:正则表达式
Inductive reg_exp (T : Type) : Type :=
| EmptySet
| EmptyStr
| Char (t : T)
| App (r1 r2 : reg_exp T)
| Union (r1 r2 : reg_exp T)
| Star (r : reg_exp T).
Arguments EmptySet {T}.
Arguments EmptyStr {T}.
Arguments Char {T} _.
Arguments App {T} _ _.
Arguments Union {T} _ _.
Arguments Star {T} _.
请注意这个定义是'多态的':reg_exp T 中的正则表达式描述了字符串,
而其中的字符取自 T——也即,T 的元素构成的列表。
(同一般的实践略有不同,我们不要求类型 T 是有限的。由此可形成一些不同的正则表达式
理论,但对于我们在本章的目的而言并无不同。)
我们通过以下规则来构建正则表达式和字符串,这些规则定义了正则表达式何时匹配一个字符串:
容易把非形式化的定义翻译为使用 Inductive 的定义。我们用记法
s =~ re 表示,通过把该记法用 Reserved “预留”在 Inductive
定义之前,我们就能在定义中使用它!
- 表达式 EmptySet 不匹配任何字符串。
- 表达式 EmptyStr 匹配空字符串 [].
- 表达式 Char x 匹配单个字符构成的字符串 [x].
- 如果 re1 匹配 s1, 且 re2 匹配 s2, 那么 App re1 re2 匹配 s1 ++ s2.
- 如果 re1 和 re2 中至少一个匹配 s, 那么 Union re1 re2 匹配 s.
- 最后,如果我们写下某个字符串 s 作为一个字符串序列的连接
s = s_1 ++ ... ++ s_k,且表达式 re 匹配其中每一个字符串 s_i,那么
Star re 匹配 s。
Reserved Notation "s =~ re" (at level 80).
Inductive exp_match {T} : list T → reg_exp T → Prop :=
| MEmpty : [] =~ EmptyStr
| MChar x : [x] =~ (Char x)
| MApp s1 re1 s2 re2
(H1 : s1 =~ re1)
(H2 : s2 =~ re2)
: (s1 ++ s2) =~ (App re1 re2)
| MUnionL s1 re1 re2
(H1 : s1 =~ re1)
: s1 =~ (Union re1 re2)
| MUnionR re1 s2 re2
(H2 : s2 =~ re2)
: s2 =~ (Union re1 re2)
| MStar0 re : [] =~ (Star re)
| MStarApp s1 s2 re
(H1 : s1 =~ re)
(H2 : s2 =~ (Star re))
: (s1 ++ s2) =~ (Star re)
where "s =~ re" := (exp_match s re).
出于可读性的考虑,在此我们也展示使用推断规则表示的定义。
请注意这些规则不'完全'等同于之前给出的非形式化定义。
首先,我们并不需要一个规则来直接表述无字符串匹配 EmptySet;
我们做的仅仅是不囊括任何可能导致有字符串被 EmptySet 所匹配的规则。
(的确,归纳定义的语法并不'允许'我们表达类似的“否定规则”(negative rule))。
其次,非形式化定义中的 Union 和 Star 各自对应了两个构造子:
MUnionL / MUnionR,和 MStar0 / MStarApp。这在逻辑上等价于
原始的定义,但在 Coq 中这样更加方便,因为递归出现的 exp_match
是作为构造子的直接参数给定的,这在对证据进行归纳时更简单。
(练习 exp_match_ex1 和 exp_match_ex2 会要求你证明归纳定义中的构造子
和从非形式化规则的表述中提炼的规则确实是等价的。)
接下来我们对一些例子使用这些规则:
(MEmpty) | |
[] =~ EmptyStr |
(MChar) | |
[x] =~ Char x |
s1 =~ re1 s2 =~ re2 | (MApp) |
s1 ++ s2 =~ App re1 re2 |
s1 =~ re1 | (MUnionL) |
s1 =~ Union re1 re2 |
s2 =~ re2 | (MUnionR) |
s2 =~ Union re1 re2 |
(MStar0) | |
[] =~ Star re |
s1 =~ re s2 =~ Star re | (MStarApp) |
s1 ++ s2 =~ Star re |
(请注意,后一个例子对字符串 [1] 和 [2] 直接应用了 MApp。
由于目标的形式是 [1; 2] 而非 [1] ++ [2],Coq 并不知道如何分解这个字符串。)
使用 inversion,我们还可以证明某些字符串'不'匹配一个正则表达式:
我们可以定义一些辅助函数来简化正则表达式的书写。函数 reg_exp_of_list
接受一个列表做参数,并构造一个正则表达式来精确地匹配这个列表:
Fixpoint reg_exp_of_list {T} (l : list T) :=
match l with
| [] ⇒ EmptyStr
| x :: l' ⇒ App (Char x) (reg_exp_of_list l')
end.
Example reg_exp_ex4 : [1; 2; 3] =~ reg_exp_of_list [1; 2; 3].
我们还可以证明一些关于 exp_match 的性质。比如,下面的引理显示
任意一个匹配 re 的字符串 s 也匹配 Star re。
(请注意对 app_nil_r 的使用改变了目标,以此可匹配 MStarApp 所需要的形式。)
练习:3 星, standard (exp_match_ex1)
下面的引理显示从形式化的归纳定义中可以得到本章开始的非形式化匹配规则。Lemma empty_is_empty : ∀ T (s : list T),
¬ (s =~ EmptySet).
Proof.
(* 请在此处解答 *) Admitted.
Lemma MUnion' : ∀ T (s : list T) (re1 re2 : reg_exp T),
s =~ re1 ∨ s =~ re2 →
s =~ Union re1 re2.
Proof.
(* 请在此处解答 *) Admitted.
接下来的引理使用了 Poly 一章中出现的 fold 函数:
如果 ss : list (list T) 表示一个字符串序列 s1, ..., sn,那么
fold app ss [] 是将所有字符串连接的结果。
Lemma MStar' : ∀ T (ss : list (list T)) (re : reg_exp T),
(∀ s, In s ss → s =~ re) →
fold app ss [] =~ Star re.
Proof.
(* 请在此处解答 *) Admitted.
☐
Lemma reg_exp_of_list_spec : ∀ T (s1 s2 : list T),
s1 =~ reg_exp_of_list s2 ↔ s1 = s2.
Proof.
(* 请在此处解答 *) Admitted.
☐
Fixpoint re_chars {T} (re : reg_exp T) : list T :=
match re with
| EmptySet ⇒ []
| EmptyStr ⇒ []
| Char x ⇒ [x]
| App re1 re2 ⇒ re_chars re1 ++ re_chars re2
| Union re1 re2 ⇒ re_chars re1 ++ re_chars re2
| Star re ⇒ re_chars re
end.
接下来我们这样陈述此定理:
Theorem in_re_match : ∀ T (s : list T) (re : reg_exp T) (x : T),
s =~ re →
In x s →
In x (re_chars re).
Proof.
intros T s re x Hmatch Hin.
induction Hmatch
as [| x'
| s1 re1 s2 re2 Hmatch1 IH1 Hmatch2 IH2
| s1 re1 re2 Hmatch IH | re1 s2 re2 Hmatch IH
| re | s1 s2 re Hmatch1 IH1 Hmatch2 IH2].
(* 课上已完成 *)
- (* MEmpty *)
simpl in Hin. destruct Hin.
- (* MChar *)
simpl. simpl in Hin.
apply Hin.
- (* MApp *)
simpl.
Something interesting happens in the MApp case. We obtain
two induction hypotheses: One that applies when x occurs in
s1 (which matches re1), and a second one that applies when x
occurs in s2 (which matches re2).
simpl. rewrite In_app_iff in ×.
destruct Hin as [Hin | Hin].
+ (* In x s1 *)
left. apply (IH1 Hin).
+ (* In x s2 *)
right. apply (IH2 Hin).
- (* MUnionL *)
simpl. rewrite In_app_iff.
left. apply (IH Hin).
- (* MUnionR *)
simpl. rewrite In_app_iff.
right. apply (IH Hin).
- (* MStar0 *)
destruct Hin.
- (* MStarApp *)
simpl.
我们再次得到了两个归纳假设,它们表明了为什么我们需要对 exp_match
的证据而非 re 进行归纳:对后者的归纳仅仅提供匹配 re
的字符串的归纳假设,却无法允许我们对 In x s2 分类进行推理。
rewrite In_app_iff in Hin.
destruct Hin as [Hin | Hin].
+ (* In x s1 *)
apply (IH1 Hin).
+ (* In x s2 *)
apply (IH2 Hin).
Qed.
Fixpoint re_not_empty {T : Type} (re : reg_exp T) : bool
(* 将本行替换成 ":= _你的_定义_ ." *). Admitted.
Lemma re_not_empty_correct : ∀ T (re : reg_exp T),
(∃ s, s =~ re) ↔ re_not_empty re = true.
Proof.
(* 请在此处解答 *) Admitted.
☐
remember 策略
Lemma star_app: ∀ T (s1 s2 : list T) (re : reg_exp T),
s1 =~ Star re →
s2 =~ Star re →
s1 ++ s2 =~ Star re.
Proof.
intros T s1 s2 re H1.
仅仅对 H1 反演并不会对处理含有递归的分类有太多帮助。(尝试一下!)
因此我们需要对证据进行归纳!下面是一个朴素的尝试:
generalize dependent s2.
induction H1
as [|x'|s1 re1 s2' re2 Hmatch1 IH1 Hmatch2 IH2
|s1 re1 re2 Hmatch IH|re1 s2' re2 Hmatch IH
|re''|s1 s2' re'' Hmatch1 IH1 Hmatch2 IH2].
现在,尽管我们得到了七个分类(正由我们从 exp_match 的定义中期待的那样),
但 H1 还是丢失了一个非常重要的信息:s1 事实上匹配了某种形式的 Star re。
这意味着对于'全部'的七个构造子分类我们都需要给出证明,尽管除了其中两个(MStar0
和 MStarApp)之外,剩下的五个构造子分类是和前提矛盾的。
我们仍然可以在一些构造子上继续证明,比如 MEmpty……
- (* MEmpty *)
simpl. intros s2 H. apply H.
……但有一些分类我们却卡住了。比如,对于 MChar 我们需要证明
s2 =~ Char x' → x' :: s2 =~ Char x',
这显然是不可能完成的。
s2 =~ Char x' → x' :: s2 =~ Char x',
- (* MChar. *) intros s2 H. simpl. (* 卡住了…… *)
Abort.
问题是,只有当 Prop 的假设是完全一般的时候,对其使用 induction 的才会起作用,
也即,我们需要其所有的参数都是变量,而非更复杂的表达式,比如 Star re。
(由此,对证据使用 induction 的行为更像是没有 eqn: 的 destruct
而非 inversion。)
解决此问题的一种直接的方式是“手动推广”这个有问题的表达式,
即为此引理添加一个显式的等式:
Lemma star_app: ∀ T (s1 s2 : list T) (re re' : reg_exp T),
re' = Star re →
s1 =~ re' →
s2 =~ Star re →
s1 ++ s2 =~ Star re.
我们现在可以直接对证据进行归纳,因为第一个假设的参数已经足够一般,
这意味着我们可以通过反演当前上下文中的 re' = Star re 来消解掉多数分类。
这在 Coq 中是一种常用的技巧,因此 Coq 提供了策略来自动生成这种等式,
并且我们也不必改写定理的陈述。
Abort.
在 Coq 中调用 remember e as x 策略会(1)替换所有表达式 e 为变量 x,
(2)在当前上下文中添加一个等式 x = e。我们可以这样使用 remember
来证明上面的结果:
Lemma star_app: ∀ T (s1 s2 : list T) (re : reg_exp T),
s1 =~ Star re →
s2 =~ Star re →
s1 ++ s2 =~ Star re.
Proof.
intros T s1 s2 re H1.
remember (Star re) as re'.
我们现在有 Heqre' : re' = Star re.
generalize dependent s2.
induction H1
as [|x'|s1 re1 s2' re2 Hmatch1 IH1 Hmatch2 IH2
|s1 re1 re2 Hmatch IH|re1 s2' re2 Hmatch IH
|re''|s1 s2' re'' Hmatch1 IH1 Hmatch2 IH2].
Heqre' 与多数分类相互矛盾,因此我们可以直接结束这些分类。
- (* MEmpty *) discriminate.
- (* MChar *) discriminate.
- (* MApp *) discriminate.
- (* MUnionL *) discriminate.
- (* MUnionR *) discriminate.
值得注意的分类是 Star。请注意 MStarApp 分类的归纳假设 IH2
包含到一个额外的前提 Star re'' = Star re,这是由 remember
所添加的等式所产生的。
- (* MStar0 *)
injection Heqre'. intros Heqre'' s H. apply H.
- (* MStarApp *)
injection Heqre'. intros H0.
intros s2 H1. rewrite <- app_assoc.
apply MStarApp.
+ apply Hmatch1.
+ apply IH2.
× rewrite H0. reflexivity.
× apply H1.
Qed.
练习:4 星, standard, optional (exp_match_ex2)
Lemma MStar'' : ∀ T (s : list T) (re : reg_exp T),
s =~ Star re →
∃ ss : list (list T),
s = fold app ss []
∧ ∀ s', In s' ss → s' =~ re.
Proof.
(* 请在此处解答 *) Admitted.
☐
练习:5 星, advanced (weak_pumping)
正则表达式中一个非常有趣的定理叫做'泵引理(Pumping Lemma)', 非形式化地来讲,它陈述了任意某个足够长的字符串 s 若匹配一个正则表达式 re, 则可以被抽取(pumped)——将 s 的某个中间部分重复任意次产生的新字符串 仍然匹配 re。 (为了简单起见,我们考虑一个比自动机理论课上陈述的定理稍微弱一点的定理。)Module Pumping.
Fixpoint pumping_constant {T} (re : reg_exp T) : nat :=
match re with
| EmptySet ⇒ 1
| EmptyStr ⇒ 1
| Char _ ⇒ 2
| App re1 re2 ⇒
pumping_constant re1 + pumping_constant re2
| Union re1 re2 ⇒
pumping_constant re1 + pumping_constant re2
| Star r ⇒ pumping_constant r
end.
在证明后面的泵引理时,你会发现关于抽取常量的引理十分有用。
Lemma pumping_constant_ge_1 :
∀ T (re : reg_exp T),
pumping_constant re ≥ 1.
Proof.
intros T re. induction re.
- (* Emptyset *)
apply le_n.
- (* EmptyStr *)
apply le_n.
- (* Char *)
apply le_S. apply le_n.
- (* App *)
simpl.
apply le_trans with (n:=pumping_constant re1).
apply IHre1. apply le_plus_l.
- (* Union *)
simpl.
apply le_trans with (n:=pumping_constant re1).
apply IHre1. apply le_plus_l.
- (* Star *)
simpl. apply IHre.
Qed.
intros T re. induction re.
- (* Emptyset *)
apply le_n.
- (* EmptyStr *)
apply le_n.
- (* Char *)
apply le_S. apply le_n.
- (* App *)
simpl.
apply le_trans with (n:=pumping_constant re1).
apply IHre1. apply le_plus_l.
- (* Union *)
simpl.
apply le_trans with (n:=pumping_constant re1).
apply IHre1. apply le_plus_l.
- (* Star *)
simpl. apply IHre.
Qed.
Lemma pumping_constant_0_false :
∀ T (re : reg_exp T),
pumping_constant re = 0 → False.
Proof.
intros T re H.
assert (Hp1 : pumping_constant re ≥ 1).
{ apply pumping_constant_ge_1. }
inversion Hp1 as [Hp1'| p Hp1' Hp1''].
- rewrite H in Hp1'. discriminate Hp1'.
- rewrite H in Hp1''. discriminate Hp1''.
Qed.
intros T re H.
assert (Hp1 : pumping_constant re ≥ 1).
{ apply pumping_constant_ge_1. }
inversion Hp1 as [Hp1'| p Hp1' Hp1''].
- rewrite H in Hp1'. discriminate Hp1'.
- rewrite H in Hp1''. discriminate Hp1''.
Qed.
接下来,定义辅助函数 napp 来重复(连接到它自己)一个字符串特定次数。
Fixpoint napp {T} (n : nat) (l : list T) : list T :=
match n with
| 0 ⇒ []
| S n' ⇒ l ++ napp n' l
end.
这个辅助引理在你证明泵引理时也非常有用。
Lemma napp_plus: ∀ T (n m : nat) (l : list T),
napp (n + m) l = napp n l ++ napp m l.
Proof.
intros T n m l.
induction n as [|n IHn].
- reflexivity.
- simpl. rewrite IHn, app_assoc. reflexivity.
Qed.
intros T n m l.
induction n as [|n IHn].
- reflexivity.
- simpl. rewrite IHn, app_assoc. reflexivity.
Qed.
Lemma napp_star :
∀ T m s1 s2 (re : reg_exp T),
s1 =~ re → s2 =~ Star re →
napp m s1 ++ s2 =~ Star re.
(弱化的)泵引理是说,如果 s =~ re 且 s 的长度最小是 re 的抽取常数(pumping constant),
那么 s 可分割成三个子字符串 s1 ++ s2 ++ s3,其中 s2 可被重复任意次,
其结果同 s1 和 s3 合并后仍然匹配 re。由于 s2 必须为非空字符串,
这是一种(构造性的)方式来以我们想要的长度生成匹配 re 的字符串。
Lemma weak_pumping : ∀ T (re : reg_exp T) s,
s =~ re →
pumping_constant re ≤ length s →
∃ s1 s2 s3,
s = s1 ++ s2 ++ s3 ∧
s2 ≠ [] ∧
∀ m, s1 ++ napp m s2 ++ s3 =~ re.
You are to fill in the proof. Several of the lemmas about
le that were in an optional exercise earlier in this chapter
may be useful.
Proof.
intros T re s Hmatch.
induction Hmatch
as [ | x | s1 re1 s2 re2 Hmatch1 IH1 Hmatch2 IH2
| s1 re1 re2 Hmatch IH | re1 s2 re2 Hmatch IH
| re | s1 s2 re Hmatch1 IH1 Hmatch2 IH2 ].
- (* MEmpty *)
simpl. intros contra. inversion contra.
(* 请在此处解答 *) Admitted.
☐
intros T re s Hmatch.
induction Hmatch
as [ | x | s1 re1 s2 re2 Hmatch1 IH1 Hmatch2 IH2
| s1 re1 re2 Hmatch IH | re1 s2 re2 Hmatch IH
| re | s1 s2 re Hmatch1 IH1 Hmatch2 IH2 ].
- (* MEmpty *)
simpl. intros contra. inversion contra.
(* 请在此处解答 *) Admitted.
☐
练习:5 星, advanced, optional (pumping)
Now here is the usual version of the pumping lemma. In addition to requiring that s2 ≠ [], it also requires that length s1 + length s2 ≤ pumping_constant re.Lemma pumping : ∀ T (re : reg_exp T) s,
s =~ re →
pumping_constant re ≤ length s →
∃ s1 s2 s3,
s = s1 ++ s2 ++ s3 ∧
s2 ≠ [] ∧
length s1 + length s2 ≤ pumping_constant re ∧
∀ m, s1 ++ napp m s2 ++ s3 =~ re.
You may want to copy your proof of weak_pumping below.
Proof.
intros T re s Hmatch.
induction Hmatch
as [ | x | s1 re1 s2 re2 Hmatch1 IH1 Hmatch2 IH2
| s1 re1 re2 Hmatch IH | re1 s2 re2 Hmatch IH
| re | s1 s2 re Hmatch1 IH1 Hmatch2 IH2 ].
- (* MEmpty *)
simpl. intros contra. inversion contra.
(* 请在此处解答 *) Admitted.
End Pumping.
☐
intros T re s Hmatch.
induction Hmatch
as [ | x | s1 re1 s2 re2 Hmatch1 IH1 Hmatch2 IH2
| s1 re1 re2 Hmatch IH | re1 s2 re2 Hmatch IH
| re | s1 s2 re Hmatch1 IH1 Hmatch2 IH2 ].
- (* MEmpty *)
simpl. intros contra. inversion contra.
(* 请在此处解答 *) Admitted.
End Pumping.
☐
Theorem filter_not_empty_In : ∀ n l,
filter (fun x ⇒ n =? x) l ≠ [] →
In n l.
Proof.
intros n l. induction l as [|m l' IHl'].
- (* l = *)
simpl. intros H. apply H. reflexivity.
- (* l = m :: l' *)
simpl. destruct (n =? m) eqn:H.
+ (* n =? m = true *)
intros _. rewrite eqb_eq in H. rewrite H.
left. reflexivity.
+ (* n =? m = false *)
intros H'. right. apply IHl'. apply H'.
Qed.
在 destruct 后的第一个分支中,我们解构 n =? m
后生成的等式显式地使用了 eqb_eq 引理,以此将假设
n =? m 转换为假设 n = m;接着使用 rewrite
策略和这个假设来完成此分支的证明。
为了简化这样的证明,我们可定义一个归纳命题,用于对 n =? m
产生更好的分类讨论原理。
它不会生成类似 (n =? m) = true这样的等式,因为一般来说对证明并不直接有用,
其生成的分类讨论原理正是我们所需要的假设: n = m。
Inductive reflect (P : Prop) : bool → Prop :=
| ReflectT (H : P) : reflect P true
| ReflectF (H : ¬ P) : reflect P false.
性质 reflect 接受两个参数:一个命题 P 和一个布尔值 b。
直观地讲,它陈述了性质 P 在布尔值 b 中所'映现'(也即,等价):
换句话说,P 成立当且仅当 b = true。为了理解这一点,请注意定义,
我们能够产生 reflect P true 的证据的唯一方式是证明 P 为真并使用
ReflectT 构造子。如果我们反转这个陈述,意味着从 reflect P true
的证明中抽取出 P 的证据也是可能的。与此类似,证明 reflect P false
的唯一方式是合并 ¬ P 的证据和 ReflectF 构造子。
形式化这种直觉并证明 P ↔ b = true 和 reflect P b
这两个表述确实等价是十分容易的。首先是从左到右的蕴含:
Theorem iff_reflect : ∀ P b, (P ↔ b = true) → reflect P b.
Proof.
(* 课上已完成 *)
intros P b H. destruct b.
- apply ReflectT. rewrite H. reflexivity.
- apply ReflectF. rewrite H. intros H'. discriminate.
Qed.
Lemma eqbP : ∀ n m, reflect (n = m) (n =? m).
Proof.
intros n m. apply iff_reflect. rewrite eqb_eq. reflexivity.
Qed.
filter_not_empty_In 的一种更流畅证明如下所示。请注意对 destruct 和 rewrite
的使用是如何合并成一个 destruct 的使用。
(为了更清晰地看到这点,使用 Coq 查看 filter_not_empty_In
的两个证明,并观察在 destruct 的第一个分类开始时证明状态的区别。)
Theorem filter_not_empty_In' : ∀ n l,
filter (fun x ⇒ n =? x) l ≠ [] →
In n l.
Proof.
intros n l. induction l as [|m l' IHl'].
- (* l = *)
simpl. intros H. apply H. reflexivity.
- (* l = m :: l' *)
simpl. destruct (eqbP n m) as [H | H].
+ (* n = m *)
intros _. rewrite H. left. reflexivity.
+ (* n <> m *)
intros H'. right. apply IHl'. apply H'.
Qed.
Fixpoint count n l :=
match l with
| [] ⇒ 0
| m :: l' ⇒ (if n =? m then 1 else 0) + count n l'
end.
Theorem eqbP_practice : ∀ n l,
count n l = 0 → ~(In n l).
Proof.
(* 请在此处解答 *) Admitted.
☐
额外练习
练习:3 星, standard, recommended (nostutter_defn)
写出性质的归纳定义是本课程中你需要的重要技能。请尝试去独立解决以下的练习。
请确保以下测试成功,但如果你觉得我们建议的证明(在注释中)并不有效,也可随意更改他们。
若你的定义与我们的不同,也可能仍然是正确的,但在这种情况下可能需要不同的证明。
(你会注意到建议的证明中使用了一些我们尚未讨论过的策略,这可以让证明适用于不同的 nostutter
定义方式。你可以取消注释并直接使用他们,也可以用基础的策略证明这些例子。)
Example test_nostutter_1: nostutter [3;1;4;1;5;6].
(* 请在此处解答 *) Admitted.
(*
Proof. repeat constructor; apply eqb_neq; auto.
Qed.
*)
Example test_nostutter_2: nostutter (@nil nat).
(* 请在此处解答 *) Admitted.
(*
Proof. repeat constructor; apply eqb_neq; auto.
Qed.
*)
Example test_nostutter_3: nostutter [5].
(* 请在此处解答 *) Admitted.
(*
Proof. repeat constructor; apply eqb_false; auto. Qed.
*)
Example test_nostutter_4: not (nostutter [3;1;1;4]).
(* 请在此处解答 *) Admitted.
(*
Proof. intro.
repeat match goal with
h: nostutter _ ⊢ _ => inversion h; clear h; subst
end.
contradiction; auto. Qed.
*)
(* 请勿修改下面这一行: *)
Definition manual_grade_for_nostutter : option (nat×string) := None.
☐
练习:4 星, advanced (filter_challenge)
让我们证明在 Poly 一章中 filter 的定义匹配某个抽象的规范。 可以这样非形式化地描述这个规范:[1;4;6;2;3]
[1;6;2]
[4;3].
(* 请在此处解答 *)
(* 请勿修改下面这一行: *)
Definition manual_grade_for_filter_challenge : option (nat×string) := None.
☐
练习:5 星, advanced, optional (filter_challenge_2)
另一种刻画 filter 行为的方式是:在 l 的所有其元素满足 test 的子序列中, filter test l 是最长的那个。请形式化这个命题并证明它。(* 请在此处解答 *)
☐
练习:4 星, standard, optional (palindromes)
回文是倒序排列与正序排列相同的序列。- 在 listX 上定义一个归纳命题 pal 来表达回文的含义。
(提示:你需要三个分类。定义应当基于列表的结构;仅仅使用一个构造子,例如
c : ∀ l, l = rev l → pal l - 证明(pal_app_rev)
∀ l, pal (l ++ rev l). - 证明(pal_rev that)
∀ l, pal l → l = rev l.
(* 请在此处解答 *)
(* 请勿修改下面这一行: *)
Definition manual_grade_for_pal_pal_app_rev_pal_rev : option (nat×string) := None.
☐
练习:5 星, standard, optional (palindrome_converse)
由于缺乏证据,反方向的证明要困难许多。使用之前练习中定义的 pal 来证明∀ l, l = rev l → pal l.
(* 请在此处解答 *)
☐
(* Fixpoint In (A : Type) (x : A) (l : list A) : Prop :=
match l with
| => False
| x' :: l' => x' = x \/ In A x l'
end *)
你的第一个任务是使用 In 来定义命题 disjoint X l1 l2:仅当列表 l1
和 l2(元素的类型为 X)不含有相同的元素时其可被证明。
(* 请在此处解答 *)
接下来,使用 In 定义归纳命题 NoDup X l,其可被证明仅当列表 l
(元素类型为 X)的每个元素都不相同。比如,NoDup nat [1;2;3;4]
和 NoDup bool [] 是可被证明的,然而 NoDup nat [1;2;1]
和 NoDup bool [true;true] 是不行的。
(* 请在此处解答 *)
最后,使用 disjoint,NoDup 和 ++(列表连接)陈述并证明一个或多个有趣的定理。
(* 请在此处解答 *)
(* 请勿修改下面这一行: *)
Definition manual_grade_for_NoDup_disjoint_etc : option (nat×string) := None.
☐
练习:4 星, advanced, optional (pigeonhole_principle)
_鸽笼原理(Pigeonhole Principle)'是一个关于计数的基本事实: 将超过 n 个物体放进 n 个鸽笼,则必有鸽笼包含至少两个物体。 与此前诸多情形相似,这一数学事实看似乏味,但其证明手段并不平凡, 如下所述:Lemma in_split : ∀ (X:Type) (x:X) (l:list X),
In x l →
∃ l1 l2, l = l1 ++ x :: l2.
Proof.
(* 请在此处解答 *) Admitted.
现在请定一个性质 repeats,使 repeats X l 断言 l
包含至少一个(类型为 X 的)重复的元素。
现在,我们这样来形式化鸽笼原理。假设列表 l2 表示鸽笼标签的列表,列表 l1
表示标签被指定给一个列表里的元素。如果元素的个数多于标签的个数,那么至少有两个
元素被指定了同一个标签——也即,列表 l1 含有重复元素。
如果使用 excluded_middule 假设并展示 In 是可判定的(decidable),
即 ∀ x l, (In x l) ∨ ¬ (In x l),那么这个证明会容易很多。
然而,若'不'假设 In 的可判定性也同样可以证明它;在这样的情况下便不必使用
excluded_middle 假设。
Theorem pigeonhole_principle: ∀ (X:Type) (l1 l2:list X),
excluded_middle →
(∀ x, In x l1 → In x l2) →
length l2 < length l1 →
repeats l1.
Proof.
intros X l1. induction l1 as [|x l1' IHl1'].
(* 请在此处解答 *) Admitted.
(* GRADE_MANUAL 1.5: check_repeats *)
☐
扩展练习:经验证的正则表达式匹配器
Coq 标准库中包含了一个不同的 ASCII 字符串的归纳定义。然而,为了应用
之前定义的匹配关系,我们在此使用刚刚给出的 ASCII 字符列表作为定义。
我们也可以定义工作在多态列表上的正则表达式匹配器,而非特定于 ASCII 字符列表。
我们将要实现的匹配算法需要知道如何对列表中的元素判断相等,因此需要给定一个
相等关系测试函数。一般化我们给出的定义、定理和证明有一点枯燥,但是可行的。
正则表达式匹配器的正确性证明会由匹配函数的性质和 match 关系的性质组成,
match 关系并不依赖匹配函数。我们将会首先证明后一类性质。他们中的多数
将会是很直接的证明,已经被直接给出;少部分关键的引理会留给你来证明。
每个可被证明的 Prop 等价于 True。
Lemma provable_equiv_true : ∀ (P : Prop), P → (P ↔ True).
Proof.
intros.
split.
- intros. constructor.
- intros _. apply H.
Qed.
Proof.
intros.
split.
- intros. constructor.
- intros _. apply H.
Qed.
其逆可被证明的 Prop 等价于 False。
Lemma not_equiv_false : ∀ (P : Prop), ¬P → (P ↔ False).
Proof.
intros.
split.
- apply H.
- intros. destruct H0.
Qed.
Proof.
intros.
split.
- apply H.
- intros. destruct H0.
Qed.
EmptySet 不匹配字符串。
Lemma null_matches_none : ∀ (s : string), (s =~ EmptySet) ↔ False.
Proof.
intros.
apply not_equiv_false.
unfold not. intros. inversion H.
Qed.
Proof.
intros.
apply not_equiv_false.
unfold not. intros. inversion H.
Qed.
EmptyStr 仅匹配空字符串。
Lemma empty_matches_eps : ∀ (s : string), s =~ EmptyStr ↔ s = [ ].
Proof.
split.
- intros. inversion H. reflexivity.
- intros. rewrite H. apply MEmpty.
Qed.
Proof.
split.
- intros. inversion H. reflexivity.
- intros. rewrite H. apply MEmpty.
Qed.
EmptyStr 不匹配非空字符串。
Lemma empty_nomatch_ne : ∀ (a : ascii) s, (a :: s =~ EmptyStr) ↔ False.
Proof.
intros.
apply not_equiv_false.
unfold not. intros. inversion H.
Qed.
Proof.
intros.
apply not_equiv_false.
unfold not. intros. inversion H.
Qed.
Char a 不匹配不以 a 字符开始的字符串。
Lemma char_nomatch_char :
∀ (a b : ascii) s, b ≠ a → (b :: s =~ Char a ↔ False).
Proof.
intros.
apply not_equiv_false.
unfold not.
intros.
apply H.
inversion H0.
reflexivity.
Qed.
∀ (a b : ascii) s, b ≠ a → (b :: s =~ Char a ↔ False).
Proof.
intros.
apply not_equiv_false.
unfold not.
intros.
apply H.
inversion H0.
reflexivity.
Qed.
如果 Char a 匹配一个非空字符串,那么这个字符串的尾(tail)为空。
Lemma char_eps_suffix : ∀ (a : ascii) s, a :: s =~ Char a ↔ s = [ ].
Proof.
split.
- intros. inversion H. reflexivity.
- intros. rewrite H. apply MChar.
Qed.
Proof.
split.
- intros. inversion H. reflexivity.
- intros. rewrite H. apply MChar.
Qed.
App re0 re1 匹配字符串 s 当且仅当 s = s0 ++ s1,其中 s0
匹配 re0 且 s1 匹配 re1。
Lemma app_exists : ∀ (s : string) re0 re1,
s =~ App re0 re1 ↔
∃ s0 s1, s = s0 ++ s1 ∧ s0 =~ re0 ∧ s1 =~ re1.
Proof.
intros.
split.
- intros. inversion H. ∃ s1, s2. split.
× reflexivity.
× split. apply H3. apply H4.
- intros [ s0 [ s1 [ Happ [ Hmat0 Hmat1 ] ] ] ].
rewrite Happ. apply (MApp s0 _ s1 _ Hmat0 Hmat1).
Qed.
s =~ App re0 re1 ↔
∃ s0 s1, s = s0 ++ s1 ∧ s0 =~ re0 ∧ s1 =~ re1.
Proof.
intros.
split.
- intros. inversion H. ∃ s1, s2. split.
× reflexivity.
× split. apply H3. apply H4.
- intros [ s0 [ s1 [ Happ [ Hmat0 Hmat1 ] ] ] ].
rewrite Happ. apply (MApp s0 _ s1 _ Hmat0 Hmat1).
Qed.
练习:3 星, standard, optional (app_ne)
App re0 re1 匹配 a::s 当且仅当 re0 匹配空字符串 且 a::s 匹配 re1 或 s=s0++s1,其中 a::s0 匹配 re0 且 s1 匹配 re1。
Lemma app_ne : ∀ (a : ascii) s re0 re1,
a :: s =~ (App re0 re1) ↔
([ ] =~ re0 ∧ a :: s =~ re1) ∨
∃ s0 s1, s = s0 ++ s1 ∧ a :: s0 =~ re0 ∧ s1 =~ re1.
Proof.
(* 请在此处解答 *) Admitted.
☐
a :: s =~ (App re0 re1) ↔
([ ] =~ re0 ∧ a :: s =~ re1) ∨
∃ s0 s1, s = s0 ++ s1 ∧ a :: s0 =~ re0 ∧ s1 =~ re1.
Proof.
(* 请在此处解答 *) Admitted.
☐
Lemma union_disj : ∀ (s : string) re0 re1,
s =~ Union re0 re1 ↔ s =~ re0 ∨ s =~ re1.
Proof.
intros. split.
- intros. inversion H.
+ left. apply H2.
+ right. apply H1.
- intros [ H | H ].
+ apply MUnionL. apply H.
+ apply MUnionR. apply H.
Qed.
s =~ Union re0 re1 ↔ s =~ re0 ∨ s =~ re1.
Proof.
intros. split.
- intros. inversion H.
+ left. apply H2.
+ right. apply H1.
- intros [ H | H ].
+ apply MUnionL. apply H.
+ apply MUnionR. apply H.
Qed.
练习:3 星, standard, optional (star_ne)
a::s 匹配 Star re 当且仅当 s = s0 ++ s1,其中 a::s0 匹配 re 且 s1 匹配 Star re。 同 app_ne一样,这个观察很重要, 因此理解,证明并留意它。Lemma star_ne : ∀ (a : ascii) s re,
a :: s =~ Star re ↔
∃ s0 s1, s = s0 ++ s1 ∧ a :: s0 =~ re ∧ s1 =~ Star re.
Proof.
(* 请在此处解答 *) Admitted.
☐
练习:3 星, standard, optional (match_eps_refl)
现在,请证明 match_eps 确实测试了给定的正则表达式是否匹配空字符串。 (提示:你会使用到互映引理 ReflectT 和 ReflectF。)
我们匹配器所进行的关键操作是迭代地构造一个正则表达式生成式的序列。
对于字符 a 和正则表达式 re,re 在 a 上的生成式是一个正则表达式,
其匹配所有匹配 re 且以 a 开始的字符串的后缀。也即,re'
是 re 在 a 上的一个生成式如果他们满足以下关系:
函数 d 生成字符串如果对于给定的字符 a 和正则表达式 re,
它求值为 re 在 a 上的生成式。也即,d 满足以下关系:
Fixpoint derive (a : ascii) (re : reg_exp ascii) : reg_exp ascii
(* 将本行替换成 ":= _你的_定义_ ." *). Admitted.
☐
(* 将本行替换成 ":= _你的_定义_ ." *). Admitted.
☐
"c" =~ EmptySet:
"c" =~ Char c:
"c" =~ Char d:
"c" =~ App (Char c) EmptyStr:
Example test_der3 : match_eps (derive c (App (Char c) EmptyStr)) = true.
Proof.
(* 请在此处解答 *) Admitted.
Proof.
(* 请在此处解答 *) Admitted.
"c" =~ App EmptyStr (Char c):
Example test_der4 : match_eps (derive c (App EmptyStr (Char c))) = true.
Proof.
(* 请在此处解答 *) Admitted.
Proof.
(* 请在此处解答 *) Admitted.
"c" =~ Star c:
"cd" =~ App (Char c) (Char d):
Example test_der6 :
match_eps (derive d (derive c (App (Char c) (Char d)))) = true.
Proof.
(* 请在此处解答 *) Admitted.
match_eps (derive d (derive c (App (Char c) (Char d)))) = true.
Proof.
(* 请在此处解答 *) Admitted.
"cd" =~ App (Char d) (Char c):
Example test_der7 :
match_eps (derive d (derive c (App (Char d) (Char c)))) = false.
Proof.
(* 请在此处解答 *) Admitted.
match_eps (derive d (derive c (App (Char d) (Char c)))) = false.
Proof.
(* 请在此处解答 *) Admitted.
练习:4 星, standard, optional (derive_corr)
请证明 derive 确实总是会生成字符串。
函数 m 匹配正则表达式如果对给定的字符串 s 和正则表达式 re,
它求值的结果映射了 s 是否被 re 匹配。也即,m 满足以下性质:
Fixpoint regex_match (s : string) (re : reg_exp ascii) : bool
(* 将本行替换成 ":= _你的_定义_ ." *). Admitted.
☐
(* 将本行替换成 ":= _你的_定义_ ." *). Admitted.
☐
练习:3 星, standard, optional (regex_refl)
最后,证明 regex_match 确实可以匹配正则表达式。(* 2022-03-14 05:26:57 (UTC+00) *)