如何在Coq中执行一次计算?

时间:2015-11-25 04:14:55

标签: coq coq-tactic

我有一个下面的证据,还有三个子目标要去。证明是关于在here的简单算术语言中优化plus 0optimize_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)

并应用归纳假设IHa1IHa2来完成证明。

我的问题是:

我如何告诉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

3 个答案:

答案 0 :(得分:3)

这里的问题是你跳跃依赖的等式是不正确的。情况并非如此:

optimize_0plus (APlus a1 a2) = APlus (optimize_0plus a1) (optimize_0plus a2)

根据您提供的optimize_0plus的定义:如果a1ANum 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策略“提取”要简化的部分,以便simplunfold仅对表达式的某个子术语执行。你应该阅读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>再次使其透明。但是,我不认为它是一种好的风格,也不建议使用它。