Рекурсивні визначення індуктивного типу з вкладеними компонентами


21

Розглянемо індуктивний тип, який має деякі рекурсивні явища у вкладеному, але суто позитивному місці. Наприклад, дерева з кінцевим розгалуженням з вузлами, що використовують загальну структуру даних списку для зберігання дітей.

Inductive LTree : Set := Node : list LTree -> LTree.

Наївний спосіб визначення рекурсивної функції над цими деревами шляхом повторення над деревами та списками дерев не працює. Ось приклад з sizeфункцією, яка обчислює кількість вузлів.

Fixpoint size (t : LTree) : nat := match t with Node l => 1 + (size_l l) end
with size_l (l : list LTree) : nat := match l with
    | nil => 0
    | cons h r => size h + size_l r
  end.

Це визначення неправильно сформовано (винесено повідомлення про помилку):

Error:
Recursive definition of size_l is ill-formed.
Recursive call to size has principal argument equal to
"h" instead of "r".

Чому визначення rневірно сформоване, хоча воно явно є підтерміном l? Чи існує спосіб визначення рекурсивних функцій на такій структурі даних?


Якщо ви не володієте синтаксисом Coq: LTreeце індуктивний тип, що відповідає наступній граматиці.

LTree::=|list(LTree)

Ми намагаємося визначити sizeфункцію за допомогою індукції над деревами та списками. В OCaml це було б:

type t = Node of t list
let rec size = function Node l -> 1 + size_l l
and size_l = function [] -> 0
                    | h::r -> size h + size_l r


Чи можете ви додати еквівалентні визначення функцій у чомусь менш Coqy та більш математичному? У мене проблеми з розумінням синтаксису.
Рафаель

1
@Raphael Я пробував, це краще зараз? Чесно кажучи, це питання досить специфічне для Coq.
Жил "ТАК - перестань бути злим"

Відповіді:


14

Що працює

Якщо ввести визначення точки фіксації у списки всередині визначення точки фіксації на деревах, результат буде добре набрано. Це загальний принцип, коли ви вклали рекурсію індуктивного типу, тобто коли рекурсія проходить через такий конструктор list.

Fixpoint size (t : LTree) : nat :=
  let size_l := (fix size_l (l : list LTree) : nat :=
                  match l with
                    | nil => 0
                    | h::r => size h + size_l r
                  end) in
  match t with Node l =>
    1 + size_l l
  end.

Або якщо ви віддаєте перевагу писати це більш коротко:

Fixpoint size (t : LTree) : nat :=
  match t with Node l =>
    1 + (fix size_l (l : list LTree) : nat :=
          match l with
            | nil => 0
            | h::r => size h + size_l r
          end) l
  end.

(Я не маю уявлення, з ким я чув це спочатку; це, безумовно, було відкрито незалежно багато разів.)

Загальний рекурсійний предикат

Більш загально, ви можете визначити "правильний" принцип індукції LTreeвручну. Автоматично генерований принцип індукції LTree_rectопускає гіпотезу зі списку, оскільки генератор принципу індукції розуміє лише невкладені строго позитивні випадки індуктивного типу.

LTree_rect = 
fun (P : LTree -> Type) (f : forall l : list LTree, P (Node l)) (l : LTree) =>
match l as l0 return (P l0) with
| Node x => f x
end
     : forall P : LTree -> Type,
       (forall l : list LTree, P (Node l)) -> forall l : LTree, P l

Додамо індукційну гіпотезу до списків. Щоб виконати його в рекурсивному виклику, ми викликаємо принцип індукції списку і передаємо йому принцип індукції дерева на менше дерево всередині списку.

Fixpoint LTree_rect_nest (P : LTree -> Type) (Q : list LTree -> Type)
                         (f : forall l, Q l -> P (Node l))
                         (g : Q nil) (h : forall t l, P t -> Q l -> Q (cons t l))
                         (t : LTree) :=
  match t as t0 return (P t0) with
    | Node l => f l (list_rect Q g (fun u r => h u r (LTree_rect_nest P Q f g h u)) l)
  end.

Чому?

Відповідь на те, чому криється в точних правилах прийому рекурсивних функцій. Ці правила є витонченими, оскільки існує делікатний баланс між дозволенням складних випадків (наприклад, цього, із вкладеною рекурсією у типі даних) та безглуздістю. Coq Довідкове керівництво вводить мову (обчислення індуктивних конструкцій, що є доказом мову Coq), в основному , з формально точними визначеннями, але якщо ви хочете точні правила щодо індукції і коіндукціі вам потрібно піти в науково - дослідних робіт, з цієї теми [1] Едуардо Гіменеса.

FixFixfi{f1:A1:=t1;f2:A2:=t2}

Γ1=(x:LTree)A1=natt1=case(x,LTree,λy.g1(f2y))Γ2=(l:listLTree)A2=natt2=case(l,listLTree,λhr.g2(f1h)(f2r))

fjtifi

  • i=1j=2ltsize
  • i=2j=1hlsize_l
  • i=2j=2rlsize_l

Причина, чому hвона не є структурно меншою, ніж lза словами перекладача Coq, мені не зрозуміла. Наскільки я розумію з обговорень списку клубів Кок [1] ​​[2], це обмеження в перекладачі, яке в принципі можна було б зняти, але дуже обережно, щоб уникнути неузгодженості.

Список літератури

Кокоріко, Вікі Coq, що не знищує: Взаємна індукція

Список розсилки Coq-Club:

Команда розвитку Coq Помічник доказу Coq: Довідковий посібник . Версія 8.3 (2010). [ веб ] гл. 4 .

Едуардо Гіменес. Кодифікація захищених визначень за допомогою рекурсивних схем . In Types'94: Types для доказів та програм , LNCS 996. Springer-Verlag, 1994. doi: 10.1007 / 3-540-60579-7_3 [ Springer ]

Едуардо Гіменес. Структурно-рекурсивні визначення в теорії типів . В ICALP'98: Матеріали 25-го Міжнародного колоквіуму з питань автоматів, мов та програмування. Springer-Verlag, 1998. [ PDF ]


7

Це, очевидно, проблема, характерна для Coq, оскільки я вважаю, що існують приємніші способи подолати її разом із деякими іншими помічниками (я дивлюся на Агду)

Спочатку я подумав, що rвін не був визнаний структурно меншим, тому що структура стосується лише індуктивного визначення, яким зараз займається Fixpoint: тож це не є LTreeпідтермією, навіть якщо вона є listпідтермією.

Але якщо розгорнути обробку списку, він працює:

Fixpoint size t :=
  match t with
  | Node l => S
     ((fix size_l l :=
     match l with
     | nil => 0
     | cons t l => size_l l + size t
     end) l)
 end.

Або оскільки допоміжна функція вже існує у стандартній бібліотеці:

Require Import List.

Fixpoint size t :=
  match t with
  | Node l => S (fold_left (fun a t => a + size t) l 0)
  end.

Якщо чесно, я не впевнений, чому це прийнято Coq, але я впевнений, що це так.

Існує також рішення, яке працює частіше і не тільки для списків: визначення автономного індуктивного типу. У цьому випадку ви можете визначити свою sizeфункцію вручну. (з двома фіксованими точками)

Inductive LTree : Set :=
  | Node : list_LTree -> LTree
with list_LTree : Set :=
  | LTree_nil : list_LTree
  | LTree_cons : LTree -> list_LTree -> list_LTree.

Зауважте, що якщо у вас є проблеми для складніших індуктивних визначень, ви можете використовувати аргумент зменшення розміру. Це можливо, але громіздко для цієї проблеми (і я би наважився сказати на більшість проблем)


Я все ще не розумію сьогодні, чому наївний підхід не працює. В принципі, це має бути наслідком роботи Едуардо Гіменеса, але я не бачу, де відбувається дедукція; це може бути обмеженням двигуна Coq, а не основним обчисленням.
Жил "ТАК - перестань бути злим"
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.