Lezione 3: Dalla deduzione naturale alle dimostrazioni in Lean
Registrazione della lezione
1. Scopo della lezione
Nelle lezioni precedenti abbiamo imparato a leggere un proof state e abbiamo usato tattiche come intro, apply, have, exact e cases. In questa lezione colleghiamo quei comandi alle regole della deduzione naturale. Vedremo come le tattiche non siano mosse arbitrarie: costruiscono e usano dimostrazioni secondo la struttura logica delle proposizioni.
Il percorso della lezione è il seguente:
- riprendere l'idea di tipo abitato;
- presentare l'idea della corrispondenza di Curry-Howard;
- leggere le regole di introduzione e di eliminazione della deduzione naturale;
- riconoscere le stesse regole nelle dimostrazioni Lean.
2. Tipi e abitanti
Un tipo descrive una classe di possibili oggetti. Dire che un tipo è abitato significa dire che esiste almeno un termine che ha quel tipo. Se t ha tipo T, scriviamo:
t : T
Possiamo leggere questa espressione come «t è un termine di tipo T» oppure «t abita T». L'idea non riguarda soltanto Lean. Per esempio, il tipo informale «luna di Giove» è abitato perché Io è una luna di Giove. Io è un oggetto concreto che soddisfa la descrizione espressa dal tipo NASA Science, Io: Facts.
In Lean possiamo osservare la stessa struttura con oggetti più semplici:
#check 4 -- 4 : Nat
#check True -- True : Prop
#check True.intro -- True.intro : True
Nat è abitato, per esempio, dal termine 4. Anche True è abitato: il termine True.intro ha esattamente tipo True. Questa seconda osservazione ci porta dalle relazioni generiche tra termini e tipi alle relazioni tra dimostrazioni e proposizioni.
3. La corrispondenza di Curry-Howard
Secondo la corrispondenza di Curry-Howard, proposizioni e tipi possono essere letti come due descrizioni della stessa struttura. Una proposizione indica che cosa deve essere dimostrato, mentre il tipo corrispondente indica quale genere di termine deve essere costruito. Una dimostrazione della proposizione è un termine che abita quel tipo Wadler, Propositions as Types.
Ad esempio:
| Logica | Teoria dei tipi |
|---|---|
| proposizione | tipo |
| dimostrazione | termine |
A → B | funzione da dimostrazioni di A a dimostrazioni di B |
introduzione di → | costruzione di una funzione |
eliminazione di → | applicazione di una funzione |
Perciò verificare una dimostrazione significa controllare che il termine costruito abbia il tipo dichiarato. Una derivazione non si limita a certificare in modo astratto che una proposizione è vera: specifica come costruirne una dimostrazione Theorem Proving in Lean 4, Propositions and Proofs.
4. Deduzione naturale e proof state
La deduzione naturale presenta una dimostrazione come una sequenza di passi locali governati da regole. Ogni regola mostra come ottenere una conclusione a partire da alcune premesse. Le regole di introduzione spiegano come costruire una dimostrazione il cui connettivo principale è quello considerato; le regole di eliminazione spiegano come usare una dimostrazione composta già disponibile Logic and Proof, Natural Deduction for Propositional Logic.
Un giudizio ha la forma Γ ⊢ A.
Γ è il contesto delle assunzioni disponibili e A è la conclusione che vogliamo derivare. Il proof state di Lean riproduce questa organizzazione: il contesto compare sopra il simbolo ⊢, mentre il goal compare dopo di esso.
Quando una regola introduce un'assunzione temporanea, questa viene aggiunta al contesto soltanto nel ramo in cui serve. Per esempio, per dimostrare A → B possiamo assumere temporaneamente A e costruire B. Quando la dimostrazione dell'implicazione è completa, l'assunzione è stata incorporata nella funzione che trasforma dimostrazioni di A in dimostrazioni di B.
5. Implicazione
Introduzione dell'implicazione, →I
Per dimostrare A → B, introduciamo un'assunzione di tipo A e costruiamo una dimostrazione di B. In Lean, intro esegue precisamente questo passaggio.
Vogliamo dimostrare che, se piove e fa freddo, allora piove. Introduciamo l'assunzione che piova e faccia freddo; abbiamo che piove prendendo la parte sinistra dell'assunzione; usiamo esattamente questo fatto.
-- Ogni esempio segue lo stesso percorso:
-- regola di deduzione naturale, lettura in linguaggio naturale, istanza concreta,
-- costruzione del termine richiesto nel proof state.
-- Introduzione dell'implicazione, →I.
-- Per dimostrare `A → B`, introduciamo l'assunzione temporanea `A` e costruiamo `B`.
-- Esempio: se piove e fa freddo, allora piove.
theorem
(Piove FaFreddo : Prop) :
Piove ∧ FaFreddo → Piove := Piove:PropFaFreddo:Prop⊢ Piove ∧ FaFreddo → Piove
Piove:PropFaFreddo:ProphPioveEFaFreddo:Piove ∧ FaFreddo⊢ Piove
Piove:PropFaFreddo:ProphPioveEFaFreddo:Piove ∧ FaFreddohPiove:Piove⊢ Piove
All goals completed! 🐙Eliminazione dell'implicazione, →E
Se abbiamo A → B e abbiamo A, possiamo applicare la prima dimostrazione alla seconda e ottenere B. Questa è di nuovo la regola del modus ponens.
Vogliamo dimostrare che prendo l'ombrello, date le assunzioni che, se piove, prendo l'ombrello e che piove. Applichiamo la prima assunzione alla seconda; abbiamo che prendo l'ombrello e usiamo esattamente questo fatto.
-- Eliminazione dell'implicazione, →E: modus ponens.
-- Da `A → B` e `A` otteniamo `B`.
-- Esempio: se piove, prendo l'ombrello; piove; dunque prendo l'ombrello.
theorem
(Piove PrendoOmbrello : Prop)
(hPioveOmbrello : Piove → PrendoOmbrello)
(hPiove : Piove) :
PrendoOmbrello := Piove:PropPrendoOmbrello:ProphPioveOmbrello:Piove → PrendoOmbrellohPiove:Piove⊢ PrendoOmbrello
Piove:PropPrendoOmbrello:ProphPioveOmbrello:Piove → PrendoOmbrellohPiove:PiovehPrendoOmbrello:PrendoOmbrello⊢ PrendoOmbrello
All goals completed! 🐙Qui hPioveOmbrello si comporta come una funzione: riceve il termine hPiove di tipo Piove e restituisce un termine di tipo PrendoOmbrello.
6. Congiunzione
Introduzione della congiunzione, ∧I
Per dimostrare A ∧ B dobbiamo costruire entrambe le componenti. Applicando And.intro, il goal viene diviso in due casi: prima A, poi B.
-- Introduzione della congiunzione, ∧I.
-- Da una dimostrazione di A e una di B costruiamo una dimostrazione di A ∧ B.
-- Esempio: piove; fa freddo; dunque piove e fa freddo.
theorem
(Piove FaFreddo : Prop)
(hPiove : Piove)
(hFaFreddo : FaFreddo) :
Piove ∧ FaFreddo := Piove:PropFaFreddo:ProphPiove:PiovehFaFreddo:FaFreddo⊢ Piove ∧ FaFreddo
Piove:PropFaFreddo:ProphPiove:PiovehFaFreddo:FaFreddo⊢ PiovePiove:PropFaFreddo:ProphPiove:PiovehFaFreddo:FaFreddo⊢ FaFreddo
Piove:PropFaFreddo:ProphPiove:PiovehFaFreddo:FaFreddo⊢ Piove All goals completed! 🐙
Piove:PropFaFreddo:ProphPiove:PiovehFaFreddo:FaFreddo⊢ FaFreddo All goals completed! 🐙I due punti elenco (·) appartengono a due goal distinti. Una dimostrazione della congiunzione è completa soltanto quando entrambi sono chiusi (simboleggiano i due congiunti).
Eliminazione della congiunzione, ∧Eₗ e ∧Eᵣ
Una dimostrazione di A ∧ B contiene una dimostrazione di A e una di B. Possiamo selezionare la componente sinistra con .left e quella destra con .right.
-- Eliminazione sinistra della congiunzione, ∧Eₗ.
-- Da `A ∧ B` selezioniamo la componente A.
-- Esempio: piove e fa freddo; dunque piove.
theorem
(Piove FaFreddo : Prop)
(hPioveEFaFreddo : Piove ∧ FaFreddo) :
Piove := Piove:PropFaFreddo:ProphPioveEFaFreddo:Piove ∧ FaFreddo⊢ Piove
Piove:PropFaFreddo:ProphPioveEFaFreddo:Piove ∧ FaFreddohPiove:Piove⊢ Piove
All goals completed! 🐙-- Eliminazione destra della congiunzione, ∧Eᵣ.
-- Da `A ∧ B` selezioniamo la componente B.
-- Esempio: piove e fa freddo; dunque fa freddo.
theorem
(Piove FaFreddo : Prop)
(hPioveEFaFreddo : Piove ∧ FaFreddo) :
FaFreddo := Piove:PropFaFreddo:ProphPioveEFaFreddo:Piove ∧ FaFreddo⊢ FaFreddo
Piove:PropFaFreddo:ProphPioveEFaFreddo:Piove ∧ FaFreddohFaFreddo:FaFreddo⊢ FaFreddo
All goals completed! 🐙L'introduzione costruisce una congiunzione a partire dalle componenti; l'eliminazione percorre il cammino opposto e recupera una componente dalla congiunzione disponibile.
7. Disgiunzione
Introduzione della disgiunzione, ∨Iₗ e ∨Iᵣ
Per dimostrare A ∨ B è sufficiente costruire uno dei due lati, ma dobbiamo specificare quale. Or.inl sceglie il lato sinistro; Or.inr sceglie il lato destro.
-- Introduzione sinistra della disgiunzione, ∨Iₗ.
-- Da A costruiamo A ∨ B scegliendo il lato sinistro.
-- Esempio: piove; dunque piove oppure nevica.
theorem
(Piove Nevica : Prop)
(hPiove : Piove) :
Piove ∨ Nevica := Piove:PropNevica:ProphPiove:Piove⊢ Piove ∨ Nevica
Piove:PropNevica:ProphPiove:Piove⊢ Piove
All goals completed! 🐙-- Introduzione destra della disgiunzione, ∨Iᵣ.
-- Da B costruiamo A ∨ B scegliendo il lato destro.
-- Esempio: nevica; dunque piove oppure nevica.
theorem
(Piove Nevica : Prop)
(hNevica : Nevica) :
Piove ∨ Nevica := Piove:PropNevica:ProphNevica:Nevica⊢ Piove ∨ Nevica
Piove:PropNevica:ProphNevica:Nevica⊢ Nevica
All goals completed! 🐙Eliminazione della disgiunzione, ∨E
Se abbiamo A ∨ B, non sappiamo in generale quale lato sia stato dimostrato. Per ottenere una stessa conclusione C, dobbiamo quindi dimostrarla sia nel caso A sia nel caso B.
Vogliamo dimostrare che prendo l'ombrello, date le assunzioni che piove oppure nevica, che se piove prendo l'ombrello e che se nevica prendo l'ombrello. Procediamo per casi sulla disgiunzione. In ogni ramo compare soltanto l'assunzione corrispondente a quel caso.
-- Eliminazione della disgiunzione, ∨E.
-- Da `A ∨ B`, `A → C` e `B → C` otteniamo C ragionando per casi.
-- Esempio: piove oppure nevica; in entrambi i casi prendo l'ombrello;
-- dunque prendo l'ombrello.
theorem
(Piove Nevica PrendoOmbrello : Prop)
(hPioveONevica : Piove ∨ Nevica)
(hPioveOmbrello : Piove → PrendoOmbrello)
(hNevicaOmbrello : Nevica → PrendoOmbrello) :
PrendoOmbrello := Piove:PropNevica:PropPrendoOmbrello:ProphPioveONevica:Piove ∨ NevicahPioveOmbrello:Piove → PrendoOmbrellohNevicaOmbrello:Nevica → PrendoOmbrello⊢ PrendoOmbrello
cases hPioveONevica with
Piove:PropNevica:PropPrendoOmbrello:ProphPioveOmbrello:Piove → PrendoOmbrellohNevicaOmbrello:Nevica → PrendoOmbrellohPiove:Piove⊢ PrendoOmbrello
Piove:PropNevica:PropPrendoOmbrello:ProphPioveOmbrello:Piove → PrendoOmbrellohNevicaOmbrello:Nevica → PrendoOmbrellohPiove:PiovehPrendoOmbrello:PrendoOmbrello⊢ PrendoOmbrello
All goals completed! 🐙
Piove:PropNevica:PropPrendoOmbrello:ProphPioveOmbrello:Piove → PrendoOmbrellohNevicaOmbrello:Nevica → PrendoOmbrellohNevica:Nevica⊢ PrendoOmbrello
Piove:PropNevica:PropPrendoOmbrello:ProphPioveOmbrello:Piove → PrendoOmbrellohNevicaOmbrello:Nevica → PrendoOmbrellohNevica:NevicahPrendoOmbrello:PrendoOmbrello⊢ PrendoOmbrello
All goals completed! 🐙cases elimina la disgiunzione rendendo esplicite le varie possibilità tramite una dimostrazione per casi.
8. Negazione, verità e falsità
Negazione
In Lean, ¬A è definita come A → False. Una dimostrazione di ¬A è quindi una funzione che trasforma ogni ipotetica dimostrazione di A in una contraddizione.
Per introdurre ¬A, introduciamo l'assunzione A e costruiamo False. Vogliamo dimostrare che non ho bevuto caffè, date le assunzioni che, se bevo caffè, resto sveglio e che non resto sveglio.
-- Introduzione della negazione, ¬I.
-- Per dimostrare `¬A`, introduciamo l'assunzione A e deriviamo False.
-- Esempio: se bevo caffè resto sveglio, ma non resto sveglio; dunque non ho
-- bevuto caffè.
theorem
(BevoCaffe RestoSveglio : Prop)
(hCaffeSveglio : BevoCaffe → RestoSveglio)
(hNonSveglio : ¬RestoSveglio) :
¬BevoCaffe := BevoCaffe:PropRestoSveglio:ProphCaffeSveglio:BevoCaffe → RestoSvegliohNonSveglio:¬RestoSveglio⊢ ¬BevoCaffe
BevoCaffe:PropRestoSveglio:ProphCaffeSveglio:BevoCaffe → RestoSvegliohNonSveglio:¬RestoSvegliohBevoCaffe:BevoCaffe⊢ False
BevoCaffe:PropRestoSveglio:ProphCaffeSveglio:BevoCaffe → RestoSvegliohNonSveglio:¬RestoSvegliohBevoCaffe:BevoCaffehRestoSveglio:RestoSveglio⊢ False
BevoCaffe:PropRestoSveglio:ProphCaffeSveglio:BevoCaffe → RestoSvegliohNonSveglio:¬RestoSvegliohBevoCaffe:BevoCaffehRestoSveglio:RestoSvegliohContraddizione:False⊢ False
All goals completed! 🐙Per eliminare una negazione, applichiamo ¬A ad A e otteniamo False.
-- Eliminazione della negazione, ¬E.
-- Da ¬A e A otteniamo False.
-- Esempio: il laboratorio è aperto e non è aperto; le assunzioni sono contraddittorie.
theorem
(LaboratorioAperto : Prop)
(hLaboratorioAperto : LaboratorioAperto)
(hLaboratorioNonAperto : ¬LaboratorioAperto) :
False := LaboratorioAperto:ProphLaboratorioAperto:LaboratorioApertohLaboratorioNonAperto:¬LaboratorioAperto⊢ False
LaboratorioAperto:ProphLaboratorioAperto:LaboratorioApertohLaboratorioNonAperto:¬LaboratorioApertohContraddizione:False⊢ False
All goals completed! 🐙Verità e falsità
True ha un costruttore canonico e non richiede assunzioni:
-- Introduzione di True, ⊤I.
-- True ha un costruttore canonico e non richiede premesse.
theorem : True := ⊢ True
All goals completed! 🐙 -- Oppure: trivialexact True.intro può anche essere sostituito con la tattica trivial.
False, invece, non ha costruttori. Se il contesto contiene già una dimostrazione di False, possiamo eliminare la falsità e ottenere qualunque proposizione. Questo principio è chiamato anche ex falso quodlibet, dal falso segue qualsiasi proposizione: apply False.elim può anche essere sostituito con la tattica exfalso, infatti.
-- Eliminazione di False, ⊥E.
-- Da una contraddizione possiamo ottenere qualunque proposizione.
-- Esempio: da assunzioni contraddittorie concludiamo che piove.
theorem
(Piove : Prop)
(hContraddizione : False) :
Piove := Piove:ProphContraddizione:False⊢ Piove
Piove:ProphContraddizione:False⊢ False -- Oppure: exfalso
All goals completed! 🐙Questa regola non dice che possiamo dimostrare arbitrariamente qualsiasi cosa: possiamo farlo se abbiamo già costruito una contraddizione.
9. Bicondizionale
Una dimostrazione di A ↔ B contiene due funzioni: una da A a B e una da B ad A.
Introduzione del bicondizionale, ↔I
Per dimostrare che «piove e fa freddo» equivale a «fa freddo e piove», applichiamo Iff.intro. Nel primo caso costruiamo la direzione da sinistra a destra; nel secondo costruiamo la direzione inversa.
-- Introduzione del bicondizionale, ↔I.
-- Costruiamo entrambe le direzioni e le uniamo.
-- Esempio: `Piove ∧ FaFreddo` equivale a `FaFreddo ∧ Piove`.
theorem
(Piove FaFreddo : Prop) :
Piove ∧ FaFreddo ↔ FaFreddo ∧ Piove := Piove:PropFaFreddo:Prop⊢ Piove ∧ FaFreddo ↔ FaFreddo ∧ Piove
Piove:PropFaFreddo:Prop⊢ Piove ∧ FaFreddo → FaFreddo ∧ PiovePiove:PropFaFreddo:Prop⊢ FaFreddo ∧ Piove → Piove ∧ FaFreddo
Piove:PropFaFreddo:Prop⊢ Piove ∧ FaFreddo → FaFreddo ∧ Piove Piove:PropFaFreddo:ProphPioveEFaFreddo:Piove ∧ FaFreddo⊢ FaFreddo ∧ Piove
Piove:PropFaFreddo:ProphPioveEFaFreddo:Piove ∧ FaFreddo⊢ FaFreddoPiove:PropFaFreddo:ProphPioveEFaFreddo:Piove ∧ FaFreddo⊢ Piove
Piove:PropFaFreddo:ProphPioveEFaFreddo:Piove ∧ FaFreddo⊢ FaFreddo All goals completed! 🐙
Piove:PropFaFreddo:ProphPioveEFaFreddo:Piove ∧ FaFreddo⊢ Piove All goals completed! 🐙
Piove:PropFaFreddo:Prop⊢ FaFreddo ∧ Piove → Piove ∧ FaFreddo Piove:PropFaFreddo:ProphFaFreddoEPiove:FaFreddo ∧ Piove⊢ Piove ∧ FaFreddo
Piove:PropFaFreddo:ProphFaFreddoEPiove:FaFreddo ∧ Piove⊢ PiovePiove:PropFaFreddo:ProphFaFreddoEPiove:FaFreddo ∧ Piove⊢ FaFreddo
Piove:PropFaFreddo:ProphFaFreddoEPiove:FaFreddo ∧ Piove⊢ Piove All goals completed! 🐙
Piove:PropFaFreddo:ProphFaFreddoEPiove:FaFreddo ∧ Piove⊢ FaFreddo All goals completed! 🐙Eliminazione del bicondizionale, ↔Eₗ e ↔Eᵣ
Iff.mp estrae la direzione sinistra-destra ( la direzione del modus ponens), mentre Iff.mpr estrae la direzione destra-sinistra (modus ponens reversed).
-- Eliminazione sinistra-destra del bicondizionale, ↔Eₗ.
-- Estraiamo A → B e la applichiamo ad A.
theorem
(NumeroPari DivisibilePerDue : Prop)
(hEquivalenza : NumeroPari ↔ DivisibilePerDue)
(hNumeroPari : NumeroPari) :
DivisibilePerDue := NumeroPari:PropDivisibilePerDue:ProphEquivalenza:NumeroPari ↔ DivisibilePerDuehNumeroPari:NumeroPari⊢ DivisibilePerDue
NumeroPari:PropDivisibilePerDue:ProphEquivalenza:NumeroPari ↔ DivisibilePerDuehNumeroPari:NumeroParihDirezione:NumeroPari → DivisibilePerDue⊢ DivisibilePerDue
NumeroPari:PropDivisibilePerDue:ProphEquivalenza:NumeroPari ↔ DivisibilePerDuehNumeroPari:NumeroParihDirezione:NumeroPari → DivisibilePerDuehDivisibilePerDue:DivisibilePerDue⊢ DivisibilePerDue
All goals completed! 🐙-- Eliminazione destra-sinistra del bicondizionale, ↔Eᵣ.
-- Estraiamo B → A e la applichiamo a B.
theorem
(NumeroPari DivisibilePerDue : Prop)
(hEquivalenza : NumeroPari ↔ DivisibilePerDue)
(hDivisibilePerDue : DivisibilePerDue) :
NumeroPari := NumeroPari:PropDivisibilePerDue:ProphEquivalenza:NumeroPari ↔ DivisibilePerDuehDivisibilePerDue:DivisibilePerDue⊢ NumeroPari
NumeroPari:PropDivisibilePerDue:ProphEquivalenza:NumeroPari ↔ DivisibilePerDuehDivisibilePerDue:DivisibilePerDuehDirezione:DivisibilePerDue → NumeroPari⊢ NumeroPari
NumeroPari:PropDivisibilePerDue:ProphEquivalenza:NumeroPari ↔ DivisibilePerDuehDivisibilePerDue:DivisibilePerDuehDirezione:DivisibilePerDue → NumeroParihNumeroPari:NumeroPari⊢ NumeroPari
All goals completed! 🐙Dopo aver estratto la direzione necessaria, la applichiamo come una normale implicazione.
10. Come scegliere una regola
Di fronte a un proof state, conviene porsi alcune domande.
La prima riguarda il goal: qual è il suo connettivo principale? Se il goal è un'implicazione, una congiunzione, una disgiunzione, una negazione o un bicondizionale, una regola di introduzione suggerisce come costruirlo.
La seconda riguarda il contesto: quali dimostrazioni composte sono già disponibili? Un'implicazione può essere applicata; una congiunzione può essere scomposta; una disgiunzione richiede casi; una negazione può essere applicata alla proposizione che nega; un bicondizionale offre due direzioni.
Le principali corrispondenze operative sono:
| Tattica o termine Lean | Lettura nella deduzione naturale |
|---|---|
intro h | introduciamo un'assunzione |
apply C | applichiamo un costruttore o una regola |
have h := ... | abbiamo un nuovo fatto intermedio |
exact h | usiamo esattamente il termine richiesto |
cases h with | abbiamo i diversi casi possibili |
h.left, h.right | eliminiamo una congiunzione |
hAB hA | eliminiamo un'implicazione |
Questa terminologia descrive la struttura del termine che Lean sta costruendo e rende leggibile il rapporto tra regola formale, argomento in linguaggio naturale e proof script.
11. Strategia di lavoro
Quando costruiamo una dimostrazione:
1. leggiamo il goal e le assunzioni; 2. identifichiamo il connettivo principale del goal; 3. scegliamo una regola di introduzione, se il goal lo suggerisce; 4. cerchiamo nelle assunzioni una regola di eliminazione utile; 5. rendiamo espliciti con have i risultati intermedi importanti; 6. chiudiamo il goal con exact quando abbiamo un termine del tipo richiesto; 7. controlliamo che tutti i casi e tutti i sotto-goal siano stati risolti.
Il file Classroom.lean mostra ciascuna regola separatamente. Il file Exercises.lean propone prima applicazioni dirette e poi argomenti che combinano più regole. Il file Solutions.lean mostra come ogni passaggio in Lean possa essere ricondotto alla regola di deduzione naturale corrispondente.
12. Fonti e letture
- Jeremy Avigad, Leonardo de Moura, Soonho Kong e Sebastian Ullrich, Theorem Proving in Lean 4, capitolo «Propositions and Proofs»: https://lean-lang.org/theorem_proving_in_lean4/Propositions-and-Proofs/.
- Jeremy Avigad, Robert Y. Lewis e Floris van Doorn, Logic and Proof, «Natural Deduction Rules»: https://leanprover.github.io/logic_and_proof_lean3/nd_quickref.html.
- Philip Wadler, «Propositions as Types», Communications of the ACM 58(12), 2015: https://homepages.inf.ed.ac.uk/wadler/papers/propositions-as-types/propositions-as-types.pdf.
- NASA Science, «Io: Facts»: https://science.nasa.gov/jupiter/jupiter-moons/io/facts/.