我有一个下面的证据,还有三个子目标要去。证明是关于在here的简单算术语言中优化plus 0
(optimize_0plus
)的正确性。 aexp
是"算术表达式"并且aeval
是"算术评估"。
3 subgoal
a1 : aexp
a2 : aexp
IHa1 : aeval (optimize_0plus a1) = aeval a1
IHa2 : aeval (optimize_0plus a2) = aeval a2
______________________________________(1/3)
aeval (optimize_0plus (APlus a1 a2)) = aeval (APlus a1 a2)
,optimize_0plus
是:
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.
我的战争计划是在当前子目标的LHS中应用optimize_0plus
,并获得:
aeval (APlus (optimize_0plus a1) (optimize_0plus a2)) = aeval (APlus a1 a2)
(但我无法弄清楚如何在Coq中做到这一点)。
然后,通过一些simpl
获取:
(aeval (optimize_0plus a1)) + (aeval (optimize_0plus a2)) = (aeval a1) + (aeval a2)
并应用归纳假设IHa1
和IHa2
来完成证明。
我的问题是:
我如何告诉Coq恰好应用optimize_0plus
的定义一次,而不做更多也不少?
我已经尝试了simpl optimize_0plus
,但它提供了一个长match
语句,这似乎做得太多了。而且我不喜欢每次使用rewrite
策略来建立一个引理,因为这个计算只是纸和笔的一步。
注意:
1.这与我的earlier question here有关,但有关使用simpl XXX
的答案在这里似乎不起作用。这似乎是一个更复杂的案例。
2.原始网站提供有效的证明。但是,当它开始对a1
等术语进行案例分析时,证据似乎比必要更复杂。
Case "APlus". destruct a1.
SCase "a1 = ANum n". destruct n.
SSCase "n = 0". simpl. apply IHa2.
SSCase "n ≠ 0". simpl. rewrite IHa2. reflexivity.
SCase "a1 = APlus a1_1 a1_2".
simpl. simpl in IHa1. rewrite IHa1.
rewrite IHa2. reflexivity.
SCase "a1 = AMinus a1_1 a1_2".
simpl. simpl in IHa1. rewrite IHa1.
rewrite IHa2. reflexivity.
SCase "a1 = AMult a1_1 a1_2".
simpl. simpl in IHa1. rewrite IHa1.
rewrite IHa2. reflexivity.
所以,我的担心并不是要证明这个简单的定理,而是如何直观地证明这一点,就像我在纸上那样。
- 更新 -
感谢@gallais,我的原始计划不正确,因为可以更改
aeval (optimize_0plus (APlus a1 a2))
到
aeval (APlus (optimize_0plus a1) (optimize_0plus a2))
仅适用于a1
不是ANum 0
的情况。 0
案件必须由destruct a1.
单独处理,与注2中引用的课程网站一样。
但是,对于下面列出的其他案例,我仍然有同样的问题,我认为我原来的计划应该有效:
5 subgoal
SCase := "a1 = APlus a1_1 a1_2" : String.string
Case := "APlus" : String.string
a1_1 : aexp
a1_2 : aexp
a2 : aexp
IHa1 : aeval (optimize_0plus (APlus a1_1 a1_2)) = aeval (APlus a1_1 a1_2)
IHa2 : aeval (optimize_0plus a2) = aeval a2
______________________________________(1/5)
aeval (optimize_0plus (APlus (APlus a1_1 a1_2) a2)) =
aeval (APlus (APlus a1_1 a1_2) a2)
...
______________________________________(5/5)
aeval (optimize_0plus (AMult a1 a2)) = aeval (AMult a1 a2)
对于这5个案例中的每一个,似乎beta
的一个应用程序(optimize_0plus
减少??)应允许我们更改,例如(对于AMinus
)
aeval (optimize_0plus (AMinus a1 a2))
到
aeval (AMinus (optimize_0plus a1) (optimize_0plus a2))
,对吧?
如果是这样,我该如何做一步减少?
注意: 我试过了
Eval cbv beta in (aeval (optimize_0plus (AMinus a1 a2))).
我甚至无法获得aeval (AMinus (optimize_0plus a1) (optimize_0plus a2))
,因为我想在证明中使用Eval
。
答案 0 :(得分:3)
这里的问题是你跳跃依赖的等式是不正确的。情况并非如此:
optimize_0plus (APlus a1 a2) = APlus (optimize_0plus a1) (optimize_0plus a2)
根据您提供的optimize_0plus
的定义:如果a1
为ANum 0
,那么optimize_0plus (APlus a1 a2)
将仅减少为optimize_0plus a2
,而不是APlus
} -headed term。
然而,您试图证明的主要定理确实是正确的,并且可以通过检查a1
来证明:它是ANum 0
(在这种情况下,第一个分支将通过调用{{{ 1}})或者不是(在这种情况下会采用第二个分支)?
根据经验,每次要证明关于由模式匹配/递归调用定义的函数的定理时,您需要经历相同的案例分析/归纳假设系列。这就是函数调用图上通常被称为功能感应或感应。
答案 1 :(得分:1)
我在这里看到两个解决方案:
用你想要的东西陈述一个重写引理,证明它然后再使用它。当你需要进行非常复杂的重写时,这是最好的解决方案,但它不能很好地扩展,因为你需要为每种情况编写一个引理。例如,您可以在此处陈述(并使用simpl
轻松证明):
forall a1 a2, optimize_0plus (APlus a1 a2) = APlus (optimize_0plus a1) (optimize_0plus a2).
如果我没记错的话,simpl
和其他人不会使用粘合剂。您可以使用pattern
策略“提取”要简化的部分,以便simpl
或unfold
仅对表达式的某个子术语执行。你应该阅读the documentation,因为这里解释的时间有点长。
编辑:我忘了谈论replace
策略,它就像rewrite
解决方案一样,但会要求你立即证明这个引理,作为一个子目标。
答案 2 :(得分:1)
我同意让Coq做我们想要的计算并不总是那么容易。但在这里,与你说的相反,第一次重写不仅仅是一个简单的计算步骤。实际上,optimize_0plus
一次破坏了它的参数,但是当它找到APlus _ _
形式的东西时,它需要破坏第一个新参数,所以在这里你需要破坏a1
来计算。 / p>
然而,你的结果仍然是正确的,并且可以被认为是证明初始定理的一个方便的辅助引理。
Lemma optimize_0plus_aux : forall a1 a2,
aeval (optimize_0plus (APlus a1 a2)) =
aeval (APlus (optimize_0plus a1) (optimize_0plus a2)).
Proof.
Admitted.
关于你关于一步计算的初步问题,我有两个技巧:
我知道你不想每次都使用rewrite
,但据我所知,有一个方程式引理是一次应用修复点的最佳方法。请注意,您通常可以使用Functional Scheme
自动创建此引理。这里,
Functional Scheme optimize_0plus_ind := Induction for optimize_0plus Sort Prop.
在极少数情况下,您需要在校样期间展示您不想展开的功能。在这种情况下,您可以使用Opaque <function>
临时使定义不透明。在校对结束时,使用Transparent <function>
再次使其透明。但是,我不认为它是一种好的风格,也不建议使用它。