Coq 使用定义良好的归纳法定义的递归函数进行计算

Coq 使用定义良好的归纳法定义的递归函数进行计算,coq,Coq,当我使用Function在Coq中定义一个非结构化递归函数时,当要求进行特定计算时,结果对象的行为异常。实际上,Eval compute in…指令没有直接给出结果,而是返回一个相当长(通常为170 000行)的表达式。Coq似乎无法计算所有内容,因此返回一个简化(但很长)的表达式,而不仅仅是一个值 问题似乎来自我证明函数生成的义务的方式。首先,我认为问题来自我使用的不透明术语,我将所有引理转换为透明常数。顺便问一下,有没有办法列出术语中出现的不透明术语?或者用其他方法把不透明的引理变成透明的引

当我使用
Function
在Coq中定义一个非结构化递归函数时,当要求进行特定计算时,结果对象的行为异常。实际上,
Eval compute in…
指令没有直接给出结果,而是返回一个相当长(通常为170 000行)的表达式。Coq似乎无法计算所有内容,因此返回一个简化(但很长)的表达式,而不仅仅是一个值

问题似乎来自我证明
函数生成的义务的方式。首先,我认为问题来自我使用的不透明术语,我将所有引理转换为透明常数。顺便问一下,有没有办法列出术语中出现的不透明术语?或者用其他方法把不透明的引理变成透明的引理

我接着说,问题更确切地来自于所使用的顺序是有充分根据的证据。但我得到了奇怪的结果

例如,我通过反复应用
div2
对自然数定义
log2
。定义如下:

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.
这里,引理1是对自然数的有充分根据的归纳的证明。在这里,我可以再次使用已经存在的引理,例如
lt\u wf\u ind
lt\u wf\u rec
lt\u wf\u rec1
位于
Coq.Arith.wf\u nat
中,甚至
位于
的良好基础的lt\u wf
。第一个不起作用,似乎这是因为它是不透明的。另外三个在工作

我试图用自然数的标准归纳法直接证明它,
nat\u 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.
引理1:forall n(P:nat->Prop), (对于所有n,(对于所有p,ppp)->pn)->pn。 证明。 介绍n P H.姿势证明(自然属性(乐趣n=>所有P,PP))。 H0中的siml。使用(n:=sn)应用H0。 -介绍。反转H1。 -介绍。反转H2。 +应用H.精确H1。 +应用H1。假设。 -应用leu\n。 定义
有了这个证明(以及它的一些变体),
log2
具有相同的奇怪行为。这个证明似乎只使用透明对象,所以问题可能不在这里


如何定义返回特定值的可理解结果的
函数

Coq中有充分依据的递归定义的函数的缩减行为通常不是很好,即使您声明证明是透明的。原因是,有充分根据的论点通常需要用复杂的证明条件来完成。由于这些证明术语最终出现在有充分依据的递归定义中,“简化”您的函数将使所有这些证明术语出现,正如您所注意到的

更容易依靠自定义策略和引理来减少以这种方式定义的函数。首先,我建议使用
程序固定点
而不是
函数
,因为后者更老,而且(我认为)维护得不太好。因此,您将得到如下定义:

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\u siml
策略来简化对
log2
的调用。下面是一个例子:

Lemma foo : log2 4 = 2.
Proof.
  program_simpl.
Qed.

我已经设法找到了引起麻烦的地方:在
lemma1
中的
inversionh2.
。事实证明,我们不需要案例分析,
直觉
可以完成证明(它在
H2
上的模式不匹配):

我相信 Xavier Leroy的博客文章解释了这种行为

它消除了头部之间相等的证明,然后在尾部上递归,最后决定是否生成左或右。这使得最终结果的左/右数据部分依赖于证明项,通常不会减少

在我们的例子中,我们消除了
引理1
证明中的不等式证明(
倒置H2.
),而
函数
机制使我们的计算依赖于证明项。因此,当n>1时,计算器无法继续

引理主体中的倒数H1。
不影响计算的原因是,对于
n=0
n=1
log2n
匹配
表达式中定义为基本情况

为了说明这一点,让我举一个例子,我们可以防止对我们选择的任何值
n
n+1
计算
log2n
,其中
n>1
,而不是其他任何值

如果你在
log2
的定义中使用这个修改过的引理,你会发现除了
n=4
n=5
之外,它到处都在计算。几乎在所有地方——使用大的
nat
s计算可能会导致堆栈溢出或分段错误,正如Coq警告我们的那样:

警告:使用时发生堆栈溢出或分段错误 nat中的大量数据(观察到的阈值可能从5000到70000不等 取决于系统限制和执行的命令)

为了使
log2
适用于
n=4
n=5
,即使对于上述“有缺陷”的证明,我们也可以这样修改
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.
在末尾添加必要的证据


“有充分根据的”证明必须是透明的,并且不能依赖于证明对象上的模式匹配,因为
函数
机制实际上使用
lt\u wf
引理来计算递减终止保护。如果我们看一下由
Eval
生成的术语(在求值无法生成
nat
的情况下),我们将看到以下内容:

fix Ffix (x : nat) (x0 : Acc (fun x0 x1 : nat => S x0 <= x1) x) {struct x0}

fix-Ffix(x:nat)(x0:Acc(fun-x0-x1:nat=>S-x0好的,这是另一种选择。但是我想知道这

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.
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.
Function log2 n {wf lt n} :=
  match n with
  | 0 => 0
  | 1 => 0
  | 4 => 2
  | 5 => 2
  | n => S (log2 (Nat.div2 n))
  end.
fix Ffix (x : nat) (x0 : Acc (fun x0 x1 : nat => S x0 <= x1) x) {struct x0}