Доказательство разрешимости для типа данных, который включает вектор - PullRequest
2 голосов
/ 25 марта 2019

Я пытаюсь работать с типом данных, который представляет выражения в некотором контексте универсальной алгебры. Обычный способ выразить это в (ручке и на бумаге) математике состоит в том, что у вас есть набор функциональных символов F вместе с функцией арности. Выражение - это дерево, в котором каждый узел помечен символом функции и имеет столько же дочерних элементов, сколько его арность. В этом конкретном примере у меня также есть набор атомарных переменных, которые вводятся явно как термины.

Довольно ясно, как записать это с помощью Coq (у меня есть фрагмент кода внизу), но я хотел бы доказать какой-то результат разрешимости. Мне удалось доказать разрешимость для векторов («Если у меня есть разрешимость по A, то я могу получить разрешимость по VectorDef.t A n»), но я не могу понять, как сделать то же самое для моего типа дерева.

Я попытался сделать явную рекурсию по структуре дерева, но в итоге мне пришлось вызывать мою функцию «разрешимый вектор», которая не проходит проверку завершения. Это разумно, поскольку векторной функции предполагается, что ей будет назначен дискриминатор для произвольных элементов ее базового типа, и это, очевидно, не до конца!

Я не могу понять, как сказать Coq, что (по индукции) у меня есть разрешимость для некоторых терминов, и это единственные термины, которые появляются в рассматриваемых векторах. Есть ли стандартная уловка для такого рода вещей?

Ниже приведены типы данных:

Require Vectors.VectorDef.

Definition vec := VectorDef.t.

Section VTree.
  (* If it helps, I have a definition for this function *)
  Variable dec_vec : forall A : Type,
                     (forall x y : A, {x = y} + {x <> y}) ->
                     forall (n : nat) (v v' : vec A n), {v = v'} + {v <> v'}.

  Variable V : Set.
  Variable F : Set.
  Variable a : F -> nat.

  Inductive VTree : Type :=
  | varTerm : V -> VTree
  | funTerm (f : F) (ts : vec VTree (a f)) : VTree.

  Section DecVTree.
    Hypothesis decV : forall x y : V, {x = y} + {x <> y}.
    Hypothesis decF : forall x y : F, {x = y} + {x <> y}.

    Definition decVTree : forall x y : VTree, {x = y} + {x <> y}.
      (* ??? *)

Ответы [ 2 ]

1 голос
/ 25 марта 2019

Хотя Ли-Яо сделал несколько полезных замечаний, зависимые типы не так уж и плохи! Оказывается, причина, по которой мой предыдущий сценарий не работал, заключается в том, что я использовал Qed вместо Defined, чтобы закончить доказательство разрешимости для векторов.

Вот полное рабочее доказательство:

Require Vectors.VectorDef.
Require Import Logic.Eqdep_dec.
Require Import PeanoNat.

Definition vec := VectorDef.t.

Section dec_vec.
  Variable A : Type.
  Hypothesis decA : forall x y : A, {x = y} + {x <> y}.

  Definition dec_vec {n} (v v' : vec A n) : {v = v'} + {v <> v'}.
    refine (VectorDef.rect2 (fun _ x y => {x = y} + {x <> y})
                            (left (eq_refl))
                            (fun n v v' veq a a' => _)
                            v v').
    - destruct (decA a a') as [ eqaH | neaH ].
      + rewrite <- eqaH; clear eqaH a'.
        destruct veq as [ eqvH | nevH ].
        * rewrite <- eqvH. apply left. exact eq_refl.
        * apply right. intro consH. inversion consH.
          exact (nevH (inj_pair2_eq_dec nat Nat.eq_dec (vec A) n v v' H0)).
      + apply right.
        intro consH. inversion consH. contradiction.
  Defined.
End dec_vec.

Section VTree.
  Variable V : Set.
  Variable F : Set.
  Variable a : F -> nat.

  Inductive VTree : Type :=
  | varTerm : V -> VTree
  | funTerm (f : F) (ts : vec VTree (a f)) : VTree.

  Section DecVTree.
    Hypothesis decV : forall x y : V, {x = y} + {x <> y}.
    Hypothesis decF : forall x y : F, {x = y} + {x <> y}.

    Lemma varTerm_ne_funTerm v f ts : varTerm v <> funTerm f ts.
    Proof.
      intros eqH. inversion eqH.
    Qed.

    Fixpoint decVTree (x y : VTree) : {x = y} + {x <> y}.
      refine (match x, y with
              | varTerm v, varTerm v' => _
              | varTerm v, funTerm f ts => _
              | funTerm f ts, varTerm v => _
              | funTerm f ts, funTerm f' ts' => _
              end
             ).
      - destruct (decV v v') as [ eqH | neH ].
        + exact (left (f_equal varTerm eqH)).
        + enough (H: varTerm v <> varTerm v');
            try (exact (right H)).
          injection; tauto.
      - exact (right (varTerm_ne_funTerm v f ts)).
      - exact (right (not_eq_sym (varTerm_ne_funTerm v f ts))).
      - destruct (decF f f') as [ feqH | fneH ].
        + revert ts'. rewrite <- feqH. clear feqH; intro ts'.
          destruct (dec_vec VTree decVTree ts ts') as [ tseqH | tsneH ].
          * apply left. apply f_equal. exact tseqH.
          * apply right. intro funH. inversion funH.
            exact (tsneH (inj_pair2_eq_dec
                            F decF (fun f => vec VTree (a f)) f ts ts' H0)).
        + enough (H: funTerm f ts <> funTerm f' ts');
            try (exact (right H)).
          injection; tauto.
    Qed.
  End DecVTree.
End VTree.
1 голос
/ 25 марта 2019

У этой проблемы есть два сложных аспекта.

  1. Программирование с независимой типизацией с индексированными типами в Coq
  2. Вложенные рекурсивные типы

Программирование с независимой типизациейс индексированными типами в Coq

Под «индексированным типом» я имею в виду конкретно индуктивные типы, такие как Vector.t, где конструкторы уточняют некоторые аргументы типа.Эти аргументы называются индексами и должны появляться между : и := в сигнатуре типа:

Inductive Vector.t (A : Type) : nat (* <- index *) -> Type :=
| nil : Vector.t A 0
| cons : A -> forall n, Vector.t A n -> Vector.t A (S n).

Индексированные индуктивные типы очень полезны для определения предложений, где термины не имеют значения.Но для фактических данных короткая история здесь: не делайте этого.Это технически возможно, но это очень глубокая кроличья нора, и работать с ней в целом довольно сложно, во многом потому, что зависимое сопоставление с образцом в Coq является такой не интуитивной конструкцией.Например, см. Этот пост в блоге: https://homes.cs.washington.edu/~jrw12/dep-destruct.html

Менее экстремальное решение - отказаться от других «зависимо-типизированных» аспектов этой программы.Следующим кандидатом на рубку здесь является sumbool ({ _ } + { _ }).Если функции (и параметры) вместо этого возвращают bool, это делает их достаточно простыми для определения (* кашель *, см. Следующий раздел).Доказательство их правильности все еще является проблемой, но, по крайней мере, у вас есть что вычислить.

Две основные альтернативы индуктивным индексированным типам:

  • Просто используйте плоскую версию (list вместо vec), отказавшись от некоторых гарантий «по конструкции».

  • Сделайте тип функцией индексов как Definition (или Fixpoint)вместо Inductive.Здесь мы используем unit и prod в качестве строительных блоков для таких типов, но вам, возможно, придется придумать свои собственные для более сложных типов.Потребуется много зависимого сопоставления с образцом.

    Fixpoint vec (A : Type) (n : nat) := match n with
                                         | O => unit | S n => (A * vec n)%type
                                         end.
    

Возможно, вы захотите пересмотреть представление языка, который вы хотите реализовать.Например, вы действительно хотите представлять арности так же явно, как функцию для символов?(Это, безусловно, может иметь место.) Например, не могли бы вы ограничить это символами арности 0, 1, 2?

Вложенные рекурсивные типы

Это рекурсивные типы, рекурсивные вхождения которыхвнутри других типов данных (которые могут быть рекурсивными).Чтобы упростить обсуждение, снять загромождение кода и из-за вышеупомянутых проблем с зависимыми типами в Coq, рассмотрите следующий тип, используя list вместо vec и с одним меньшим количеством конструктора:

Inductive LTree : Type :=
| funTerm : list LTree -> LTree.

Вы можете определить рекурсивные функции для такого типа с помощью Fixpoint, но вы должны быть особенно осторожны с тем, как рекурсивные вызовы вкладываются.Конечно, это действительно имеет значение для любого рекурсивного типа, но шаблон гораздо более естественный, когда рекурсия не является вложенной, поэтому проблема менее заметна.

Ниже показано, как мы можем определить равенство для LTree.Мы отказываемся от зависимого sumbool, возвращая вместо него bool.Определение dec_list является стандартным и общим.

Require Import List.
Import ListNotations.

Section List.

Context {A : Type} (decA : A -> A -> bool).

Fixpoint dec_list (l l' : list A) : bool :=
  match l, l' ith
  | [], [] => true
  | a :: l0, a' :: l0' =>
    decA a a' && dec_list l0 l0'
  | _, _ => false
  end.

End List.

Тогда равенство LTree выглядит невинным ...

Fixpoint decLTree (x y : LTree) : bool :=
  match x, y with
  | funTerm lx, funTerm ly =>
    dec_list decLTree lx ly
  end.

... но есть очень тонкие детали, которыенужно знать, чтобы убедить Coq в том, что рекурсия структурно уменьшается.

Правильность decLTree определенно очень тонко зависит от того, как dec_list использует свой аргумент decA, поэтомуdec_list должно быть прозрачным определением:

  1. Он применяется только к подтерме первого списка (вы можете сделать его вторым, если хотите, с некоторыми аннотациями struct).

  2. decA связан вне из Fixpoint dec_list.Функция decLTree не будет правильно сформирована, если вместо этой строки будет читать Fixpoint dec_list {A : Type} (decA : A -> A -> bool).

Также можно упаковать эти трюки, написав некоторые общие схемы рекурсии / индукции для LTree / VTree.

...