Вложенная рекурсия и `Program Fixpoint` или` Function`

Я хотел бы определить следующую функцию, используя Program Fixpoint или Function в Coq:

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), не зная, что t является членом ts.

Аналогично с 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, которая (AFAIK) связана с командой 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.

Обратите внимание, что Изабель не требует подтверждения об отключении вручную, и следующее определение работает нормально:

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]

person Joachim Breitner    schedule 19.10.2017    source источник


Ответы (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 выполняется для каждого дерева t', появляющегося в ts.

Вот утверждение, которое вы можете доказать, используя этот принцип индукции:

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.
person Arthur Azevedo De Amorim    schedule 19.10.2017
comment
«В этом случае вам на самом деле не нужна хорошо обоснованная рекурсия во всей ее общности». Думаю, я слишком упростил пример. Это работает, потому что map тщательно определен с локальной фиксированной точкой, верно, и программа проверки завершения видит это. Что, если я рекурсивно использую что-то не так хорошо, и мне действительно нужна хорошо обоснованная рекурсия? - person Joachim Breitner; 20.10.2017
comment
Кроме того, спасибо за Unset Elimination Schemes - это как-то заставляет Coq поднять tree_ind, когда вы делаете induction t? - person Joachim Breitner; 20.10.2017
comment
Coq всегда выбирает foo_ind для индукции индуктивного типа с именем foo. - person Arthur Azevedo De Amorim; 20.10.2017
comment
Ха, это мило, и это полезно знать. Существуют ли запутанные конкурсы доказательств Coq? - person Joachim Breitner; 20.10.2017

Теперь вы можете сделать это с помощью Equations и автоматически получить правильный принцип исключения, используя структурная вложенная рекурсия или хорошо обоснованная рекурсия

person Matthieu Sozeau    schedule 22.11.2017
comment
Мне нравится, что определение map_In не требует доказательств, как в моем ответе! Хотя я не понимаю, почему - как решается _ в аргументе рекурсивного вызова map_In? (Предположительно к сроку or_intror H.) - person Joachim Breitner; 22.11.2017
comment
Дыры заполняются автоматически с использованием тактики program_simpl, которая вызывает здесь auto, я думаю, этого достаточно, чтобы их решить. В противном случае вы получите доказательства обязательств, никакой магии, доказательства все еще есть! - person Matthieu Sozeau; 24.11.2017

В общем, рекомендуется избегать этой проблемы. Но если кто-то действительно хочет получить обязательство по доказательству, которое дает вам Изабель, вот способ:

В Изабель мы можем дать внешнюю лемму, которая показывает, что 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.

person Joachim Breitner    schedule 21.10.2017