Coq — возвращаемое значение типа, равного типу возвращаемой функции

#types #coq #typechecking

#типы #coq #проверка типов

Вопрос:

По некоторой теореме мы знаем, что тип A равен типу B . Как я могу сообщить об этом компилятору Coq во время проверки типа?

Я хочу реализовать непустое дерево таким образом, чтобы каждый узел знал свой размер:

 Inductive Struct: positive -> Type :=
| Leaf : Struct 1%positive
| Inner: forall {n m}, Struct n -> Struct m -> Struct (n   m).
  

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

7 -> 6 1 -> (3 3) 1 -> ((2 1) (2 1)) 1 -> (((1 1) 1) ((1 1) 1)) 1

 Fixpoint nat2struct n : (Struct n) :=
  match n with
  | xH => Leaf
  | xO n => Inner (nat2struct n) (nat2struct n) 
  | xI n => Inner Leaf (Inner (nat2struct n) (nat2struct n))
  end.
  

Однако я получаю сообщение об ошибке:

Термин «Внутренний лист (Inner (nat2struct n0) (nat2struct n0))» имеет тип «Struct (1 (n0 n0))», в то время как ожидается, что он будет иметь тип «Struct n0 ~ 1».

Как я могу это исправить? Мы это знаем (1 n n) = xI n , но Coq этого не делает. Если я сформулирую эту теорему ранее, это ничего не изменит:

 Theorem nBy2p1: forall n, Pos.add 1%positive (n   n) = xI n. Proof. Admitted.
Hint Resolve nBy2p1.
  

Есть ли какие-либо подсказки для Coq, чтобы быть в курсе этой теоремы?

PS1: эта теорема уже доказана в стандартной библиотеке? Я не нашел ни одного.

PS2: Я на самом деле хочу разделить более естественно: 7 -> 4 3 -> (2 2) (2 1) -> ((1 1) (1 1)) ((1 1) 1) . Возможно ли это? Я не знаю, как это написать, чтобы Coq понимал, что функция сходится.

Ответ №1:

При проверке типов Coq использует более слабую форму равенства (иногда называемую определяющим, оценочным или вычислительным равенством). В отличие от равенства высказываний (к чему по умолчанию привязывается «=»), равенство определений разрешимо. Coq может принимать любые два термина и решать, можно ли преобразовать одно в другое. Если бы при проверке типов было разрешено пропозициональное равенство, проверка типов больше не была бы разрешимой1.

Чтобы решить вашу проблему (а это довольно большая проблема), у вас есть несколько вариантов.

Сделайте Struct запись

Я продемонстрирую принцип, используя списки. Во-первых, у нас есть понятие списков без размера.

 Inductive UnsizedList (A: Type) :=
| unil
| ucons (a: A) (u: UnsizedList A).

Arguments unil [A], A.
Arguments ucons [A] a u, A a u.

Fixpoint length {A: Type} (u: UnsizedList A) :=
match u with
| unil => 0
| ucons _ u' => S (length u')
end.
  

Мы также можем определить размер списка, в котором каждый элемент SizedList A n имеет длину n .

 Inductive SizedList (A: Type): nat -> Type :=
| snil: SizedList A 0
| scons {n: nat} (a: A) (u: SizedList A n): SizedList A (S n).
  

Это определение сталкивается с точно такой же проблемой, как у вас. Например, если вы хотите показать, что конкатенация ассоциативна, вы не можете просто доказать concat (concat u v) w = concat u (concat v w) , поскольку две стороны равенства имеют разные типы ( (i j) k vs i (j k) ). Если бы мы могли просто сообщить Coq, какого размера мы ожидаем от списка, а затем доказать это позже, мы могли бы решить эту проблему. Вот что делает это определение, которое объединяет UnsizedList с доказательством того, что этот список имеет желаемую длину.

 Record SizedListPr (A: Type) (n: nat): Type := {
  list: UnsizedList A;
  list_len_eq: length list = n;
}.
  

Теперь мы можем иметь concat (concat u v) w = concat u (concat v w) ; нам просто нужно доказать, что обе стороны имеют длину (i j) k .

Используйте принуждения

Этот подход может стать довольно запутанным, если вы не будете осторожны, поэтому он не часто является предпочтительным подходом.

Позвольте мне определить своего рода принуждение, которое сопоставляет элементы типа P x с элементами типа P y if x = y .2

 Definition coercion {A: Type} (P: A -> Type) {x y: A} (p: x = y): P x -> P y :=
match p with
| eq_refl => fun t => t
end.
  

Здесь мы используем индукцию по термину p: x = y . Принцип индукции, по сути, гласит, что если мы можем что-то доказать, когда x и y по определению равны, то мы можем доказать это, когда они пропозиционально равны.3 Когда P x и P y совпадают, мы можем просто использовать функцию идентификации.

Теперь, например, утверждение об ассоциативности конкатенации для списков определенного размера является concat (concat u v) w = coercion (SizedList A) (add_assoc) (concat u (concat v w)) . Итак, мы приводим что-то типа SizedList A (i (j k)) к чему-то типа SizedList A ((i j) k) , используя равенство add_assoc: i (j k) = (i j) k (я опустил некоторые параметры для удобства чтения).


Какой выбор вы сделаете, зависит от вас. Обсуждение этой проблемы и связанных с ней проблем, а также некоторые дополнительные решения можно найти на странице Certified Programming with Dependent Types Равенство.


1 Класс теорий, в которых это обычно происходит, см. в расширительной теории типов. В диссертации Мартина Хофманна дается обзор разницы между интенсиональной и экстенсиональной теориями.

2 Если вы знакомы с теорией гомотопических типов, это transport .

3 В этом утверждении достаточно оговорок, указывающих на то, что различия между равенством высказываний и определениями все еще существуют.

Ответ №2:

Основываясь на ответе пользователя, это решение, к которому я пришел:

 Inductive Struct: positive -> Type :=
| Leaf : Struct 1
| Inner : forall {lsize rsize size}
    (proof: Pos.add lsize rsize = size),
    (Struct lsize) -> (Struct rsize) -> Struct size.

Local Lemma nBy2: forall {n}, Pos.add n n = xO n.
Proof.
intros.
assert (Zpos (n   n) = Zpos (n~0)). { rewrite Pos2Z.inj_add. rewrite Z.add_diag. symmetry. apply Pos2Z.inj_xO. }
inversion H. auto.
Qed.

Local Lemma nBy2p1: forall {n}, Pos.add 1 (xO n) = xI n.
Proof.
intros. simpl.
assert (Zpos (1   n~0) = Zpos (n~1)). { rewrite Pos2Z.inj_add. reflexivity. }
inversion H. auto.
Qed.

Fixpoint structCreate (size : positive) : (Struct size) :=
  match size with
  | xH => Leaf
  | xO n =>
    let child := structCreate n in
    Inner nBy2 child child
  | xI n => 
    let child := structCreate n in
    Inner nBy2p1 Leaf (Inner nBy2 child child)
  end.