Definizioni ricorsive su un tipo induttivo con componenti nidificati


21

Considera un tipo induttivo che presenta alcune occorrenze ricorsive in una posizione nidificata, ma strettamente positiva. Ad esempio, alberi con ramificazione finita con nodi che utilizzano una struttura di dati di elenco generica per memorizzare i figli.

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

Il modo ingenuo di definire una funzione ricorsiva su questi alberi ricorrendo a alberi e liste di alberi non funziona. Ecco un esempio con la sizefunzione che calcola il numero di nodi.

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.

Questa definizione non è corretta (messaggio di errore estratto):

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

Perché la definizione è mal formata, anche se rè chiaramente un sotterfugio di l? Esiste un modo per definire le funzioni ricorsive su una tale struttura di dati?


Se non sei fluente nella sintassi di Coq: LTreeè un tipo induttivo corrispondente alla seguente grammatica.

LTree::=|list(LTree)

Tentiamo di definire la sizefunzione per induzione su alberi e liste. In OCaml, questo sarebbe:

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

È su questo argomento? Non ne sono sicuro; ne parliamo su Meta .
Gilles 'SO- smetti di essere malvagio' il

Puoi aggiungere definizioni di funzioni equivalenti in qualcosa di meno Coqy e più matematica? Ho difficoltà a comprendere la sintassi.
Raffaello

1
@Raphael Ho provato, va meglio adesso? Onestamente, questa domanda è abbastanza specifica per Coq.
Gilles 'SO- smetti di essere malvagio' l'

Risposte:


14

Cosa funziona

Se annidate la definizione del fixpoint sugli elenchi all'interno della definizione del fixpoint sugli alberi, il risultato è ben digitato. Questo è un principio generale quando hai annidato la ricorsione in un tipo induttivo, cioè quando la ricorsione passa attraverso un costruttore come 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.

O se preferisci scrivere questo più tersamente:

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.

(Non ho idea di chi l'ho sentito per primo; questo è stato sicuramente scoperto in modo indipendente molte volte.)

Un predicato di ricorsione generale

Più in generale, è possibile definire LTreemanualmente il principio di induzione "corretto" . Il principio di induzione generato automaticamente LTree_rectomette l'ipotesi sulla lista, perché il generatore del principio di induzione comprende solo eventi strettamente positivi non nidificati di tipo induttivo.

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

Aggiungiamo l'ipotesi di induzione nelle liste. Per soddisfarlo nella chiamata ricorsiva, chiamiamo il principio di induzione dell'elenco e gli passiamo il principio di induzione dell'albero sull'albero più piccolo all'interno dell'elenco.

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.

Perché

La risposta al perché sta nelle regole precise per accettare le funzioni ricorsive. Queste regole sono per forza sottili, perché esiste un delicato equilibrio tra consentire casi complessi (come questo, con ricorsione annidata nel tipo di dati) e l'insondatezza. Il manuale di riferimento di Coq introduce il linguaggio (il calcolo delle costruzioni induttive, che è il linguaggio di prova di Coq), principalmente con definizioni formalmente precise, ma se desideri le regole esatte relative all'induzione e alla coinduzione dovrai andare ai documenti di ricerca, su questo argomento Eduardo Giménez [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

Il motivo per cui hnon è strutturalmente più piccolo di quello lsecondo l'interprete Coq non mi è chiaro. Per quanto ho capito dalle discussioni sull'elenco del Coq-club [1] [2], si tratta di una restrizione all'interprete, che in linea di principio potrebbe essere eliminata, ma con molta attenzione per evitare di introdurre un'incoerenza.

Riferimenti

Cocorico, il wiki non terminante di Coq: Mutual Induction

Mailing list del Coq-Club:

Il team di sviluppo di Coq. The Coq Proof Assistant: Manuale di riferimento . Versione 8.3 (2010). [ web ] ch. 4 .

Eduardo Giménez. Codificazione di definizioni custodite con schemi ricorsivi . In Tipi'94: Tipi per prove e programmi , LNCS 996. Springer-Verlag, 1994. doi: 10.1007 / 3-540-60579-7_3 [ Springer ]

Eduardo Giménez. Definizioni ricorsive strutturali nella teoria dei tipi . In ICALP'98: Atti del 25 ° Colloquio internazionale su automi, lingue e programmazione. Springer-Verlag, 1998. [ PDF ]


7

Questo è ovviamente un problema specifico di Coq poiché credo che ci siano modi migliori per aggirarlo con altri assistenti di prova (sto guardando Agda)

All'inizio ho pensato che rnon fosse riconosciuto come strutturalmente più piccolo perché la struttura riguarda solo la definizione induttiva attualmente gestita da Fixpoint: quindi questo non è un LTreesotterfugio anche se è un listsotterfugio.

Ma se espandi l'elaborazione dell'elenco, allora funziona:

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.

O poiché la funzione ausiliaria esiste già nella libreria standard:

Require Import List.

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

Ad essere sincero, non sono sicuro del motivo per cui quelli sono accettati da Coq, ma sono sicuro che lo siano.

Esiste anche una soluzione che funziona più spesso e non solo per gli elenchi: la definizione di un tipo induttivo autonomo. In questo caso puoi definire la tua sizefunzione manualmente. (con due punti fissi)

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

Si noti che se si riscontrano problemi per definizioni induttive più complesse, è possibile utilizzare un argomento con riduzione delle dimensioni. Questo è possibile ma ingombrante per questo problema (e oserei dire per la maggior parte dei problemi)


Ciò che ancora non capisco oggi è perché l'approccio ingenuo non funziona. In linea di principio, questo dovrebbe essere una conseguenza del documento di Eduardo Giménez, ma non vedo dove si interrompe la detrazione; questo può essere una limitazione del motore Coq piuttosto che il calcolo sottostante.
Gilles 'SO- smetti di essere malvagio'
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.