Rekursive Definitionen über einen induktiven Typ mit verschachtelten Komponenten

21

Stellen Sie sich einen induktiven Typ vor, der an einer verschachtelten, aber streng positiven Position einige rekursive Vorkommen aufweist. Zum Beispiel Bäume mit endlicher Verzweigung mit Knoten, die eine generische Listendatenstruktur zum Speichern der Kinder verwenden.

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

Die naive Art, eine rekursive Funktion über diese Bäume zu definieren, indem über Bäume und Baumlisten rekursiv verfahren wird, funktioniert nicht. Hier ist ein Beispiel mit der sizeFunktion, die die Anzahl der Knoten berechnet.

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.

Diese Definition ist unkorrekt (Fehlermeldung auszugsweise):

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

Warum ist die Definition falsch rformuliert , obwohl es sich eindeutig um ein Subterm handelt l? Gibt es eine Möglichkeit, rekursive Funktionen für eine solche Datenstruktur zu definieren?


Wenn Sie die Coq-Syntax nicht fließend beherrschen: Ist LTreeein induktiver Typ, der der folgenden Grammatik entspricht.

LTree:: =|lichst(LTree)

Wir versuchen die sizeFunktion durch Induktion über Bäume und Listen zu definieren . In OCaml wäre das:

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
Gilles 'SO - hör auf böse zu sein'
quelle
Ist das zum Thema? Ich bin mir nicht sicher; Lassen Sie uns dies auf Meta diskutieren .
Gilles 'SO- hör auf böse zu sein'
Können Sie äquivalente Funktionsdefinitionen in etwas weniger Coqy und mehr Mathy hinzufügen? Ich habe Probleme, die Syntax zu verstehen.
Raphael
1
@Raphael Ich habe es versucht, ist das jetzt besser? Ehrlich gesagt ist diese Frage ziemlich spezifisch für Coq.
Gilles 'SO- hör auf böse zu sein'

Antworten:

14

Was funktioniert

Wenn Sie die Definition des Fixpunkts in Listen innerhalb der Definition des Fixpunkts in Bäumen verschachteln, ist das Ergebnis gut typisiert. Dies ist ein allgemeines Prinzip, wenn Sie eine Rekursion in einem induktiven Typ verschachtelt haben, dh wenn die Rekursion einen Konstruktor wie folgt durchläuft 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.

Oder wenn Sie es vorziehen, dies knapper zu schreiben:

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.

(Ich habe keine Ahnung, von wem ich es zuerst gehört habe; dies wurde sicherlich viele Male unabhängig voneinander entdeckt.)

Ein allgemeines Rekursionsprädikat

Im Allgemeinen können Sie das „richtige“ Induktionsprinzip LTreemanuell festlegen . Das automatisch erzeugte Induktionsprinzip LTree_rectlässt die Hypothese in der Liste weg, da der Induktionsprinzip-Generator nur nicht verschachtelte streng positive Ereignisse vom Typ Induktion versteht.

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

Fügen wir die Induktionshypothese zu Listen hinzu. Um dies im rekursiven Aufruf zu erfüllen, nennen wir das Listeninduktionsprinzip und übergeben es dem kleineren Baum in der Liste.

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.

Warum

Die Antwort auf das Warum liegt in den genauen Regeln für die Annahme rekursiver Funktionen. Diese Regeln sind zwangsläufig subtil, da es ein empfindliches Gleichgewicht zwischen dem Zulassen komplexer Fälle (wie dieser mit verschachtelter Rekursion im Datentyp) und Unzuverlässigkeit gibt. Das Coq-Referenzhandbuch enthält eine Einführung in die Sprache (die Berechnung induktiver Konstruktionen, die die Beweissprache von Coq ist), meist mit formal präzisen Definitionen. Wenn Sie jedoch die genauen Regeln für Induktion und Coinduktion wünschen, lesen Sie die Forschungsarbeiten. zu diesem Thema von Eduardo Giménez [1].

FixFichxfich{f1:EIN1: =t1;f2:EIN2: =t2}

Γ1=(x:LTree)EIN1=neintt1=ceinse(x,LTree,λy.G1(f2y))Γ2=(l:lichstLTree)EIN2=neintt2=ceinse(l,lichstLTree,λhr.G2(f1h)(f2r))

fjtichfich

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

Der Grund, warum hstrukturell nicht kleiner ist als llaut Coq-Interpreter, ist mir nicht klar. Soweit ich aus den Diskussionen über die Coq-Club-Liste [1] [2] weiß, handelt es sich um eine Einschränkung des Dolmetschers, die grundsätzlich aufgehoben werden könnte, aber sehr sorgfältig, um Inkonsistenzen zu vermeiden.

Verweise

Cocorico, das nicht abschließende Coq-Wiki: Gegenseitige Induktion

Coq-Club Mailingliste:

Das Coq-Entwicklungsteam. Der Coq Proof Assistant: Referenzhandbuch . Version 8.3 (2010). [ web ] ch. 4 .

Eduardo Giménez. Codierung von geschützten Definitionen mit rekursiven Schemata . In Types'94: Types for Proofs and Programs , LNCS 996. Springer-Verlag, 1994. doi: 10.1007 / 3-540-60579-7_3 [ Springer ]

Eduardo Giménez. Strukturelle rekursive Definitionen in der Typentheorie . In ICALP'98: Proceedings of the 25. International Colloquium on Automata, Languages ​​and Programming. Springer-Verlag, 1998. [ PDF ]

Gilles 'SO - hör auf böse zu sein'
quelle
7

Dies ist offensichtlich ein spezielles Problem für Coq, da ich glaube, dass es bessere Möglichkeiten gibt, es mit einigen anderen Proof-Assistenten zu umgehen (ich sehe Agda an).

Anfangs dachte ich, dass dies rnicht als strukturell kleiner erkannt wurde, da es sich bei der Struktur nur um die induktive Definition handelt, die derzeit behandelt wird Fixpoint: Dies ist also keine LTreeUnterfunktion, auch wenn es sich um eine listUnterfunktion handelt.

Wenn Sie jedoch die Verarbeitung der Liste erweitern, funktioniert Folgendes:

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.

Oder da die Hilfsfunktion bereits in der Standardbibliothek vorhanden ist:

Require Import List.

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

Um ehrlich zu sein, bin ich mir nicht sicher, warum diese von Coq akzeptiert werden, aber ich bin froh, dass sie es sind.

Es gibt auch eine Lösung, die nicht nur für Listen häufiger funktioniert: die Definition eines in sich geschlossenen induktiven Typs. In diesem Fall können Sie Ihre sizeFunktion manuell definieren . (mit zwei Fixpunkten)

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

Beachten Sie, dass Sie bei Problemen mit komplexeren induktiven Definitionen ein Argument verwenden können, das die Größe verringert. Das ist möglich, aber umständlich für dieses Problem (und ich würde es wagen, für die meisten Probleme zu sagen)

jmad
quelle
Was ich heute noch nicht verstehe, ist, warum der naive Ansatz nicht funktioniert. Im Prinzip sollte dies eine Folge von Eduardo Giménez 'Aufsatz sein, aber ich sehe nicht, wo der Abzug bricht; Dies kann eher eine Einschränkung der Coq-Engine als des zugrunde liegenden Kalküls sein.
Gilles 'SO - hör auf böse zu sein'