当我使用Function
在Coq中定义非结构递归函数时,在询问特定计算时,结果对象的行为很奇怪。实际上,Eval compute in ...
指令不是直接给出结果,而是返回相当长的(通常为170 000行)表达式。似乎Coq无法评估所有内容,因此返回一个简化(但很长)的表达式,而不仅仅是一个值。
问题似乎来自于我证明Function
所产生的义务的方式。首先,我认为问题来自我使用的不透明术语,并且我将所有的引理转换为透明常量。那么,有没有办法列出一个术语中出现的不透明术语?或者任何其他方式将不透明的引理变成透明的引理?
然后我说,问题来自于所使用的订单有充分根据的证据。但我得到了奇怪的结果。
例如,我通过重复应用log2
在自然数上定义div2
。这是定义:
Function log2 n {wf lt n} :=
match n with
| 0 => 0
| 1 => 0
| n => S (log2 (Nat.div2 n))
end.
我有两个证明义务。第一个检查n
是否尊重递归调用中的lt
关系,并且可以很容易地证明。
forall n n0 n1 : nat, n0 = S n1 -> n = S (S n1) -> Nat.div2 (S (S n1)) < S (S n1)
intros. apply Nat.lt_div2. apply le_n_S. apply le_0_n.
第二个检查lt
是否是有根据的命令。这已经在标准库中得到证明。相应的引理是Coq.Arith.Wf_nat.lt_wf
。
如果我使用此证明,则生成的函数表现正常。 Eval compute in log24 10.
会返回3
。
但如果我想自己做证明,我并不总是得到这种行为。首先,如果我用Qed
而不是Defined
结束证明,计算结果(即使是小数字)也是一个复杂的表达式而不是单个数字。所以我使用Defined
并尝试仅使用透明的引理。
Lemma lt_wf2 : well_founded lt.
Proof.
unfold well_founded. intros n.
apply (lemma1 n). clear n.
intros. constructor. apply H.
Defined.
在这里,引理1证明了对自然数的有根据的归纳。在这里,我可以再次使用现有的词条,例如位于lt_wf_ind
的{{1}},lt_wf_rec
,lt_wf_rec1
,甚至Coq.Arith.Wf_nat
。第一个不起作用,似乎这是因为它是不透明的。其他三个人都在工作。
我尝试使用自然数well_founded_ind lt_wf
上的标准归纳法直接证明它。这给出了:
nat_ind
使用此证明(以及它的一些变体),Lemma lemma1 : forall n (P:nat -> Prop),
(forall n, (forall p, p < n -> P p) -> P n) -> P n.
Proof.
intros n P H. pose proof (nat_ind (fun n => forall p, p < n -> P p)).
simpl in H0. apply H0 with (n:=S n).
- intros. inversion H1.
- intros. inversion H2.
+ apply H. exact H1.
+ apply H1. assumption.
- apply le_n.
Defined.
具有相同的奇怪行为。而且这个证据似乎只使用透明物体,所以也许问题就不存在了。
如何定义能够在特定值上返回可理解结果的log2
?
答案 0 :(得分:3)
我设法指出导致麻烦的地方:inversion H2.
lemma1
。intuition
。事实证明,我们不需要进行案例分析,H2
可以完成证明(它不会在Lemma lemma1 : forall n (P:nat -> Prop),
(forall n, (forall p, p < n -> P p) -> P n) -> P n.
Proof.
intros n P H. pose proof (nat_ind (fun n => forall p, p < n -> P p)).
simpl in H0. apply H0 with (n:=S n).
- intros. inversion H1.
- intros. intuition.
- apply le_n.
Defined.
上进行模式匹配):
lemma1
如果我们将log2 10
与此证明一起使用,则3
的计算会产生lt_wf2
。
顺便说一句,这是我的Lemma lt_wf2 : well_founded lt.
Proof.
unfold well_founded; intros n.
induction n; constructor; intros k Hk.
- inversion Hk.
- constructor; intros m Hm.
apply IHn; omega.
(* OR: apply IHn, Nat.lt_le_trans with (m := k); auto with arith. *)
Defined.
版本(它也可以让我们计算):
inversion H2.
我相信 Xavier Leroy撰写的Using Coq's evaluation mechanisms in anger博客文章解释了这种行为。
它在递归尾部之前消除了头部之间相等的证明,并最终决定是产生左边还是右边。这使得最终结果的左/右数据部分依赖于证明项,这通常不会减少!
在我们的案例中,我们在lemma1
的证明中消除了不等式证明(Function
),而inversion H1.
机制使我们的计算依赖于证明项。因此,当n> 1时,评估者不能继续进行。 1.
引理正文中n = 0
的原因并不影响计算,因为n = 1
和log2 n
,match
定义在{{ 1}}表达式作为基本情况。
为了说明这一点,让我举例说明我们可以阻止对我们选择的任何值log2 n
和n
进行n + 1
评估,其中n > 1
和< em>其他地方!
Lemma lt_wf2' : well_founded lt.
Proof.
unfold well_founded; intros n.
induction n; constructor; intros k Hk.
- inversion Hk. (* n = 0 *)
- destruct n. intuition. (* n = 1 *)
destruct n. intuition. (* n = 2 *)
destruct n. intuition. (* n = 3 *)
destruct n. inversion Hk; intuition. (* n = 4 and n = 5 - won't evaluate *)
(* n > 5 *)
constructor; intros m Hm; apply IHn; omega.
Defined.
如果您在log2
的定义中使用此修改后的引理,则会看到它计算除n = 4
和n = 5
之外的所有位置。好吧,几乎无处不在 - 大型nat
的计算可能导致堆栈溢出或分段错误,Coq警告我们:
警告:使用时发生堆栈溢出或分段错误 nat中的大数(观察到的阈值可能在5000到70000之间变化) 取决于您的系统限制和执行的命令。)
并使log2
适用于n = 4
和n = 5
,即使对于上述&#34;有缺陷的&#34;证明,我们可以像这样修改log2
Function log2 n {wf lt n} :=
match n with
| 0 => 0
| 1 => 0
| 4 => 2
| 5 => 2
| n => S (log2 (Nat.div2 n))
end.
在最后添加必要的证明。
<小时/> &#34;有根据的&#34;证明必须是透明的,并且不能依赖于证明对象上的模式匹配,因为
Function
机制实际上使用lt_wf
引理来计算减少的终止守卫。如果我们查看Eval
生成的术语(在评估无法生成nat
的情况下),我们会看到以下内容:
fix Ffix (x : nat) (x0 : Acc (fun x0 x1 : nat => S x0 <= x1) x) {struct x0}
很容易看到x0 : Prop
,因此在将功能程序log2
提取到OCaml中时会被删除,但Coq的内部评估机制必须使用它确保终止。
答案 1 :(得分:2)
即使您声明您的证明是透明的,Coq中有根据的递归定义的函数的减少行为通常也不是很好。这样做的原因是,有充分理由的论据通常需要用复杂的证明术语来完成。由于这些证明条款最终出现在有根据的递归定义中,因此简化了#34;正如您所注意到的,您的函数将使所有这些证明术语出现。
依靠自定义策略和引理来减少以这种方式定义的函数更容易。首先,我建议赞成Program Fixpoint
超过Function
,因为后者更老,而且(我认为)维护得不太好。因此,您最终会得到如下定义:
Require Import Coq.Numbers.Natural.Peano.NPeano.
Require Import Coq.Program.Wf.
Require Import Coq.Program.Tactics.
Program Fixpoint log2 n {wf lt n} :=
match n with
| 0 => 0
| 1 => 0
| n => S (log2 (Nat.div2 n))
end.
Next Obligation.
admit.
Qed.
现在,您只需使用program_simpl
策略来简化对log2
的调用。这是一个例子:
Lemma foo : log2 4 = 2.
Proof.
program_simpl.
Qed.