【问题标题】:Nested recursion and `Program Fixpoint` or `Function`嵌套递归和“程序固定点”或“函数”
【发布时间】:2018-04-01 00:35:41
【问题描述】:

我想在 Coq 中使用 Program FixpointFunction 定义以下函数:

Require Import Coq.Lists.List.
Import ListNotations.
Require Import Coq.Program.Wf.
Require Import Recdef.

Inductive Tree := Node : nat -> list Tree -> Tree.

Fixpoint height (t : Tree) : nat :=
  match t with
   | Node x ts => S (fold_right Nat.max 0 (map height ts))
  end.

Program Fixpoint mapTree (f : nat -> nat) (t : Tree)  {measure (height t)} : Tree :=
  match t with 
    Node x ts => Node (f x) (map (fun t => mapTree f t) ts)
  end.
Next Obligation.

不幸的是,此时我有证明义务height t < height (Node x ts) 不知道tts 的成员。

Function 类似,而不是Program Fixpoint,只是Function 检测到问题并中止定义:

Error:
the term fun t : Tree => mapTree f t can not contain a recursive call to mapTree

我希望得到In t ts → height t < height (Node x ts)的证明义务。

有没有一种不涉及重构函数定义的方法? (例如,我知道需要在此处内联 map 的定义的变通方法——我想避免这些。)

伊莎贝尔

为了证明这种期望是正确的,让我展示当我在 Isabelle 中使用 function 命令(与 Coq 的 Function 命令相关)时会发生什么:

theory Tree imports Main begin

datatype Tree = Node nat "Tree list"

fun height where
  "height (Node _ ts) = Suc (foldr max (map height ts) 0)"

function mapTree where
  "mapTree f (Node x ts) = Node (f x) (map (λ t. mapTree f t) ts)"
by pat_completeness auto

termination
proof (relation "measure (λ(f,t). height t)")
  show "wf (measure (λ(f, t). height t))" by auto
next
  fix f :: "nat ⇒ nat" and x :: nat  and ts :: "Tree list" and t
  assume "t ∈ set ts"
  thus "((f, t), (f, Node x ts))  ∈ measure (λ(f, t). height t)"
    by (induction ts) auto
qed

在终止证明中,我得到假设t ∈ set ts

请注意,Isabelle 在这里不需要手动终止证明,以下定义可以正常工作:

fun mapTree where
  "mapTree f (Node x ts) = Node (f x) (map (λ t. mapTree f t) ts)"

这是因为map 函数有一个“同余引理”的形式

xs = ys ⟹ (⋀x. x ∈ set ys ⟹ f x = g x) ⟹ map f xs = map g ys

function命令用来找出终止证明只需要考虑t ∈ set ts..

如果这样的引理不可用,例如因为我定义了

definition "map' = map"

并在mapTree 中使用它,我得到了与 Coq 中相同的不可证明的证明义务。我可以通过为map' 声明一个全等引理使其再次工作,例如使用

declare map_cong[folded map'_def,fundef_cong]

【问题讨论】:

    标签: recursion coq isabelle termination


    【解决方案1】:

    您现在可以使用 Equations 执行此操作,并使用 structural nested recursionwell-founded recursion 自动获得正确的消除原理

    【讨论】:

    • 我喜欢map_In 的定义不像我的回答那样需要证明!虽然我不明白为什么——递归调用map_In 的参数中的_ 是如何解决的? (大概是or_intror H这个词。)
    • 空洞是使用在此处调用 auto 的 program_simpl 策略自动填充的,我想这足以解决它们。否则你得到证明义务,没有魔法,证明还在!
    【解决方案2】:

    一般来说,最好避免这个问题。但如果真的想获得伊莎贝尔给你的证明义务,这里有一个办法:

    在 Isabelle 中,我们可以给出一个外部引理,该引理统计 map 仅将其参数应用于给定列表的成员。在 Coq 中,我们不能在外部引理中做到这一点,但我们可以在类型中做到这一点。所以代替普通类型的地图

    forall A B, (A -> B) -> list A -> list B
    

    我们希望类型说“f 只应用于列表的元素:

    forall A B (xs : list A), (forall x : A, In x xs -> B) -> list B
    

    (需要重新排序参数,以便f 的类型可以提及xs)。

    编写这个函数并不简单,我发现使用证明脚本更容易:

    Definition map {A B} (xs : list A) (f : forall (x:A), In x xs -> B) : list B.
    Proof.
      induction xs.
      * exact [].
      * refine (f a _ :: IHxs _).
        - left. reflexivity.
        - intros. eapply f. right. eassumption.
    Defined.
    

    但你也可以“手写”:

    Fixpoint map {A B} (xs : list A) : forall (f : forall (x:A), In x xs -> B), list B :=
      match xs with
       | [] => fun _ => []
       | x :: xs => fun f => f x (or_introl eq_refl) :: map xs (fun y h => f y (or_intror h))
      end.
    

    无论哪种情况,结果都很好:我可以在mapTree中使用这个函数,即

    Program Fixpoint mapTree (f : nat -> nat) (t : Tree)  {measure (height t)} : Tree :=
      match t with 
        Node x ts => Node (f x) (map ts (fun t _ => mapTree f t))
      end.
    Next Obligation.
    

    我不需要对f 的新参数做任何事情,但它会根据需要显示在终止证明义务中,如In t ts → height t < height (Node x ts)。所以我可以证明并定义mapTree

      simpl.
      apply Lt.le_lt_n_Sm.
      induction ts; inversion_clear H.
      - subst. apply PeanoNat.Nat.le_max_l.
      - rewrite IHts by assumption.
        apply PeanoNat.Nat.le_max_r.
    Qed.
    

    不幸的是,它只适用于Program Fixpoint,不适用于Function

    【讨论】:

      【解决方案3】:

      在这种情况下,您实际上不需要充分通用的有根据的递归:

      Require Import Coq.Lists.List.
      
      Set Implicit Arguments.
      
      Inductive tree := Node : nat -> list tree -> tree.
      
      Fixpoint map_tree (f : nat -> nat) (t : tree) : tree :=
        match t with
        | Node x ts => Node (f x) (map (fun t => map_tree f t) ts)
        end.
      

      Coq 能够自行判断对 map_tree 的递归调用是在严格的子项上执行的。然而,证明这个函数的任何事情都很困难,因为为tree 生成的归纳原理没有用:

      tree_ind : 
        forall P : tree -> Prop, 
          (forall (n : nat) (l : list tree), P (Node n l)) ->
          forall t : tree, P t
      

      这与您之前描述的问题基本相同。幸运的是,我们可以通过用证明项证明我们自己的归纳原理来解决这个问题。

      Require Import Coq.Lists.List.
      Import ListNotations.
      
      Unset Elimination Schemes.
      Inductive tree := Node : nat -> list tree -> tree.
      Set Elimination Schemes.
      
      Fixpoint tree_ind
        (P : tree -> Prop)
        (IH : forall (n : nat) (ts : list tree),
                fold_right (fun t => and (P t)) True ts ->
                P (Node n ts))
        (t : tree) : P t :=
        match t with
        | Node n ts =>
          let fix loop ts :=
            match ts return fold_right (fun t' => and (P t')) True ts with
            | [] => I
            | t' :: ts' => conj (tree_ind P IH t') (loop ts')
            end in
          IH n ts (loop ts)
        end.
      
      Fixpoint map_tree (f : nat -> nat) (t : tree) : tree :=
        match t with
        | Node x ts => Node (f x) (map (fun t => map_tree f t) ts)
        end.
      

      Unset Elimination Schemes 命令阻止 Coq 为tree 生成其默认(且无用)的归纳原则。归纳假设中fold_right 的出现简单地表达了谓词P 对出现在ts 中的每棵树t' 成立。

      这是一个你可以使用这个归纳原理证明的陈述:

      Lemma map_tree_comp f g t :
        map_tree f (map_tree g t) = map_tree (fun n => f (g n)) t.
      Proof.
        induction t as [n ts IH]; simpl; f_equal.
        induction ts as [|t' ts' IHts]; try easy.
        simpl in *.
        destruct IH as [IHt' IHts'].
        specialize (IHts IHts').
        now rewrite IHt', <- IHts.
      Qed.
      

      【讨论】:

      • “在这种情况下,你实际上不需要充分通用的有根据的递归” 我想我把这个例子简化得太多了。它之所以有效,是因为map 是用本地固定点仔细定义的,对,终止检查器可以看穿它。如果我在表现不佳的情况下进行递归,我真的需要有充分根据的递归怎么办?
      • 另外,感谢Unset Elimination Schemes - 当您使用induction t 时,这是否让 Coq 选择了tree_ind
      • Coq 总是选择 foo_ind 来对名为 foo 的归纳类型执行归纳。
      • 哈,这很可爱,很高兴知道。是否有混淆 Coq 证明竞赛?
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2016-07-12
      • 2021-12-04
      • 2014-08-07
      • 1970-01-01
      • 1970-01-01
      • 2018-07-25
      • 1970-01-01
      相关资源
      最近更新 更多