我正在关注软件基金会这本书,我正在关于名为“Imp”的章节。
作者揭露了一种小语言:
Inductive aexp : Type :=
| ANum : nat -> aexp
| APlus : aexp -> aexp -> aexp
| AMinus : aexp -> aexp -> aexp
| AMult : aexp -> aexp -> aexp.
以下是评估这些表达式的函数:
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.
练习是创建一个优化评估的功能。例如:
APlus a (ANum 0) --> a
这里有我的优化功能:
Fixpoint optimizer_a (a:aexp) :aexp :=
match a with
| ANum n => ANum n
| APlus (ANum 0) e2 => optimizer_a e2
| APlus e1 (ANum 0) => optimizer_a e1
| APlus e1 e2 => APlus (optimizer_a e1) (optimizer_a e2)
| AMinus e1 (ANum 0) => optimizer_a e1
| AMinus e1 e2 => AMinus (optimizer_a e1) (optimizer_a e2)
| AMult (ANum 1) e2 => optimizer_a e2
| AMult e1 (ANum 1) => optimizer_a e1
| AMult e1 e2 => AMult (optimizer_a e1) (optimizer_a e2)
end.
现在,我会证明优化功能是合理的:
Theorem optimizer_a_sound : forall a, aeval (optimizer_a a) = aeval a.
这个证明非常困难。所以我尝试用一些引理来分解证明。
这是一个引理:
Lemma optimizer_a_plus_sound : forall a b, aeval (optimizer_a (APlus a b)) = aeval (APlus (optimizer_a a) (optimizer_a b)).
我有证据,但很无聊。我在a上进行归纳然后,对于每种情况,我都会破坏b并破坏exp以处理b为0时的情况。
我需要这样做,因为
n+0 = n
不会自动减少,我们需要一个定义为plus_0_r。
现在,我想知道,我怎么能用Coq建立一个更好的证据,以避免在证明过程中出现一些无聊的重复。
以下是我对这个引理的证明:
我想我应该使用“Hint Rewrite plus_0_r”,但我不知道如何。
顺便说一下,我也有兴趣知道一些提示,以便显示初始定理(我的优化函数的响度)。
答案 0 :(得分:5)
如果你使用上面的技巧,你可以定义自己的战术,所以你不必输入那么多。由于证据很短,你可以没有引理。 (我为destruct-congruence-auto调用了战术dca
。)
较短的证据不具有可读性,但它本质上是:考虑变量的情况。
Lemma ANum0_dec: forall a, {a = ANum 0} + { a <> ANum 0}.
destruct a; try destruct n; try (right; discriminate); left; auto.
Qed.
Require Import Arith.
Ltac dca v := destruct v; try congruence; auto.
Lemma optimizer_a_plus_sound :
forall a b,
aeval (optimizer_a (APlus a b)) = aeval (APlus (optimizer_a a) (optimizer_a b)).
Proof.
intros a b;
destruct (ANum0_dec a), (ANum0_dec b).
- dca a; dca n.
- dca a; dca n0.
- dca b; dca n0; dca a; simpl; auto with arith; dca n0.
- dca a; dca b; dca n1; dca n2.
Qed.
然后使用它
Theorem optimizer_a_sound : forall a, aeval (optimizer_a a) = aeval a.
induction a.
* auto.
* rewrite optimizer_a_plus_sound; simpl; auto.
* (* ... and so on for Minus and Mult *)
你也可以用这种紧凑的形式做完整的证明。
答案 1 :(得分:3)
当你在数据结构上进行归纳并需要考虑案例时,我发现这是一个有用的方法来考虑变量X
的情况,摆脱不可能的情况,并解决琐碎的情况
destruct X; try congruence; auto.
在您的情况下,我们可以使用它来证明有关optimization_a
函数的有用重写引理。
Lemma opt1: forall a b, a = ANum 0 -> optimizer_a (APlus a b) = optimizer_a b.
intros.
destruct a; try congruence; auto;
destruct n; try congruence; auto.
Qed.
Lemma opt2: forall a b, b = ANum 0 -> optimizer_a (APlus a b) = optimizer_a a.
intros;
destruct a; try congruence; auto;
destruct b; try congruence; auto;
destruct n; try congruence; auto;
destruct n0; try congruence; auto.
Qed.
Lemma opt3:
forall a b,
a <> ANum 0 -> b <> ANum 0 ->
optimizer_a (APlus a b) = APlus (optimizer_a a) (optimizer_a b).
Proof.
intros.
destruct a; try congruence; auto;
destruct b; try congruence; auto;
destruct n; try congruence; auto;
destruct n0; try congruence; auto.
Qed.
实际上,这些证明也可以更紧凑地编写为具有一些洞察力和Ltac-fu的单行,但我的观点是,它表明它也可以几乎机械地使用。
另请注意,使用分号而不是指向字符串,以便在每个步骤后对所有剩余目标进行处理。
无论如何,既然我们有这些引理,我们可以考虑不同的情况(是a = ANum 0还是不是?是b?),只是重写。为了考虑这些案例,我们还需要一个说明a
是ANum 0
或者不是{p}的引理。
Lemma ANum0_dec: forall a, {a = ANum 0} + { a <> ANum 0}.
destruct a; try destruct n; try (right; discriminate); left; auto.
Qed.
我们可以破坏ANum0_dec a
并在上下文中获取a = ANum 0
,或在上下文中获取a <> ANum 0
。
Require Import Arith. (* for "auto with arith" to handle n = n + 0 *)
Lemma optimizer_a_plus_sound :
forall a b,
aeval (optimizer_a (APlus a b)) = aeval (APlus (optimizer_a a) (optimizer_a b)).
Proof.
intros a b.
destruct (ANum0_dec a); destruct (ANum0_dec b).
- rewrite opt1; subst; auto.
- rewrite opt1; subst; auto.
- rewrite opt2; subst; simpl; auto with arith.
- rewrite opt3; subst; auto.
Qed.