建立树并减少修正论点

时间:2019-05-14 13:56:05

标签: coq

我正在尝试在Coq中使用以下函数来实现使用n个元素构建Braun树的函数,但是Coq给了我一个错误,它无法猜测fix参数的减小:

SELECT phone 
  FROM members 
 WHERE phone REGEXP '^[[:alpha:]]+$'

我知道不是问题出现在偶数和奇数情况下,因为当我删除偶数/奇数情况时,它给出了相同的错误:

Fixpoint copy (x : V) (n : nat) : BraunTree :=
let
  fix copy2 (a : V) (i : nat) : (BraunTree * BraunTree) := 
    match i with
    | 0 => (T a E E,E) 
    | _ => match Nat.odd i with
           | true => let m := ((i - 1) / 2) in 
                     let (s,t) := copy2 a m in
                     ((T a s t),(T a t t)) 
           | false => let m := ((i - 2) / 2) in
                      let (s,t) := copy2 a m in
                     ((T a s s),(T a s t)) 
           end
    end
in
match copy2 x n with
|(_,snd) => snd
end.

我怎样才能说服Coq我实际上是在减少争论?

BraunTree的编辑类型:

Fixpoint copy (x : V) (n : nat) : BraunTree :=
let
  fix copy2 (a : V) (i : nat) : (BraunTree * BraunTree) := 
    match i with
    | 0 => (T a E E,E) 
    | _ => let m := ((i - 1) / 2) in 
                     let (s,t) := copy2 a m in
                     ((T a s t),(T a t t)) 
    end
in
match copy2 x n with
|(_,snd) => snd
end.

1 个答案:

答案 0 :(得分:4)

Fixpoint / fix仅允许对语法上较小的参数进行递归调用。

Fixpoint example (n : nat) :=
  ... (* There must be a match on [n] somewhere *)
    ... match n with
        | O => base_case (* no recursive call allowed *)
        | S m =>
          ... (example m)
          (* We can only call [example] on [m], or some even smaller value obtained by matching on [m] *)
         end ...

尤其是,不允许对通过某些任意函数(在这种情况下,div中的subcopy2 a ((i-1) / 2)获得的值进行递归调用。

这是三个选项:

  1. 选择自然数的另一种表示形式,以便其上的模式匹配自然分解为所需定义的不同分支(基本情况(零),偶数,奇数)。

  2. 利用递归深度实际上受n限制的事实,因此我们可以将n用作“燃料”,我们知道在完成之前实际上并不会耗尽它。 / p>

  3. 巧妙地提取出递减参数的子项以进行递归调用。与以前的解决方案相比,此解决方案的通用性和鲁棒性较低。与终止检查器的对抗要困难得多。


替代表示形式

我们有三种情况:零,偶数和奇数。幸运的是,标准库的类型具有几乎相同的结构,positive

Inductive positive :=    (* p > 0 *)
| xH                     (* 1 *)
| xI (p : positive)      (* 2p + 1 *)
| xO (p : positive)      (* 2p     *)
.

将类型positive指向一个额外的零,我们得到N

Inductive N :=
| N0                      (* 0 *)
| Npos (p : positive)     (* p > 0 *)
.

还有一个转换函数N.of_nat : nat -> N,尽管如果转换变得很烦人,最好在各处使用N而不是nat

最终定义是从对N的案例分析开始的,揭示positive数字的案例是用fix点处理的,基本案例是1而不是0。我们必须转移一些细节,因为偶数情况是2p而不是2p + 2,因此我们必须做(i-1,i)而不是一对大小为(i + 1,i)的树。但是总的来说,递归案例仍然很自然地符合非正式规范:

Require Import NArith PArith.

Parameter V : Type.

Inductive BraunTree : Type :=
| E : BraunTree
| T: V -> BraunTree -> BraunTree -> BraunTree.

Definition copy (x : V) (n : N) : BraunTree :=
  match n with
  | N0 => E
  | Npos p =>
    let
      (* copy2 a i : a tree of (i-1) copies of a, and another of i copies of a *)
      fix copy2 (a : V) (i : positive) : (BraunTree * BraunTree) := 
        match i with

        | xH => (* i = 1 *)
          (E, T a E E)

        | xI p => (* i = 2p + 1 *)
          let (s,t) := copy2 a p in
          ((T a t s),(T a t t))

        | xO p => (* i = 2p *)
          let (s,t) := copy2 a p in
          ((T a s s),(T a t s))

        end
    in
    match copy2 x p with
    |(_,snd) => snd
    end
  end.

燃料足够

我们向fix添加燃料作为递减参数。如果n = i = 0,我们就用光了,所以我们知道结果应该是什么。

(* note: This doesn't need to be a Fixpoint *)
Definition copy (x : V) (n : nat) : BraunTree :=
let
  fix copy2 (a : V) (n : nat) (i : nat) : (BraunTree * BraunTree)  := 
    match n with
    | O => (T a E E,E)
    | S n' =>
      match i with
      | O => (T a E E,E) 
      | _ =>
        if Nat.odd i then
          let m := div2 ((i - 1) / 2) in 
          let (s,t) := copy2 a n' m in
          ((T a s t),(T a t t)) 
        else
          let m := div2 ((i - 2) / 2) in 
          let (s,t) := copy2 a n' m in
          ((T a s s),(T a s t)) 
      end
    end
in
match copy2 x n n with
|(_,snd) => snd
end.

在以下情况下效果很好:

  • 我们可以计算所需的燃料量;
  • 当我们用完燃料时,有一个可以预测的答案。

如果这两个假设中的任何一个都不成立,则需要用option填充代码。


嵌套递归

如前所述,Coq对于减少参数有严格的规定。通常的解释是,我们只能递归调用通过递减参数(或传递式,其子项之一)通过模式匹配获得的子项。

一个明显的限制是,因为条件是句法上的(即Coq查看定义以跟踪递减参数的出处),所以参数n最多只能减少一个常数(与相对于n),因为在定义中只有match个有限。特别是,无法对除以2的结果进行递归调用,因为这表示减少n/2,即n中线性的值。

不管好坏,Coq的终止标准实际上比这聪明一点:可以将递减的参数传递给嵌套的固定点,然后通过它跟踪“子项”关系。

无缺点划分

实际上,Peano nat的除法可以这样定义:Coq可以判断结果是股息的子项:

Definition div2 (n : nat) :=
  let fix d2 (n1 : nat) (n2 : nat) {struct n1} :=
    match n2 with
    | S (S n2') =>
      match n1 with
      | O => n1
      | S n1' => d2 n1' n2'
      end
    | _ => n1
    end
  in d2 n n.

这个想法是写一个fix的两个参数点(有点像燃料解决方案),它们的起点是相等的(d2 n n),我们删除了两个我们从另一个(S中删除的每个一个 n2中的一个S中的n1个构造函数。重要详细信息:

  • 在所有非递归情况下,我们返回n1(无论如何返回 not 0),然后保证它是以下条件的子项最顶部的n

  • 并且该函数必须在n1(我们返回的术语)中递减,而不是在n2(Coq仅跟踪递减参数的子项)中递减。

确保div2 nn的子项(不是 strict子项(或 proper子项))的所有原因,因为{{1 }}可以是n

这与以前的基于燃料的解决方案有相似之处,但是在这里,递减的论点比欺骗类型检查器的设备更为重要。

这项技术是无缺点编程的一种变体。 (请注意,尽管约束条件与文献中讨论的约束条件并不完全相同,例如,当重点是避免内存分配而不是通过结构井来确保终止时,基础。)

结论:O的定义

一旦有了copy,我们就可以通过一些调整来定义div2,以获得copyi-1作为{{的正确子项 1}},再次通过模式匹配。下面,i-2ii'的适当子项(通过目视检查),i''idiv2 i'和{ {1}}(根据div2 i''的定义)。通过传递性,它们是i'的适当子项,因此终止检查器接受。

i''