Reasoning, Proof, and Formalization with Lean 4
Lezione 6 - Tipi induttivi, ricorsione e induzione
Stefano M. Nicoletti
Struttura della lezione
- Vediamo come definire tipi induttivamente e costruiamo i nostri numeri naturali.
- Introduciamo le dimostrazioni per induzione e alcune tattiche utili in matematica.
- Concludiamo con una prima dimostrazione per induzione.
Promemoria: che cos'è l'induzione?
L'induzione è un modo per dimostrare che una proprietà vale per tutti gli elementi di una struttura costruita passo dopo passo.
Una dimostrazione per induzione è formata da:
- uno o più casi base, nei quali dimostriamo direttamente la proprietà per gli elementi iniziali.
- un passo induttivo, nel quale assumiamo che la proprietà valga per un elemento già costruito e dimostriamo che vale anche per quello successivo.
L'ipotesi usata nel passo si chiama ipotesi induttiva, o ipotesi di induzione. Caso base e passo induttivo permettono insieme di raggiungere tutti gli elementi della struttura.
Da Cosa a un tipo con una struttura
Nelle lezioni precedenti, dichiarare Cosa : Type lasciava aperti gli elementi e la struttura del dominio.
Un tipo induttivo viene invece definito elencando tutti i modi in cui possiamo costruirne i valori. Questi modi si chiamano costruttori.
Per un semaforo consideriamo esattamente tre possibilità: rosso, giallo e verde.
Il tipo Semaforo
La dichiarazione di Semaforo introduce il tipo e i tre costruttori rosso, giallo e verde.
Ogni costruttore produce un valore del nuovo tipo. Poiché non abbiamo dichiarato altri costruttori, questi tre casi descrivono tutti i valori possibili.
Pattern matching e predicati
Un pattern matching definisce il risultato separatamente per ciascun costruttore.
SiDeveFermare .rossosi riduce aTrue.SiDeveFermare .giallosi riduce aTrue.SiDeveFermare .verdesi riduce aFalse.
Ritroviamo così un concetto già noto: SiDeveFermare : Semaforo → Prop è un predicato, ma ora conosciamo tutti gli elementi possibili del suo dominio.
Esercizio: ragionare sui costruttori
Vogliamo dimostrare che ogni semaforo è rosso, giallo oppure verde:
lecture06_semaforo_ha_un_colore (s : Semaforo) :
s = .rosso ∨ s = .giallo ∨ s = .verde.
Costruiamo i naturali
Definiamo un nuovo tipo Naturale senza usare Nat.
zeroè un naturale.- Se
nè un naturale,succ nè un naturale. - Non esistono altri modi di costruire un naturale.
Per ora non usiamo numerali come 1 o 2: costruiamo i valori direttamente con zero e succ.
Zero, uno, due, tre
Dichiariamo:
zero.uno := succ zero.due := succ uno.tre := succ due.
Ogni valore finito è quindi una catena di applicazioni di succ che termina con zero.
Promemoria: che cos'è la ricorsione?
La ricorsione permette di risolvere un problema usando la soluzione dello stesso problema su un caso più semplice.
Una definizione ricorsiva contiene normalmente:
- uno o più casi base, che danno direttamente un risultato.
- uno o più casi ricorsivi, che riducono il problema a un caso più semplice e ne usano il risultato.
Le riduzioni devono infine raggiungere un caso base: altrimenti il calcolo continuerebbe senza terminare.
Che cos'è una definizione ricorsiva?
Una funzione ricorsiva segue i costruttori del dato sul quale opera.
- Diciamo che cosa fare nel caso
zero. - Diciamo che cosa fare nel caso
succ n. - Nel secondo caso possiamo riutilizzare il risultato calcolato sul naturale più piccolo
n.
Lean accetta la definizione perché, nel caso succ n, la funzione richiama se stessa su n: abbiamo tolto un succ e siamo quindi più vicini a zero. In questo modo il calcolo non può continuare a richiamare la funzione per sempre.
Definiamo la somma
Scriviamo somma (m n : Naturale) : Naturale: la funzione riceve due naturali e restituisce un naturale. Con match n with rendiamo esplicito che la ricorsione segue il secondo argomento.
somma m zerorestituiscem.somma m (succ n)restituiscesucc (somma m n).
Il secondo caso riduce il problema dalla somma con succ n alla somma con il naturale più piccolo n.
Definiamo il doppio
Scriviamo doppio (n : Naturale) : Naturale: la funzione riceve un naturale e restituisce un naturale. Con match n with analizziamo i costruttori di n.
- Il doppio di
zeroèzero. - Il doppio di
succ naggiunge due successori al doppio din.
Verifichiamo computazioni concrete come somma uno uno = due e doppio uno = due. In entrambi i casi Lean calcola i due lati e rfl chiude il goal.
Uguaglianza definizionale
Due espressioni sono definizionalmente uguali quando Lean può calcolarle fino a ottenere la stessa espressione.
somma m zerosi riduce direttamente am.somma m (succ n)si riduce asucc (somma m n).doppio (succ n)si riduce asucc (succ (doppio n)).
In questi casi rfl chiude il goal perché, dopo la computazione, entrambi i lati coincidono.
Rendere visibile una definizione con unfold
unfold nome apre la definizione indicata nel goal.
Dimostriamo doppio uno = due: apriamo doppio, uno e due, quindi rfl riconosce che i due lati calcolano allo stesso valore.
Riscrivere con un'uguaglianza
Se abbiamo un'assunzione h : a = b, possiamo sostituire un lato con l'altro.
rw [h]riscrive daaversob.rw [← h]riscrive dabversoa.
Riscrittura in avanti
Vogliamo dimostrare somma a zero = b, data l'assunzione h : a = b.
Prima di rw [h] il goal contiene a. Dopo la riscrittura contiene b. Il lato sinistro si riduce quindi a b e rfl chiude il goal.
Riscrittura nella direzione opposta
Vogliamo dimostrare somma b zero = a, data ancora l'assunzione h : a = b.
La freccia ← cambia l'orientamento della sostituzione. Leggiamo sempre il goal per decidere quale direzione avvicina l'espressione alla forma desiderata.
Un'uguaglianza che non si risolve per computazione
Confrontiamo:
- in
somma m zero = m, il secondo argomento èzero: Lean può scegliere subito il primo caso della definizione. - in
somma zero n = n, il secondo argomento è la variabilen: Lean non sa ancora se siazerooppuresucc ke non può scegliere un caso.
Non basta controllare qualche valore di n: l'enunciato deve valere per tutti i naturali. Ci serve quindi un metodo che permetta di ragionare sistematicamente su ogni possibile costruzione di n.
Questo metodo è l'induzione. Vediamo ora come è fatta una dimostrazione per induzione.
Che cos'è una dimostrazione per induzione?
Per dimostrare una proprietà di ogni naturale:
- caso base: dimostriamo la proprietà per
zero. - ipotesi induttiva: nel caso
succ n, assumiamo che la proprietà valga pern. - passo induttivo: usando questa assunzione, dimostriamo la proprietà per
succ n.
Questo basta perché ogni Naturale è costruito a partire da zero mediante un numero finito di applicazioni di succ.
cases e induction
Entrambe le tattiche seguono i costruttori, ma forniscono informazioni diverse.
cases nproduce un caso perzeroe uno persucc n.induction nproduce gli stessi casi e, nel caso successore, anche l'ipotesi induttivaih.
Se il passo per succ n deve riutilizzare il risultato per n, la sola analisi per casi non basta.
Come scegliere la variabile d'induzione
Non scegliamo automaticamente la prima variabile del theorem. Cerchiamo invece la definizione ricorsiva che non riesce a calcolare.
- Individuiamo quale argomento viene confrontato con
zeroesuccnella definizione. - Facciamo induzione sulla variabile che occupa quella posizione nel goal.
- Controlliamo il passo
succ: l'ipotesi induttiva deve riguardare la chiamata ricorsiva più piccola che compare nel goal.
Nei teoremi più complessi, talvolta conviene prima riformulare o generalizzare il goal.
Prima induzione: zero a sinistra
Vogliamo dimostrare l'enunciato:
lecture06_zero_somma (n : Naturale) : somma zero n = n.
Ricordiamo la definizione che unfold somma rende visibile:
def somma (m n : Naturale) : Naturale :=
match n with
| zero => m
| succ n => succ (somma m n)
somma analizza il proprio secondo argomento. Nel goal quel posto è occupato da n, quindi facciamo induzione su n.
Leggere induction n with
Nel proof script:
induction n withscegliencome variabile d'induzione.| zero =>apre il caso base.| succ n ih =>introduce il predecessorene l'ipotesi induttivaih.- ogni ramo deve dimostrare la proprietà per il costruttore corrispondente.
Dove siamo arrivati
Abbiamo definito un tipo induttivo e funzioni ricorsive sui nostri naturali. Abbiamo poi usato rfl, unfold, rw e una prima dimostrazione per induzione.
Il risultato conclusivo è:
lecture06_zero_somma (n : Naturale) : somma zero n = n.
Nella prossima lezione useremo la stessa struttura per dimostrare altre proprietà della somma e introdurre simp.
Fonti
Theorem Proving in Lean 4, Inductive Types
Theorem Proving in Lean 4, Induction and Recursion
Theorem Proving in Lean 4, Tactics
Functional Programming in Lean, Datatypes and Patterns