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 LTree
manualmente il principio di induzione "corretto" . Il principio di induzione generato automaticamente LTree_rect
omette 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].
Fix
Fixfi{f1:A1:=t1;f2:A2:=t2}
Γ1Γ2=(x:LTree)=(l:listLTree)A1A2=nat=natt1t2=case(x,LTree,λy.g1(f2y))=case(l,listLTree,λhr.g2(f1h)(f2r))
fjtifi
- i=1j=2
l
t
size
- i=2j=1
h
l
size_l
- i=2j=2
r
l
size_l
Il motivo per cui h
non è strutturalmente più piccolo di quello l
secondo 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 ]