Imp简单的指令式程序
Z ::= X;;
Y ::= 1;;
WHILE ~(Z = 0) DO
Y ::= Y × Z;;
Z ::= Z - 1
END
Set Warnings "-notation-overridden,-parsing".
From Coq Require Import Bool.Bool.
From Coq Require Import Init.Nat.
From Coq Require Import Arith.Arith.
From Coq Require Import Arith.EqNat.
From Coq Require Import omega.Omega.
From Coq Require Import Lists.List.
From Coq Require Import Strings.String.
Import ListNotations.
From LF Require Import Maps.
以下两个定义指定了算术和布尔表达式的'抽象语法(Abstract Syntax)'。
Inductive aexp : Type :=
| ANum (n : nat)
| APlus (a1 a2 : aexp)
| AMinus (a1 a2 : aexp)
| AMult (a1 a2 : aexp).
Inductive bexp : Type :=
| BTrue
| BFalse
| BEq (a1 a2 : aexp)
| BLe (a1 a2 : aexp)
| BNot (b : bexp)
| BAnd (b1 b2 : bexp).
在本章中,我们省略了大部分从程序员实际编写的具体语法到其抽象语法树的翻译
例如,它会将字符串 "1 + 2 × 3" 翻译成如下 AST:
APlus (ANum 1) (AMult (ANum 2) (ANum 3)).
可选的章节 ImpParser 中开发了一个简单的词法分析器和解析器,
它可以进行这种翻译。你'无需'通过理解该章来理解本章,
但如果你没有上过涵盖这些技术的课程(例如编译器课程),可能想要略读一下该章节。
作为对比,下面是用约定的 BNF(巴克斯-诺尔范式)文法定义的同样的抽象语法:
a ::= nat
| a + a
| a - a
| a × a
b ::= true
| false
| a = a
| a ≤ a
| ¬b
| b && b
与前面的 Coq 版本相对比...
适应这两种记法都很有必要:非形式化的用语人类之间的交流,
而形式化的则用于实现和证明。
APlus (ANum 1) (AMult (ANum 2) (ANum 3)).
a ::= nat
| a + a
| a - a
| a × a
b ::= true
| false
| a = a
| a ≤ a
| ¬b
| b && b
- BNF 是非形式化的 -- 例如,它给出了表达式表面上的语法的建议
(例如加法运算符写作中缀的 +),而没有指定词法分析和解析的其它方面
(如 +、- 和 × 的相对优先级,用括号来明确子表达式的分组等)。
在实现编译器时,需要一些附加的信息(以及人类的智慧)
才能将此描述转换成形式化的定义。
- 反之,BNF 版本则更加清晰易读。它的非形式化使其更加灵活,
在讨论和在黑板上书写时,它有很大的优势,
此时传达一般的概念要比精确定下所有细节更加重要。
Fixpoint aeval (a : aexp) : nat :=
match a with
| ANum n ⇒ n
| APlus a1 a2 ⇒ (aeval a1) + (aeval a2)
| AMinus a1 a2 ⇒ (aeval a1) - (aeval a2)
| AMult a1 a2 ⇒ (aeval a1) × (aeval a2)
end.
Example test_aeval1:
aeval (APlus (ANum 2) (ANum 2)) = 4.
Proof. reflexivity. Qed.
同样,对布尔表达式求值会得到布尔值。
Fixpoint beval (b : bexp) : bool :=
match b with
| BTrue ⇒ true
| BFalse ⇒ false
| BEq a1 a2 ⇒ (aeval a1) =? (aeval a2)
| BLe a1 a2 ⇒ (aeval a1) <=? (aeval a2)
| BNot b1 ⇒ negb (beval b1)
| BAnd b1 b2 ⇒ andb (beval b1) (beval b2)
end.
优化
Fixpoint optimize_0plus (a:aexp) : aexp :=
match a with
| ANum n ⇒ ANum n
| APlus (ANum 0) e2 ⇒ optimize_0plus e2
| APlus e1 e2 ⇒ APlus (optimize_0plus e1) (optimize_0plus e2)
| AMinus e1 e2 ⇒ AMinus (optimize_0plus e1) (optimize_0plus e2)
| AMult e1 e2 ⇒ AMult (optimize_0plus e1) (optimize_0plus e2)
end.
要保证我们的优化是正确的,可以在某些示例中测试它并观察其输出出否正确。
Example test_optimize_0plus:
optimize_0plus (APlus (ANum 2)
(APlus (ANum 0)
(APlus (ANum 0) (ANum 1))))
= APlus (ANum 2) (ANum 1).
Proof. reflexivity. Qed.
但如果要确保该优化的正确性,即优化后的表达式与原表达式的求值结果相同,
那么我们应当证明它。
Theorem optimize_0plus_sound: ∀ a,
aeval (optimize_0plus a) = aeval a.
Proof.
intros a. induction a.
- (* ANum *) reflexivity.
- (* APlus *) destruct a1 eqn:Ea1.
+ (* a1 = ANum n *) destruct n eqn:En.
× (* n = 0 *) simpl. apply IHa2.
× (* n <> 0 *) simpl. rewrite IHa2. reflexivity.
+ (* a1 = APlus a1_1 a1_2 *)
simpl. simpl in IHa1. rewrite IHa1.
rewrite IHa2. reflexivity.
+ (* a1 = AMinus a1_1 a1_2 *)
simpl. simpl in IHa1. rewrite IHa1.
rewrite IHa2. reflexivity.
+ (* a1 = AMult a1_1 a1_2 *)
simpl. simpl in IHa1. rewrite IHa1.
rewrite IHa2. reflexivity.
- (* AMinus *)
simpl. rewrite IHa1. rewrite IHa2. reflexivity.
- (* AMult *)
simpl. rewrite IHa1. rewrite IHa2. reflexivity. Qed.
intros a. induction a.
- (* ANum *) reflexivity.
- (* APlus *) destruct a1 eqn:Ea1.
+ (* a1 = ANum n *) destruct n eqn:En.
× (* n = 0 *) simpl. apply IHa2.
× (* n <> 0 *) simpl. rewrite IHa2. reflexivity.
+ (* a1 = APlus a1_1 a1_2 *)
simpl. simpl in IHa1. rewrite IHa1.
rewrite IHa2. reflexivity.
+ (* a1 = AMinus a1_1 a1_2 *)
simpl. simpl in IHa1. rewrite IHa1.
rewrite IHa2. reflexivity.
+ (* a1 = AMult a1_1 a1_2 *)
simpl. simpl in IHa1. rewrite IHa1.
rewrite IHa2. reflexivity.
- (* AMinus *)
simpl. rewrite IHa1. rewrite IHa2. reflexivity.
- (* AMult *)
simpl. rewrite IHa1. rewrite IHa2. reflexivity. Qed.
Coq 自动化
Theorem silly1 : ∀ ae, aeval ae = aeval ae.
Proof. try reflexivity. (* 它和 reflexivity 做的一样。 *) Qed.
Theorem silly2 : ∀ (P : Prop), P → P.
Proof.
intros P HP.
try reflexivity. (* 和 reflexivity 失败时一样。 *)
apply HP. (* 我们仍然可以换种方式来结束此证明。 *)
Qed.
我们并没有真正的理由在像这样的手动证明中使用 try,不过在连同
; 泛策略一起进行自动化证明时它会非常有用,接下来我们来展示它。
Lemma foo : ∀ n, 0 <=? n = true.
Proof.
intros.
destruct n.
(* 会产生两个执行过程相同的子目标... *)
- (* n=0 *) simpl. reflexivity.
- (* n=Sn' *) simpl. reflexivity.
Qed.
我们可以用 ; 泛策略来化简它:
Lemma foo' : ∀ n, 0 <=? n = true.
Proof.
intros.
(* destruct 解构当前子目标 *)
destruct n;
(* 然后用 simpl 化简每个产生的子目标 *)
simpl;
(* 之后再对每个产生的子目标执行 reflexivity *)
reflexivity.
Qed.
try 配合 ; 一同使用,我们可以从之前证明中麻烦的重复里解脱出来。
Theorem optimize_0plus_sound': ∀ a,
aeval (optimize_0plus a) = aeval a.
Proof.
intros a.
induction a;
(* 大部分情况后面直接就是 IH... *)
try (simpl; rewrite IHa1; rewrite IHa2; reflexivity).
(* ... 不过剩下的情况 -- ANum 和 APlus -- 则不同: *)
- (* ANum *) reflexivity.
- (* APlus *)
destruct a1 eqn:Ea1;
(* 同样,大部分情况后面直接就是 IH: *)
try (simpl; simpl in IHa1; rewrite IHa1;
rewrite IHa2; reflexivity).
(* 当 e1 = ANum n 时出现了有趣的情况,其中 try... 什么也不做。
此时,我们需要解构 n(来确认优化是否应用)并用归纳假设来改写它。 *)
+ (* a1 = ANum n *) destruct n eqn:En;
simpl; rewrite IHa2; reflexivity. Qed.
Coq 专家经常在像 induction 这样的策略之后使用这种“...; try... ”的习语,
以便一次处理所有相似的情况。自然,在非形式化证明中也有同样的做法。
例如,以下对该优化定理的非形式化证明与形式化证明的结构一致:
'定理':对于所有的算术表达式 a,
aeval (optimize_0plus a) = aeval a.
'证明':对 a 进行归纳。大部分情况根据即可 IH 得证。其余情况如下:
然而,此证明仍然可以改进:第一种情况(a = ANum n)非常平凡,
甚至比我们根据归纳假设化简的那个情况还要平凡,然而我们却把它完整地写了出来。
为了更加清楚,最好把它去掉,然后在最上面说:“大部分情况可以立即得出,
或直接从归纳假设得出。唯一有趣的情况是 APlus...”
我们也可以在形式化证明中做出这种改进,方法如下:
aeval (optimize_0plus a) = aeval a.
- 假设设对于某些 n 有 a = ANum n for some n。我们必须证明
aeval (optimize_0plus (ANum n)) = aeval (ANum n). - 假设对于某些 a1 和 a2 有 a = APlus a1 a2。我们必须证明
aeval (optimize_0plus (APlus a1 a2)) = aeval (APlus a1 a2).
optimize_0plus (APlus a1 a2) = optimize_0plus a2
Theorem optimize_0plus_sound'': ∀ a,
aeval (optimize_0plus a) = aeval a.
Proof.
intros a.
induction a;
(* Most cases follow directly by the IH *)
try (simpl; rewrite IHa1; rewrite IHa2; reflexivity);
(* ... or are immediate by definition *)
try reflexivity.
(* The interesting case is when a = APlus a1 a2. *)
- (* APlus *)
destruct a1; try (simpl; simpl in IHa1; rewrite IHa1;
rewrite IHa2; reflexivity).
+ (* a1 = ANum n *) destruct n;
simpl; rewrite IHa2; reflexivity. Qed.
; 泛策略(一般形式)
T; [T1 | T2 | ... | Tn]
T; [T' | T' | ... | T']
策略 repeat T 永远不会失败:如果策略 T 并未应用到原始目标上,
那么 repeat 仍然会成功而不改变原始目标(即,它重复了零次)。
Theorem In10' : In 10 [1;2;3;4;5;6;7;8;9;10].
Proof.
repeat (left; reflexivity).
repeat (right; try (left; reflexivity)).
Qed.
策略 repeat T 应用 T 的次数也没有任何上界。如果 T 策略总是成功,
那么重复 T 会永远循环(例如 repeat simpl 会一直循环,因为 simpl
总是会成功)。虽然 Coq 的主语言 Gallina 中的求值保证会终止,
然而策略却不会!然而这并不会影响 Coq 的逻辑一致性,因为 repeat
和其它策略的工作就是指导 Coq 去构造证明;如果构造过程发散(即不停机),
那就意味着我们构造证明失败,而非构造出了错误的证明。
练习:3 星, standard (optimize_0plus_b_sound)
由于 optimize_0plus 变换不会改变 aexp 的值, 因此我们可以将它应用到所有出现在 bexp 中的 aexp 上而不改变 bexp 的值。请编写一个对 bexp 执行此变换的函数,并证明它的可靠性。 利用我们刚学过的泛策略来构造一个尽可能优雅的证明。Fixpoint optimize_0plus_b (b : bexp) : bexp
(* 将本行替换成 ":= _你的_定义_ ." *). Admitted.
Theorem optimize_0plus_b_sound : ∀ b,
beval (optimize_0plus_b b) = beval b.
Proof.
(* 请在此处解答 *) Admitted.
☐
练习:4 星, standard, optional (optimize)
'设计练习':optimize_0plus 函数只是众多算术和布尔表达式优化的方法之一。 请编写一个更加聪明的优化器并证明它的正确性。(最容易的方法就是从小处着手: 一开始只添加单个简单的优化并证明它的正确性,然后逐渐增加其它更有趣的优化。)(* 请在此处解答 *)
☐
定义新的策略记法
- 下面展示的 Tactic Notation 习语给出了定义“简写策略”的简便方式,
它将多个策略封装成单条指令。
- 对于更加复杂的编程,Coq 提供了内建的 Ltac 语言,
它带有可以检查和修改证明状态的原语。由于详情太过复杂,这里不便展开
(Ltac 通常也被认为不是 Coq 的设计中最美妙的部分!),
你可以在参考手册和其它关于 Coq 的书中找到它,Coq 的标准库中有很多
Ltac 定义的例子,你也可以参考它们。
- 还有 OCaml 的 API,它可以构建从底层访问 Coq 内部结构的策略, 不过普通 Coq 用于很少需要麻烦它。
Tactic Notation "simpl_and_try" tactic(c) :=
simpl;
try c.
这定义了一个新的名为 simpl_and_try 的泛策略,它接受一个策略 c
作为参数,其定义等价于策略 simpl; try c。现在在证明中写
“simpl_and_try reflexivity.”和写“simpl; try reflexivity.”是一样的。
omega 策略
- 数值常量、加法(+ 和 S)、减法(- 和 pred)以及常量乘法
(这就是 Presburger 算术的构成要素)
- 相等关系(= 和 ≠)和序(≤)
- 逻辑连结 ∧、∨、¬ 和 →
Example silly_presburger_example : ∀ m n o p,
m + n ≤ n + o ∧ o + 3 = p + 3 →
m ≤ p.
Proof.
intros. omega.
Qed.
(注意本文件顶部 From Coq Require Import omega.Omega.。)
更多方便的策略
- clear H:从上下文中删除前提 H。
- subst x:对于变量 x,在上下文中查找假设 x = e 或 e = x,
将整个上下文和当前目标中的所有 x 替换为 e 并清除该假设。
- subst:替换掉'所有'形如 x = e 或 e = x 的假设(其中 x 为变量)。
- rename... into...:更改证明上下文中前提的名字。例如,
如果上下文中包含名为 x 的变量,那么 rename x into y
就会将所有出现的 x 重命名为 y。
- assumption:尝试在上下文中查找完全匹配目标的前提 H。
如果找到了,那么其行为与 apply H 相同。
- contradiction:尝试在当前上下文中查找逻辑等价于 False 的前提 H。
如果找到了,就解决该目标。
- constructor:尝试在当前环境中的 Inductive 定义中查找可用于解决当前目标的构造子 c。如果找到了,那么其行为与 apply c 相同。
求值作为关系
Module aevalR_first_try.
Inductive aevalR : aexp → nat → Prop :=
| E_ANum n :
aevalR (ANum n) n
| E_APlus (e1 e2: aexp) (n1 n2: nat) :
aevalR e1 n1 →
aevalR e2 n2 →
aevalR (APlus e1 e2) (n1 + n2)
| E_AMinus (e1 e2: aexp) (n1 n2: nat) :
aevalR e1 n1 →
aevalR e2 n2 →
aevalR (AMinus e1 e2) (n1 - n2)
| E_AMult (e1 e2: aexp) (n1 n2: nat) :
aevalR e1 n1 →
aevalR e2 n2 →
aevalR (AMult e1 e2) (n1 × n2).
Module TooHardToRead.
(* A small notational aside. We would previously have written the
definition of aevalR like this, with explicit names for the
hypotheses in each case: *)
Inductive aevalR : aexp → nat → Prop :=
| E_ANum n :
aevalR (ANum n) n
| E_APlus (e1 e2: aexp) (n1 n2: nat)
(H1 : aevalR e1 n1)
(H2 : aevalR e2 n2) :
aevalR (APlus e1 e2) (n1 + n2)
| E_AMinus (e1 e2: aexp) (n1 n2: nat)
(H1 : aevalR e1 n1)
(H2 : aevalR e2 n2) :
aevalR (AMinus e1 e2) (n1 - n2)
| E_AMult (e1 e2: aexp) (n1 n2: nat)
(H1 : aevalR e1 n1)
(H2 : aevalR e2 n2) :
aevalR (AMult e1 e2) (n1 × n2).
Instead, we've chosen to leave the hypotheses anonymous, just
giving their types. This style gives us less control over the
names that Coq chooses during proofs involving aevalR, but it
makes the definition itself quite a bit lighter.
如果 aevalR 有中缀记法的话会很方便。我们用 e ==> n
表示算术表达式 e 求值为 n。
Notation "e '==>' n"
:= (aevalR e n)
(at level 50, left associativity)
: type_scope.
End aevalR_first_try.
实际上,Coq 提供了一种在 aevalR 自身内使用此记法的方式。
这样可以避免在进行涉及 e ==> n 形式的证明时,必须向前引用
aevalR e n 形式的定义的情况,从而减少混淆。
具体做法是,我们先“保留”该记法,然后在给出定义的同时声明它的意义。
Reserved Notation "e '==>' n" (at level 90, left associativity).
Inductive aevalR : aexp → nat → Prop :=
| E_ANum (n : nat) :
(ANum n) ==> n
| E_APlus (e1 e2 : aexp) (n1 n2 : nat) :
(e1 ==> n1) → (e2 ==> n2) → (APlus e1 e2) ==> (n1 + n2)
| E_AMinus (e1 e2 : aexp) (n1 n2 : nat) :
(e1 ==> n1) → (e2 ==> n2) → (AMinus e1 e2) ==> (n1 - n2)
| E_AMult (e1 e2 : aexp) (n1 n2 : nat) :
(e1 ==> n1) → (e2 ==> n2) → (AMult e1 e2) ==> (n1 × n2)
where "e '==>' n" := (aevalR e n) : type_scope.
推理规则的记法
| E_APlus : ∀ (e1 e2: aexp) (n1 n2: nat),
aevalR e1 n1 →
aevalR e2 n2 →
aevalR (APlus e1 e2) (n1 + n2)
e1 ==> n1 | |
e2 ==> n2 | (E_APlus) |
APlus e1 e2 ==> n1+n2 |
(E_ANum) | |
ANum n ==> n |
e1 ==> n1 | |
e2 ==> n2 | (E_APlus) |
APlus e1 e2 ==> n1+n2 |
e1 ==> n1 | |
e2 ==> n2 | (E_AMinus) |
AMinus e1 e2 ==> n1-n2 |
e1 ==> n1 | |
e2 ==> n2 | (E_AMult) |
AMult e1 e2 ==> n1*n2 |
练习:1 星, standard, optional (beval_rules)
下面是 Coq 中 beval 函数的定义:Fixpoint beval (e : bexp) : bool :=
match e with
| BTrue ⇒ true
| BFalse ⇒ false
| BEq a1 a2 ⇒ (aeval a1) =? (aeval a2)
| BLe a1 a2 ⇒ (aeval a1) <=? (aeval a2)
| BNot b1 ⇒ negb (beval b1)
| BAnd b1 b2 ⇒ andb (beval b1) (beval b2)
end.
(* 请在此处解答 *)
(* 请勿修改下面这一行: *)
Definition manual_grade_for_beval_rules : option (nat×string) := None.
☐
(* 请勿修改下面这一行: *)
Definition manual_grade_for_beval_rules : option (nat×string) := None.
☐
Theorem aeval_iff_aevalR : ∀ a n,
(a ==> n) ↔ aeval a = n.
Proof.
split.
- (* -> *)
intros H.
induction H; simpl.
+ (* E_ANum *)
reflexivity.
+ (* E_APlus *)
rewrite IHaevalR1. rewrite IHaevalR2. reflexivity.
+ (* E_AMinus *)
rewrite IHaevalR1. rewrite IHaevalR2. reflexivity.
+ (* E_AMult *)
rewrite IHaevalR1. rewrite IHaevalR2. reflexivity.
- (* <- *)
generalize dependent n.
induction a;
simpl; intros; subst.
+ (* ANum *)
apply E_ANum.
+ (* APlus *)
apply E_APlus.
apply IHa1. reflexivity.
apply IHa2. reflexivity.
+ (* AMinus *)
apply E_AMinus.
apply IHa1. reflexivity.
apply IHa2. reflexivity.
+ (* AMult *)
apply E_AMult.
apply IHa1. reflexivity.
apply IHa2. reflexivity.
Qed.
split.
- (* -> *)
intros H.
induction H; simpl.
+ (* E_ANum *)
reflexivity.
+ (* E_APlus *)
rewrite IHaevalR1. rewrite IHaevalR2. reflexivity.
+ (* E_AMinus *)
rewrite IHaevalR1. rewrite IHaevalR2. reflexivity.
+ (* E_AMult *)
rewrite IHaevalR1. rewrite IHaevalR2. reflexivity.
- (* <- *)
generalize dependent n.
induction a;
simpl; intros; subst.
+ (* ANum *)
apply E_ANum.
+ (* APlus *)
apply E_APlus.
apply IHa1. reflexivity.
apply IHa2. reflexivity.
+ (* AMinus *)
apply E_AMinus.
apply IHa1. reflexivity.
apply IHa2. reflexivity.
+ (* AMult *)
apply E_AMult.
apply IHa1. reflexivity.
apply IHa2. reflexivity.
Qed.
我们可以利用泛策略将此证明缩减到很短。
Theorem aeval_iff_aevalR' : ∀ a n,
(a ==> n) ↔ aeval a = n.
Proof.
(* 课上已完成 *)
split.
- (* -> *)
intros H; induction H; subst; reflexivity.
- (* <- *)
generalize dependent n.
induction a; simpl; intros; subst; constructor;
try apply IHa1; try apply IHa2; reflexivity.
Qed.
Inductive bevalR: bexp → bool → Prop :=
(* 请在此处解答 *)
.
Lemma beval_iff_bevalR : ∀ b bv,
bevalR b bv ↔ beval b = bv.
Proof.
(* 请在此处解答 *) Admitted.
☐
例如,假设我们想要用除法来扩展算术运算:
Inductive aexp : Type :=
| ANum (n : nat)
| APlus (a1 a2 : aexp)
| AMinus (a1 a2 : aexp)
| AMult (a1 a2 : aexp)
| ADiv (a1 a2 : aexp). (* <--- 新增 *)
扩展 aeval 的定义来处理此讯算并不是很直观(我们要返回什么作为
ADiv (ANum 5) (ANum 0) 的结果?)。然而扩展 aevalR 却很简单。
Reserved Notation "e '==>' n"
(at level 90, left associativity).
Inductive aevalR : aexp → nat → Prop :=
| E_ANum (n : nat) :
(ANum n) ==> n
| E_APlus (a1 a2 : aexp) (n1 n2 : nat) :
(a1 ==> n1) → (a2 ==> n2) → (APlus a1 a2) ==> (n1 + n2)
| E_AMinus (a1 a2 : aexp) (n1 n2 : nat) :
(a1 ==> n1) → (a2 ==> n2) → (AMinus a1 a2) ==> (n1 - n2)
| E_AMult (a1 a2 : aexp) (n1 n2 : nat) :
(a1 ==> n1) → (a2 ==> n2) → (AMult a1 a2) ==> (n1 × n2)
| E_ADiv (a1 a2 : aexp) (n1 n2 n3 : nat) : (* <----- NEW *)
(a1 ==> n1) → (a2 ==> n2) → (n2 > 0) →
(mult n2 n3 = n1) → (ADiv a1 a2) ==> n3
where "a '==>' n" := (aevalR a n) : type_scope.
Notice that the evaluation relation has now become partial:
There are some inputs for which it simply does not specify an
output.
假设我们想要用非确定性的数值生成器 any 来扩展算术运算,
该生成器会在求值时产生任何数。(注意,这不同于在所有可能的数值中作出
'概率上的'选择 -- 我们没有为结果指定任何具体的概率分布,只是说了
'可能的结果'。)
Reserved Notation "e '==>' n" (at level 90, left associativity).
Inductive aexp : Type :=
| AAny (* <--- NEW *)
| ANum (n : nat)
| APlus (a1 a2 : aexp)
| AMinus (a1 a2 : aexp)
| AMult (a1 a2 : aexp).
同样,扩展 aeval 会很棘手,因为现在的求值'并不是'一个从表达式到数值的确定性函数,
而扩展 aevalR 则无此问题...
Inductive aevalR : aexp → nat → Prop :=
| E_Any (n : nat) :
AAny ==> n (* <--- NEW *)
| E_ANum (n : nat) :
(ANum n) ==> n
| E_APlus (a1 a2 : aexp) (n1 n2 : nat) :
(a1 ==> n1) → (a2 ==> n2) → (APlus a1 a2) ==> (n1 + n2)
| E_AMinus (a1 a2 : aexp) (n1 n2 : nat) :
(a1 ==> n1) → (a2 ==> n2) → (AMinus a1 a2) ==> (n1 - n2)
| E_AMult (a1 a2 : aexp) (n1 n2 : nat) :
(a1 ==> n1) → (a2 ==> n2) → (AMult a1 a2) ==> (n1 × n2)
where "a '==>' n" := (aevalR a n) : type_scope.
End aevalR_extended.
这时你可能会问:默认情况下应该使用哪种风格?
我们刚看到的例子表明关系式的定义反而比函数式的更加有用。
对于这种定义的东西不太容易用函数表达,或者确实'不是'函数的情况来说,
明显别无选择。但如果两种风格均可行呢?
关系式定义的一个优点是,它们会更优雅,更容易理解。
另一个优点是,Coq 会根据 Inductive 定义自动生成不错的反演函数和归纳法则。
另一方面,函数式定义通常会更方便:
此外,函数还可以直接从 Gallina“提取”出 OCaml 或 Haskell 的可执行代码。
最终,选择视具体情况而定,或者只是品味问题。确实,在大型的 Coq
开发中,经常可以看到一个定义同时给出了函数式和关系式'两种'风格,
加上一条陈述了二者等价的引理,以便在之后的证明中能够在这两种视角下随意切换。
- 函数的定义是确定性的,且在所有参数上定义;而对于关系式定义来说, 我们需要这些性质时必须显式地'证明'它们。
- 有了函数,我们还可以利用 Coq 的计算机制在证明过程中简化表达式。
状态
Inductive aexp : Type :=
| ANum (n : nat)
| AId (x : string) (* <--- 新增 *)
| APlus (a1 a2 : aexp)
| AMinus (a1 a2 : aexp)
| AMult (a1 a2 : aexp).
为几个变量名定义简单记法能让示例更加易读:
Definition W : string := "W".
Definition X : string := "X".
Definition Y : string := "Y".
Definition Z : string := "Z".
(这种命名程序变量的约定(X、Y、Z)
与我们之前使用大写字母表示类型有点冲突。由于我们在本章中开发 Imp
时没怎么使用多态,所以这种重载应该不会产生混淆。)
bexp 的定义现在除了引用了新的 aexp 之外并未更改:
Inductive bexp : Type :=
| BTrue
| BFalse
| BEq (a1 a2 : aexp)
| BLe (a1 a2 : aexp)
| BNot (b : bexp)
| BAnd (b1 b2 : bexp).
记法
要让 Imp 程序更易读写,我们引入了一些记法和隐式转换(Coercion)。Coercion AId : string >-> aexp.
Coercion ANum : nat >-> aexp.
Definition bool_to_bexp (b : bool) : bexp :=
if b then BTrue else BFalse.
Coercion bool_to_bexp : bool >-> bexp.
Bind Scope imp_scope with aexp.
Bind Scope imp_scope with bexp.
Delimit Scope imp_scope with imp.
Notation "x + y" := (APlus x y) (at level 50, left associativity) : imp_scope.
Notation "x - y" := (AMinus x y) (at level 50, left associativity) : imp_scope.
Notation "x * y" := (AMult x y) (at level 40, left associativity) : imp_scope.
Notation "x <= y" := (BLe x y) (at level 70, no associativity) : imp_scope.
Notation "x = y" := (BEq x y) (at level 70, no associativity) : imp_scope.
Notation "x && y" := (BAnd x y) (at level 40, left associativity) : imp_scope.
Notation "'~' b" := (BNot b) (at level 75, right associativity) : imp_scope.
现在我们可以用 3 + (X × 2) 来代替 APlus 3 (AMult X 2) 了,同样可以用
true && !(X ≤ 4) 来代替 BAnd true (BNot (BLe X 4))。
然而这只有在我们告诉 Coq 在恰当的记法作用域中用 %imp 解析我们的表达式时,
或者当 Coq 知道该表达式的类型(aexp 或 bexp)被「限定」为使用该记法作用域时才可行。
强制转换有一点不便之处,即它会略微提高人类推导表达式类型的难度。
如果你感到有点困惑,请用 Set Printing Coercions 来查看具体发生了什么。
Set Printing Coercions.
Print example_bexp.
(* ===> (example_bexp = bool_to_bexp true && ~ (AId X <= ANum 4))*)
Unset Printing Coercions.
Fixpoint aeval (st : state) (a : aexp) : nat :=
match a with
| ANum n ⇒ n
| AId x ⇒ st x (* <--- 新增 *)
| APlus a1 a2 ⇒ (aeval st a1) + (aeval st a2)
| AMinus a1 a2 ⇒ (aeval st a1) - (aeval st a2)
| AMult a1 a2 ⇒ (aeval st a1) × (aeval st a2)
end.
Fixpoint beval (st : state) (b : bexp) : bool :=
match b with
| BTrue ⇒ true
| BFalse ⇒ false
| BEq a1 a2 ⇒ (aeval st a1) =? (aeval st a2)
| BLe a1 a2 ⇒ (aeval st a1) <=? (aeval st a2)
| BNot b1 ⇒ negb (beval st b1)
| BAnd b1 b2 ⇒ andb (beval st b1) (beval st b2)
end.
我们为具体状态的全映射声明具体的记法,即使用 (_ !-> 0) 作为空状态。
现在我们可以为“单例状态(singleton state)”添加新的记法了,
即只有一个绑定到值的变量。
Notation "x '!->' v" := (t_update empty_st x v) (at level 100).
Example aexp1 :
aeval (X !-> 5) (3 + (X × 2))
= 13.
Example bexp1 :
beval (X !-> 5) (true && ~(X ≤ 4))
= true.
Example aexp1 :
aeval (X !-> 5) (3 + (X × 2))
= 13.
Proof. reflexivity. Qed.
Example bexp1 :
beval (X !-> 5) (true && ~(X ≤ 4))
= true.
Proof. reflexivity. Qed.
语法
c ::= SKIP | x ::= a | c ;; c | TEST b THEN c ELSE c FI
| WHILE b DO c END
Z ::= X;;
Y ::= 1;;
WHILE ~(Z = 0) DO
Y ::= Y × Z;;
Z ::= Z - 1
END
Inductive com : Type :=
| CSkip
| CAss (x : string) (a : aexp)
| CSeq (c1 c2 : com)
| CIf (b : bexp) (c1 c2 : com)
| CWhile (b : bexp) (c : com).
至于表达式,我们可以用一些 Notation 声明来让 Imp 程序的读写更加方便。
Bind Scope imp_scope with com.
Notation "'SKIP'" :=
CSkip : imp_scope.
Notation "x '::=' a" :=
(CAss x a) (at level 60) : imp_scope.
Notation "c1 ;; c2" :=
(CSeq c1 c2) (at level 80, right associativity) : imp_scope.
Notation "'WHILE' b 'DO' c 'END'" :=
(CWhile b c) (at level 80, right associativity) : imp_scope.
Notation "'TEST' c1 'THEN' c2 'ELSE' c3 'FI'" :=
(CIf c1 c2 c3) (at level 80, right associativity) : imp_scope.
例如,下面是个阶乘函数,写成 Coq 的形式化定义:
Definition fact_in_coq : com :=
(Z ::= X;;
Y ::= 1;;
WHILE ~(Z = 0) DO
Y ::= Y × Z;;
Z ::= Z - 1
END).
脱糖记法
- Unset Printing Notations(用 Set Printing Notations 撤销)
- Set Printing Coercions(用 Unset Printing Coercions 撤销)
- Set Printing All(用 Unset Printing All 撤销)
Unset Printing Notations.
Print fact_in_coq.
(* ===>
fact_in_coq =
CSeq (CAss Z X)
(CSeq (CAss Y (S O))
(CWhile (BNot (BEq Z O))
(CSeq (CAss Y (AMult Y Z))
(CAss Z (AMinus Z (S O))))))
: com *)
Set Printing Notations.
Set Printing Coercions.
Print fact_in_coq.
(* ===>
fact_in_coq =
(Z ::= AId X;;
Y ::= ANum 1;;
WHILE ~ (AId Z = ANum 0) DO
Y ::= AId Y * AId Z;;
Z ::= AId Z - ANum 1
END)
: com *)
Unset Printing Coercions.
Locate "&&".
(* ===>
Notation "x && y" := andb x y : bool_scope (default interpretation) *)
Locate ";;".
(* ===>
Notation "c1 ;; c2" := CSeq c1 c2 : imp_scope (default interpretation) *)
Locate "WHILE".
(* ===>
Notation "'WHILE' b 'DO' c 'END'" := CWhile b c : imp_scope
(default interpretation) *)
(* ===>
Notation "x && y" := andb x y : bool_scope (default interpretation) *)
Locate ";;".
(* ===>
Notation "c1 ;; c2" := CSeq c1 c2 : imp_scope (default interpretation) *)
Locate "WHILE".
(* ===>
Notation "'WHILE' b 'DO' c 'END'" := CWhile b c : imp_scope
(default interpretation) *)
Locate aexp.
(* ===>
Inductive Top.aexp
Inductive Top.AExp.aexp
(shorter name to refer to it in current context is AExp.aexp)
Inductive Top.aevalR_division.aexp
(shorter name to refer to it in current context is aevalR_division.aexp)
Inductive Top.aevalR_extended.aexp
(shorter name to refer to it in current context is aevalR_extended.aexp)
*)
(* ===>
Inductive Top.aexp
Inductive Top.AExp.aexp
(shorter name to refer to it in current context is AExp.aexp)
Inductive Top.aevalR_division.aexp
(shorter name to refer to it in current context is aevalR_division.aexp)
Inductive Top.aevalR_extended.aexp
(shorter name to refer to it in current context is aevalR_extended.aexp)
*)
Definition plus2 : com :=
X ::= X + 2.
Definition XtimesYinZ : com :=
Z ::= X × Y.
Definition subtract_slowly_body : com :=
Z ::= Z - 1 ;;
X ::= X - 1.
Definition subtract_slowly : com :=
(WHILE ~(X = 0) DO
subtract_slowly_body
END).
Definition subtract_3_from_5_slowly : com :=
X ::= 3 ;;
Z ::= 5 ;;
subtract_slowly.
Open Scope imp_scope.
Fixpoint ceval_fun_no_while (st : state) (c : com)
: state :=
match c with
| SKIP ⇒
st
| x ::= a1 ⇒
(x !-> (aeval st a1) ; st)
| c1 ;; c2 ⇒
let st' := ceval_fun_no_while st c1 in
ceval_fun_no_while st' c2
| TEST b THEN c1 ELSE c2 FI ⇒
if (beval st b)
then ceval_fun_no_while st c1
else ceval_fun_no_while st c2
| WHILE b DO c END ⇒
st (* 假装能用 *)
end.
Close Scope imp_scope.
Fixpoint ceval_fun_no_while (st : state) (c : com)
: state :=
match c with
| SKIP ⇒
st
| x ::= a1 ⇒
(x !-> (aeval st a1) ; st)
| c1 ;; c2 ⇒
let st' := ceval_fun_no_while st c1 in
ceval_fun_no_while st' c2
| TEST b THEN c1 ELSE c2 FI ⇒
if (beval st b)
then ceval_fun_no_while st c1
else ceval_fun_no_while st c2
| WHILE b DO c END ⇒
st (* 假装能用 *)
end.
Close Scope imp_scope.
在 OCaml 或 Haskell 这类传统的函数式编程语言中,我们可以像下面这样添加
WHILE 的情况:
由于它对于所有的输入都不会终止,因此 ceval_fun 无法在 Coq 中写出
至少需要一些技巧和变通才行(如果你对此好奇的话请阅读
ImpCEvalFun 一章)。
Fixpoint ceval_fun (st : state) (c : com) : state := match c with ... | WHILE b DO c END => if (beval st b) then ceval_fun st (c ;; WHILE b DO c END) else st end.Coq 会拒绝这种定义(“Error: Cannot guess decreasing argument of fix”, 错误:无法猜测出固定的递减参数),因为我们想要定义的函数并不保证终止。 确实,它并不'总是会终止':例如,ceval_fun 函数应用到上面的 loop 程序的完整版本永远不会终止。由于 Coq 不仅是一个函数式编程语言, 还是个逻辑一致的语言,因此任何潜在的不可终止函数都会被拒绝。下面是一个 (无效的)程序,它展示了如果 Coq 允许不可终止的递归函数的话会产生什么错误:
Fixpoint loop_false (n : nat) : False := loop_false n.也就是说,像 False 这样的假命题可以被证明(loop_false 0 会是 False 的一个证明),这对于 Coq 的逻辑一致性来说是个灾难。
求值作为一种关系
操作语义
(E_Skip) | |
st =[ SKIP ]=> st |
aeval st a1 = n | (E_Ass) |
st =[ x := a1 ]=> (x !-> n ; st) |
st =[ c1 ]=> st' | |
st' =[ c2 ]=> st'' | (E_Seq) |
st =[ c1;;c2 ]=> st'' |
beval st b1 = true | |
st =[ c1 ]=> st' | (E_IfTrue) |
st =[ TEST b1 THEN c1 ELSE c2 FI ]=> st' |
beval st b1 = false | |
st =[ c2 ]=> st' | (E_IfFalse) |
st =[ TEST b1 THEN c1 ELSE c2 FI ]=> st' |
beval st b = false | (E_WhileFalse) |
st =[ WHILE b DO c END ]=> st |
beval st b = true | |
st =[ c ]=> st' | |
st' =[ WHILE b DO c END ]=> st'' | (E_WhileTrue) |
st =[ WHILE b DO c END ]=> st'' |
Reserved Notation "st '=[' c ']=>' st'"
(at level 40).
Inductive ceval : com → state → state → Prop :=
| E_Skip : ∀ st,
st =[ SKIP ]=> st
| E_Ass : ∀ st a1 n x,
aeval st a1 = n →
st =[ x ::= a1 ]=> (x !-> n ; st)
| E_Seq : ∀ c1 c2 st st' st'',
st =[ c1 ]=> st' →
st' =[ c2 ]=> st'' →
st =[ c1 ;; c2 ]=> st''
| E_IfTrue : ∀ st st' b c1 c2,
beval st b = true →
st =[ c1 ]=> st' →
st =[ TEST b THEN c1 ELSE c2 FI ]=> st'
| E_IfFalse : ∀ st st' b c1 c2,
beval st b = false →
st =[ c2 ]=> st' →
st =[ TEST b THEN c1 ELSE c2 FI ]=> st'
| E_WhileFalse : ∀ b st c,
beval st b = false →
st =[ WHILE b DO c END ]=> st
| E_WhileTrue : ∀ st st' st'' b c,
beval st b = true →
st =[ c ]=> st' →
st' =[ WHILE b DO c END ]=> st'' →
st =[ WHILE b DO c END ]=> st''
where "st =[ c ]=> st'" := (ceval c st st').
将求值定义成关系而非函数的代价是,我们需要自己为某个程序求值成某种结束状态'构造证明',
而不能只是交给 Coq 的计算机制去做了。
Example ceval_example1:
empty_st =[
X ::= 2;;
TEST X ≤ 1
THEN Y ::= 3
ELSE Z ::= 4
FI
]=> (Z !-> 4 ; X !-> 2).
Proof.
(* 我们必须提供中间状态 *)
apply E_Seq with (X !-> 2).
- (* 赋值指令 *)
apply E_Ass. reflexivity.
- (* if 指令 *)
apply E_IfFalse.
reflexivity.
apply E_Ass. reflexivity.
Qed.
Example ceval_example2:
empty_st =[
X ::= 0;; Y ::= 1;; Z ::= 2
]=> (Z !-> 2 ; Y !-> 1 ; X !-> 0).
Proof.
(* 请在此处解答 *) Admitted.
☐
empty_st =[
X ::= 0;; Y ::= 1;; Z ::= 2
]=> (Z !-> 2 ; Y !-> 1 ; X !-> 0).
Proof.
(* 请在此处解答 *) Admitted.
☐
练习:3 星, standard, optional (pup_to_n)
写一个 Imp 程序对从 1 到 X 进行求值(包括:将 1 + 2 + ... + X) 赋予变量 Y。 证明此程序对于 X = 2 会按预期执行(这可能比你预想的还要棘手)。Definition pup_to_n : com
(* 将本行替换成 ":= _你的_定义_ ." *). Admitted.
Theorem pup_to_2_ceval :
(X !-> 2) =[
pup_to_n
]=> (X !-> 0 ; Y !-> 3 ; X !-> 1 ; Y !-> 2 ; Y !-> 0 ; X !-> 2).
Proof.
(* 请在此处解答 *) Admitted.
☐
求值的确定性
Theorem ceval_deterministic: ∀ c st st1 st2,
st =[ c ]=> st1 →
st =[ c ]=> st2 →
st1 = st2.
Proof.
intros c st st1 st2 E1 E2.
generalize dependent st2.
induction E1; intros st2 E2; inversion E2; subst.
- (* E_Skip *) reflexivity.
- (* E_Ass *) reflexivity.
- (* E_Seq *)
assert (st' = st'0) as EQ1.
{ (* 对断言的证明 *) apply IHE1_1; assumption. }
subst st'0.
apply IHE1_2. assumption.
- (* E_IfTrue,b 求值为 true *)
apply IHE1. assumption.
- (* E_IfTrue,b 求值为 false(矛盾) *)
rewrite H in H5. discriminate H5.
- (* E_IfFalse, b 求值为 true(矛盾) *)
rewrite H in H5. discriminate H5.
- (* E_IfFalse,b 求值为 false *)
apply IHE1. assumption.
- (* E_WhileFalse,b 求值为 false *)
reflexivity.
- (* E_WhileFalse,b 求值为 true(矛盾) *)
rewrite H in H2. discriminate H2.
- (* E_WhileTrue, b 求值为 false(矛盾) *)
rewrite H in H4. discriminate H4.
- (* E_WhileTrue,b 求值为 true *)
assert (st' = st'0) as EQ1.
{ (* 对断言的证明 *) apply IHE1_1; assumption. }
subst st'0.
apply IHE1_2. assumption. Qed.
intros c st st1 st2 E1 E2.
generalize dependent st2.
induction E1; intros st2 E2; inversion E2; subst.
- (* E_Skip *) reflexivity.
- (* E_Ass *) reflexivity.
- (* E_Seq *)
assert (st' = st'0) as EQ1.
{ (* 对断言的证明 *) apply IHE1_1; assumption. }
subst st'0.
apply IHE1_2. assumption.
- (* E_IfTrue,b 求值为 true *)
apply IHE1. assumption.
- (* E_IfTrue,b 求值为 false(矛盾) *)
rewrite H in H5. discriminate H5.
- (* E_IfFalse, b 求值为 true(矛盾) *)
rewrite H in H5. discriminate H5.
- (* E_IfFalse,b 求值为 false *)
apply IHE1. assumption.
- (* E_WhileFalse,b 求值为 false *)
reflexivity.
- (* E_WhileFalse,b 求值为 true(矛盾) *)
rewrite H in H2. discriminate H2.
- (* E_WhileTrue, b 求值为 false(矛盾) *)
rewrite H in H4. discriminate H4.
- (* E_WhileTrue,b 求值为 true *)
assert (st' = st'0) as EQ1.
{ (* 对断言的证明 *) apply IHE1_1; assumption. }
subst st'0.
apply IHE1_2. assumption. Qed.
Theorem plus2_spec : ∀ st n st',
st X = n →
st =[ plus2 ]=> st' →
st' X = n + 2.
Proof.
intros st n st' HX Heval.
反转 Heval 实际上会强制让 Coq 展开 ceval 求值的一个步骤 --
由于 plus2 是一个赋值,因此这种情况揭示了 st' 一定是 st
通过新的值 X 扩展而来的。
(* 请在此处解答 *)
(* 请勿修改下面这一行: *)
Definition manual_grade_for_XtimesYinZ_spec : option (nat×string) := None.
☐
Theorem loop_never_stops : ∀ st st',
~(st =[ loop ]=> st').
Proof.
intros st st' contra. unfold loop in contra.
remember (WHILE true DO SKIP END)%imp as loopdef
eqn:Heqloopdef.
~(st =[ loop ]=> st').
Proof.
intros st st' contra. unfold loop in contra.
remember (WHILE true DO SKIP END)%imp as loopdef
eqn:Heqloopdef.
归纳讨论假设“loopdef 会终止”之构造,其中多数情形的矛盾显而易见,
可用 discriminate 一步解决。
(* 请在此处解答 *) Admitted.
☐
Open Scope imp_scope.
Fixpoint no_whiles (c : com) : bool :=
match c with
| SKIP ⇒
true
| _ ::= _ ⇒
true
| c1 ;; c2 ⇒
andb (no_whiles c1) (no_whiles c2)
| TEST _ THEN ct ELSE cf FI ⇒
andb (no_whiles ct) (no_whiles cf)
| WHILE _ DO _ END ⇒
false
end.
Close Scope imp_scope.
此断言只对没有 WHILE 循环的程序产生 true。请用 Inductive
写出一个性质 no_whilesR 使得 no_whilesR c 仅当 c 是个没有
WHILE 循环的程序时才可以证明。之后证明它与 no_whiles 等价。
Inductive no_whilesR: com → Prop :=
(* 请在此处解答 *)
.
Theorem no_whiles_eqv:
∀ c, no_whiles c = true ↔ no_whilesR c.
Proof.
(* 请在此处解答 *) Admitted.
☐
练习:4 星, standard (no_whiles_terminating)
不涉及 WHILE 循环的 Imp 程序一定会终止。请陈述并证明定理 no_whiles_terminating 来说明这一点。 按照你的偏好使用 no_whiles 或 no_whilesR。(* 请在此处解答 *)
(* 请勿修改下面这一行: *)
Definition manual_grade_for_no_whiles_terminating : option (nat×string) := None.
☐
附加练习
练习:3 星, standard (stack_compiler)
旧式惠普计算器的编程语言类似于 Forth 和 Postscript,而其抽象机器类似于 Java 虚拟机,即所有对算术表达式的求值都使用'栈'来进行。例如,表达式(2*3)+(3*(4-2))会被写成
2 3 * 3 4 2 - * +的形式,其求值过程如下(右侧为被求值的程序,左侧为栈中的内容):
[ ] | 2 3 * 3 4 2 - * + [2] | 3 * 3 4 2 - * + [3, 2] | * 3 4 2 - * + [6] | 3 4 2 - * + [3, 6] | 4 2 - * + [4, 3, 6] | 2 - * + [2, 4, 3, 6] | - * + [2, 3, 6] | * + [6, 6] | + [12] |此练习的目的在于编写一个小型编译器,它将 aexp 翻译成栈机器指令。
- SPush n:将数 n 压栈。
- SLoad x:从存储中加载标识符 x 并将其压栈。
- SPlus: 从栈顶弹出两个数,将二者相加,并将其结果压栈。
- SMinus: 类似,不过执行减法。
- SMult: 类似,不过执行乘法。
请编写一个函数对栈语言程序进行求值。它应当接受一个状态、
一个表示为数字列表的栈(栈顶项在表头),以及一个表示为指令列表的程序作为输入,
并在程序执行后返回该栈。请在以下示例中测试你的函数。
注意,当栈中的元素少于两个时,规范并未指定 SPlus、SMinus 或 SMult 指令的行为。
从某种意义上说,这样做并无必要,因为我们的编译器永远不会产生这种畸形的程序。
Fixpoint s_execute (st : state) (stack : list nat)
(prog : list sinstr)
: list nat
(* 将本行替换成 ":= _你的_定义_ ." *). Admitted.
Example s_execute1 :
s_execute empty_st []
[SPush 5; SPush 3; SPush 1; SMinus]
= [2; 5].
(* 请在此处解答 *) Admitted.
Example s_execute2 :
s_execute (X !-> 3) [3;4]
[SPush 4; SLoad X; SMult; SPlus]
= [15; 4].
(* 请在此处解答 *) Admitted.
接下来请编写一个将 aexp 编译成栈机器程序的函数。运行此程序的效果
应当和将该表达式的值压入栈中一致。
在定义完 s_compile 之后,请证明以下示例来测试它是否起作用。
Example s_compile1 :
s_compile (X - (2 × Y))
= [SLoad X; SPush 2; SLoad Y; SMult; SMinus].
(* 请在此处解答 *) Admitted.
☐
练习:4 星, advanced (stack_compiler_correct)
现在我们将证明在之前练习中实现的编译器的正确性。记住当栈中的元素少于两个时, 规范并未指定 SPlus、SMinus 或 SMult 指令的行为。 (为了让正确性证明更加容易,你可能需要返回去修改你的实现!)Theorem s_compile_correct : ∀ (st : state) (e : aexp),
s_execute st [] (s_compile e) = [ aeval st e ].
Proof.
(* 请在此处解答 *) Admitted.
☐
练习:3 星, standard, optional (short_circuit)
大部分现代编程语言对布尔 and 运算提供了“短路求值”的方法:要对 BAnd b1 b2 进行求值,首先对 b1 求值。如果结果为 false,那么整个 BAnd 表达式的求值就是 false,而无需对 b2 求值。否则,b2 的求值结果就决定了 BAnd 表达式的值。(* 请在此处解答 *)
☐
练习:4 星, advanced (break_imp)
像 C 和 Java 这样的指令式语言通常会包含 break 或类似地语句来中断循环的执行。 在本练习中,我们考虑如何为 Imp 加上 break。首先,我们需要丰富语言的指令。Inductive com : Type :=
| CSkip
| CBreak (* <--- 新增 *)
| CAss (x : string) (a : aexp)
| CSeq (c1 c2 : com)
| CIf (b : bexp) (c1 c2 : com)
| CWhile (b : bexp) (c : com).
Notation "'SKIP'" :=
CSkip.
Notation "'BREAK'" :=
CBreak.
Notation "x '::=' a" :=
(CAss x a) (at level 60).
Notation "c1 ;; c2" :=
(CSeq c1 c2) (at level 80, right associativity).
Notation "'WHILE' b 'DO' c 'END'" :=
(CWhile b c) (at level 80, right associativity).
Notation "'TEST' c1 'THEN' c2 'ELSE' c3 'FI'" :=
(CIf c1 c2 c3) (at level 80, right associativity).
接着,我们需要定义 BREAK 的行为。非形式化地说,只要 BREAK
在指令序列中执行,它就会终止该序列的执行并发出最内层围绕它的循环应当终止的信号。
(如果没有任何围绕它的循环,那么就终止整个程序。)最终状态应当与
BREAK 语句执行后的状态相同。
其要点之一在于当一个给定的 BREAK 位于多个循环中时应该做什么。
此时,BREAK 应当只终止'最内层'的循环。因此,在执行完以下指令之后...
X ::= 0;;
Y ::= 1;;
WHILE ~(0 = Y) DO
WHILE true DO
BREAK
END;;
X ::= 1;;
Y ::= Y - 1
END
...X 的值应为 1 而非 0。
表达这种行为的一种方式求值为求值关系添加一个形参,指定某个指令是否会执行
BREAK 语句:
X ::= 0;;
Y ::= 1;;
WHILE ~(0 = Y) DO
WHILE true DO
BREAK
END;;
X ::= 1;;
Y ::= Y - 1
END
Inductive result : Type :=
| SContinue
| SBreak.
Reserved Notation "st '=[' c ']=>' st' '/' s"
(at level 40, st' at next level).
直觉上说,st =[ c ]=> st' / s 表示如果 c 在 st 状况下开始,
它会在 st' 状态下终止,围绕它的最内层循环(或整个程序)
要么收到立即退出的信号(s = SBreak),要么继续正常执行(s = SContinue)。
“st =[ c ]=> st' / s”关系的定义非常类似于之前我们为一般求值关系
(st =[ c ]=> st')给出的定义 -- 我们只需要恰当地处理终止信号。
基于以上描述,请完成 ceval 关系的定义。
- 若指令为 SKIP,则状态不变,任何围绕它的循环继续正常执行。
- 若指令为 BREAK,则状态保持不变但发出 SBreak 的信号。
- 若指令为赋值,则根据状态更新该变量绑定的值,并发出继续正常执行的信号。
- 若指令为 TEST b THEN c1 ELSE c2 FI 的形式,则按照 Imp 的原始语义更新状态,
除此之外我们还要从被选择执行的分支中传播信号。
- 若指令为一系列 c1 ;; c2,我们首先执行 c1。如果它产生了
SBreak,我们就跳过 c2 的执行并将 SBreak 的信号传给其外围的上下文中;
结果状态与执行 c1 后获得的相同。否则,我们在执行完 c1 后的状态下执行
c2 并继续传递它产生的信号。
- 最后,对于形如 WHILE b DO c END 的循环,其语义基本和此前相同。 唯一的不同是,当 b 求值为 true 时执行 c 并检查它产生的信号。 若信号为 SContinue,则按照原始语义继续执行。否则,我们终止此循环的执行, 而其结果状态与当前迭代执行的结果相同。对于其它情况,由于 BREAK 只终止最内层的循环,因此 WHILE 发出 SContinue 的信号。
Inductive ceval : com → state → result → state → Prop :=
| E_Skip : ∀ st,
st =[ CSkip ]=> st / SContinue
(* 请在此处解答 *)
where "st '=[' c ']=>' st' '/' s" := (ceval c st s st').
现在证明你定义的 ceval 的如下性质:
Theorem break_ignore : ∀ c st st' s,
st =[ BREAK;; c ]=> st' / s →
st = st'.
Proof.
(* 请在此处解答 *) Admitted.
Theorem while_continue : ∀ b c st st' s,
st =[ WHILE b DO c END ]=> st' / s →
s = SContinue.
Proof.
(* 请在此处解答 *) Admitted.
Theorem while_stops_on_break : ∀ b c st st',
beval st b = true →
st =[ c ]=> st' / SBreak →
st =[ WHILE b DO c END ]=> st' / SContinue.
Proof.
(* 请在此处解答 *) Admitted.
☐
Theorem while_break_true : ∀ b c st st',
st =[ WHILE b DO c END ]=> st' / SContinue →
beval st' b = true →
∃ st'', st'' =[ c ]=> st' / SBreak.
Proof.
(* 请在此处解答 *) Admitted.
☐
st =[ WHILE b DO c END ]=> st' / SContinue →
beval st' b = true →
∃ st'', st'' =[ c ]=> st' / SBreak.
Proof.
(* 请在此处解答 *) Admitted.
☐
Theorem ceval_deterministic: ∀ (c:com) st st1 st2 s1 s2,
st =[ c ]=> st1 / s1 →
st =[ c ]=> st2 / s2 →
st1 = st2 ∧ s1 = s2.
Proof.
(* 请在此处解答 *) Admitted.
☐
st =[ c ]=> st1 / s1 →
st =[ c ]=> st2 / s2 →
st1 = st2 ∧ s1 = s2.
Proof.
(* 请在此处解答 *) Admitted.
☐
练习:4 星, standard, optional (add_for_loop)
为该语言添加 C 风格的 for 循环指令,更新 ceval 的定义来定义 for 循环,按需添加 for 循环的情况使得本文件中的所有证明都被 Coq 所接受。(* 请在此处解答 *)
☐
(* 2022-03-14 05:26:58 (UTC+00) *)