\chapter{Esempio: il Teorema di Euclide} \label{chap:euclid} \setcounter{sessioncount}{0} In questo capitolo, dimostriamo in \holn{} che per ogni numero, c'è un numero primo che è più grande, cioè, che i numeri primi formano una sequenza infinita. Questa dimostrazione è stata estratta e adattata da un più ampio esempio, dovuto a John Harrison, in cui egli dimostrava il caso $n = 4$ dell'ultimo teorema di Fermat. Lo sviluppo della dimostrazione ha lo scopo di servire come un'introduzione all'esecuzione di dimostrazioni interattive di alto livello in \holn\footnote{Le dimostrazioni discusse di sotto si possono trovare in \texttt{examples/euclid.sml} della distribuzione \HOL{}.}. Molti dei dettagli possono essere difficili da cogliere per il lettore alle prime armi; ciononostante, si raccomanda di seguire l'esempio in modo completo al fine di ottenere una vera sensazione dell'uso di \HOL{} per dimostrare teoremi non-banali. Alcuni tutorial sulla descrizione di sistemi di dimostrazione, mostrano il sistema mentre esegue incredibili gesta di dimostrazione automatica di teoremi. In questo esempio, \textit{non} abbiamo seguito questo approccio; piuttosto, cerchiamo di mostrare come si affronta nella realtà il problema di dimostrare teoremi in \holn{}: quando è possibile più di un modo per dimostrare qualcosa, considereremo le scelte; quando sorge una difficoltà, tenteremo di spiegare come affrontare il problema in modo chiaro. Si `guida' \holn{} interagendo con il loop interattivo ML. In questo stile interattivo, si eseguono chiamate a funzioni ML per richiamare un contesto logico già stabilito, ad esempio, attraverso \ml{load}; per definire nuovi concetti, ad esempio, attraverso \ml{Hol\_datatype}, \ml{Define}, e \ml{Hol\_reln}; e per eseguire dimostrazioni usando l'interfaccia goalstack, e gli strumenti di dimostrazione da \ml{bossLib} (o se essi falliscono, dalle librerie di basso livello). Cominciamo. Per prima cosa, avviamo il sistema, con il comando \ml{/bin/hol}. Quindi con ``\ml{open}'' apriamo la teoria aritmetica; questo significa che tutti i binding \ML{} dalla teoria \HOL{} sono resi disponibili al loop interattivo. \begin{session} \begin{verbatim} - open arithmeticTheory; ... \end{verbatim} \end{session} Ora iniziamo la formalizzazione. Al fine di definire il concetto di numero primo, abbiamo prima bisogno di definire la relazione di \emph{divisibilità}: \begin{session} \begin{verbatim} - val divides_def = Define `divides a b = ?x. b = a * x`; Definition has been stored under "divides_def". > val divides_def = |- !a b. divides a b = ?x. b = a * x : thm \end{verbatim} \end{session} La definizione è aggiunta alla teoria corrente con il nome \ml{divides\_def}, ed è anche restituita dall'invocazione di \ml{Define}. Approfittiamo di questo ed effettuiamo un binding del nome \ML{} \ml{divides\_def} alla definizione. Nel modo usuale di interagire con \HOL, un tale binding \ML{} è fatto per ogni definizione e teorema (utile) dimostrato: l'ambiente \ML{} viene così usato come un luogo conveniente per mantenere definizioni e teoremi per un futuro riferimento nella sessione. Vogliamo trattare \ml{divides} come un infisso (non-associativo): \begin{session} \begin{verbatim} - set_fixity "divides" (Infix(NONASSOC, 450)); \end{verbatim} \end{session} Poi definiamo la proprietà di essere un numero \emph{primo}: un numero $p$ è primo se e solo se non è uguale a $1$ e non ha altri divisori oltre $1$ e sé stesso: \begin{session} \begin{verbatim} - val prime_def = Define `prime p = ~(p=1) /\ !x. x divides p ==> (x=1) \/ (x=p)`; Definition has been stored under "prime_def". > val prime_def = |- !p. prime p = ~(p = 1) /\ !x. x divides p ==> (x = 1) \/ (x = p) : thm \end{verbatim} \end{session} Questo conclude le definizioni da fare. Ora dobbiamo ``solo'' dimostrare che ci sono infiniti numeri primi. Se fossimo arrivati ad affrontare questo problema senza un background, allora saremmo costretti a passare per un processo non completamente chiaro e spesso tremendamente difficile di ricerca dei giusti lemmi richiesti per dimostrare il nostro teorema desiderato\footnote{Questo naturalmente è un problema generale quando si affronta qualsiasi tipo di dimostrazione.}. Fortunatamente, stiamo lavorando a partire da un dimostrazione già completata e possiamo dedicarci al problema decisamente più semplice di spiegare come dimostrare i teoremi richiesti. \paragraph{Strumenti di dimostrazione} Lo sviluppo illustrerà che c'è spesso più di un modo di affrontare una dimostrazione HOL, persino quando si ha in mente un'unica dimostrazione (informale). In questo esempio, spesso \emph{troviamo} dimostrazioni usando la tattica di riscrittura \ml{RW\_TAC} per espandere le definizioni ed eseguire semplificazioni di base, riducendo spesso un goal alla sua essenza. \begin{session} \begin{verbatim} RW_TAC; val it = fn :simpset -> thm list -> term list * term -> (term list * term) list * (thm list -> thm) \end{verbatim} \end{session} Il tipo ML di \ml{RW\_TAC} è \ml{:simpset -> thm list -> tactic}\footnote{Sfortunatamente, il sistema MoscowML non stampa il tipo delle tattiche nella sua forma abbreviata.}. Quando \ml{RW\_TAC} è applicata a un \textit{simpset}---per questo esempio sarà sempre \ml{arith\_ss}---e a una lista di teoremi, i teoremi saranno aggiunti al simpset come regole di riscrittura aggiuntive. Vedremo che \ml{arith\_ss} è anche informato in qualche modo circa l'aritmetica\footnote{Specialmente l'aritmetica lineare: formule puramente universali, che coinvolgono gli operatori {\tt SUC}, $+$, $-$, letterali numerici, $<$, $\leq$, $>$, $\geq$, $=$, e la moltiplicazione per letterali numerici}. A volte la semplificazione con \ml{RW\_TAC} dimostra il goal immediatamente. Spesso comunque, ci rimane un goal che richiede un pò di studio prima di realizzare quali lemmi sono necessari per concludere la dimostrazione. Una volta che questi lemmi sono stati dimostrati, o individuati nelle teorie progenitrici, \ml{METIS\_TAC}\footnote{\ml{METIS\_TAC} esegue una ricerca della dimostrazione al primo ordine nello stile della risoluzione.} può essere invocata con essi, con la speranza che essa individuerà le istanziazioni corrette necessarie per concludere la dimostrazione. Si noti che queste due operazioni, la semplificazione e la ricerca automatica di una dimostrazione nello stile della risoluzione, non saranno sufficienti per eseguire tutte le dimostrazioni in questo esempio: in particolare, il nostro sviluppo, avrà anche bisogno dell'analisi dei casi e dell'induzione. \paragraph{Trovare i teoremi} Questo fa sorgere la seguente domanda: come si trovano i giusti lemmi e le regole di riscrittura da usare? Questo è piuttosto problematico, specialmente dal momento che il numero di teorie progenitrici, e di teoremi al loro interno, è grande. Ci sono molte possibilità. \begin{itemize} \item Si può usare il sistema di help per cercare definizioni e teoremi, così come procedure di dimostrazione; per esempio, un'invocazione di {\small \begin{verbatim} help "arithmeticTheory" \end{verbatim}} mostrerà tutte le definizioni e i teoremi che sono stati archiviati nella teoria dell'aritmetica. Comunque, bisogna conoscere il nome completo dell'elemento che si sta cercando prima che il sistema di help sia utile, così i due seguenti strumenti di ricerca sono spesso più utili. \item \verb+DB.match+ permette l'uso di pattern per individuare il teorema cercato. Sarà restituito qualunque teorema archiviato che ha un'istanza del pattern come un sotto termine. \item \verb+DB.find+ userà frammenti dei nomi come chiavi con cui cercare l'informazione \end{itemize} \paragraph{Composizione di tattiche} Una volta che è stata trovata una dimostrazione di una proposizione, è d'abitudine, benché non necessario, avviare un processo di \emph{revisione}, in cui la sequenza di tattiche originaria è composta in un'unica tattica. A volte, la tattica risultante è molto più breve, e in un certo senso più gradevole da un punto di vista estetico. Alcuni utenti spendono un bel pò di tempo per raffinare queste tattiche, nonostante non sembri esserci un reale maggior beneficio nel fare questo, dal momento che esse sono ricette di dimostrazione \emph{ad hoc}, una per ciascun teorema. Nel seguito, mostreremo in pochi casi come questo viene fatto. \section{Divisibilità} Iniziamo dimostrando un numero di teoremi circa la relazione \verb+divides+. Vedremo che ciascuno di questi teoremi iniziali può essere dimostrato con una singola invocazione di \ml{METIS\_TAC}. Sia \ml{RW\_TAC} sia \ml{METIS\_TAC} sono ragionatori piuttosto potenti, e la scelta di un ragionatore in una situazione particolare è una questione di esperienza. La ragione principale per cui \ml{METIS\_TAC} funziona così bene è che \verb+divides+ è definita per mezzo di un quantificatore esistenziale, e \ml{METIS\_TAC} è piuttosto buono per istanziare in modo automatico gli esistenziali nel corso di una dimostrazione. Per un semplice esempio, si consideri la dimostrazione di $\forall x.\ x\; \mathtt{divides}\; 0$. Si inserisce una nuova proposizione da dimostrare attraverso ``\ml{g}'', che avvia un nuovo goalstack. \begin{session} \begin{verbatim} - g `!x. x divides 0`; > val it = Proof manager status: 1 proof. 1. Incomplete: Initial goal: !x. x divides 0 : proofs \end{verbatim} \end{session} Il manager di dimostrazione ci dice che c'è soltanto una dimostrazione da gestire, e ripete il goal dato. Ora espandiamo la definizione di \verb+divides+. Si noti che ha luogo un'$\alpha$-conversione al fine di mantenere distinta la $x$ del goal e la $x$ nella definizione di \ml{divides}: \begin{session} \begin{verbatim} - e (RW_TAC arith_ss [divides_def]); OK.. 1 subgoal: > val it = ?x'. (x = 0) \/ (x' = 0) \end{verbatim} \end{session} E' naturalmente piuttosto facile istanziare il quantificatore esistenziale a mano. \begin{session} \begin{verbatim} - e (EXISTS_TAC ``0``); OK.. 1 subgoal: > val it = (x = 0) \/ (0 = 0) \end{verbatim} \end{session} Poi un passo di semplificazione conclude la dimostrazione. \begin{session} \begin{verbatim} - e (RW_TAC arith_ss []); OK.. Goal proved. |- (x = 0) \/ (0 = 0) Goal proved. |- ?x'. (x = 0) \/ (x' = 0) > val it = Initial goal proved. |- !x. x divides 0 \end{verbatim} \end{session} Cosa è appena successo qui? L'applicazione di \ml{RW\_TAC} al goal lo ha decomposto in una lista vuota di subgoal; in altre parole, il goal è stato dimostrato da \ml{RW\_TAC}. Una volta che il goal è stato dimostrato, è tirato fuori dal goalstack, stampato a video, e il teorema diventa disponibile per l'uso al livello dello stack. Quando tutti i subgoal richiesti da \textit{quel} livello sono stati dimostrati, anche il goal corrispondente a quel livello può essere dimostrato. Questo processo di `svolgimento' continua fino a quando lo stack è vuoto, o fino a quando raggiunge un goal con più di un subgoal non dimostrato rimanente. Questo processo può essere difficile da visualizzare,\footnote{Forse per il fatto che abbiamo usato uno stack per implementare quello che teoricamente è un albero!} ma ciò non importa, dal momento che il goalstack è stato scritto espressamente per permettere all'utente d'ignorare tali dettagli. Se le tre interazioni sono combinate insieme con \ml{THEN} per formare una singola tattica, possiamo provare la dimostrazione dall'inizio (usando la funzione \ml{restart}) e questa volta richiederà soltanto un unico passo: \begin{session} \begin{verbatim} - restart(); > ... - e (RW_TAC arith_ss [divides_def] THEN EXISTS_TAC ``0`` THEN RW_TAC arith_ss []); OK.. > val it = Initial goal proved. |- !x. x divides 0 \end{verbatim} \end{session} Abbiamo visto un modo di dimostrare il teorema. Comunque, come menzionato in precedenza, c'è un altro modo: si potrebbe lasciare che \ml{METIS\_TAC} espanda la definizione di \ml{divides} e trovi l'istanziazione richiesta per \verb+x'+ dal teorema \ml{MULT\_CLAUSES}\footnote{Potreste voler provare a digitare \ml{MULT\_CLAUSES} nel loop interativo per vedere esattamente cosa esso afferma.}. \begin{session} \begin{verbatim} - restart(); > ... - e (METIS_TAC [divides_def, MULT_CLAUSES]); OK.. metis: r[+0+10]+0+0+0+1+2# > val it = Initial goal proved. |- !x. x divides 0 \end{verbatim} \end{session} Mentre lavora, \ml{METIS\_TAC} stampa a video alcune diagnostiche possibilmente interessanti. In ogni caso, avendo eseguito la nostra dimostrazione all'interno del pacchetto goalstack, ora vogliamo avere accesso al valore del teorema che abbiamo dimostrato. Per far questo usiamo la funzione \ml{top\_thm}, e quindi usiamo \ml{drop} per svuotare lo stack: \begin{session} \begin{verbatim} - val DIVIDES_0 = top_thm(); > val DIVIDES_0 = |- !x. x divides 0 : thm - drop(); OK.. > val it = There are currently no proofs. : proofs \end{verbatim} \end{session} Abbiamo usato \ml{METIS\_TAC} in questo modo per dimostrare la seguente collezione di teoremi circa \ml{divides}. Come menzionato in precedenza, i teoremi forniti a \ml{METIS\_TAC} nelle seguenti dimostrazioni (di solito) non provengono dal nulla: in molti casi è stato fatto un qualche lavoro esplorativo con \ml{RW\_TAC} per espandere le definizioni e vedere quali lemmi sarebbero stati richiesti da \ml{METIS\_TAC}. \begin{description} \label{euclid:extra-proofs} \item [\small{({\it DIVIDES\_0\/})}] \begin{tabular}[t]{l} \verb+!x. x divides 0+ \\ \hline \verb+METIS_TAC [divides_def, MULT_CLAUSES]+ \end{tabular} \item[\small{({\it DIVIDES\_ZERO\/})}] \begin{tabular}[t]{l} \verb+!x. 0 divides x = (x = 0)+ \\ \hline \verb+METIS_TAC [divides_def, MULT_CLAUSES]+ \end{tabular} \item[\small{({\it DIVIDES\_ONE\/})}] \begin{tabular}[t]{l} \verb+!x. x divides 1 = (x = 1)+ \\ \hline \verb+METIS_TAC [divides_def, MULT_CLAUSES, MULT_EQ_1]+ \end{tabular} \item[\small{({\it DIVIDES\_REFL\/})}] \begin{tabular}[t]{l} \verb+!x. x divides x+ \\ \hline \verb+METIS_TAC [divides_def, MULT_CLAUSES]+ \\ \end{tabular} \item[\small{({\it DIVIDES\_TRANS\/})}] \begin{tabular}[t]{l} \verb+!a b c. a divides b /\ b divides c ==> a divides c+ \\ \hline \verb+METIS_TAC [divides_def, MULT_ASSOC]+ \\ \end{tabular} \item[\small{({\it DIVIDES\_ADD\/})}] \begin{tabular}[t]{l} \verb|!d a b. d divides a /\ d divides b ==> d divides (a+b)| \\ \hline \verb|METIS_TAC [divides_def,LEFT_ADD_DISTRIB]| \end{tabular} \item[\small{({\it DIVIDES\_SUB\/})}] \begin{tabular}[t]{l} \verb+!d a b. d divides a /\ d divides b ==> d divides (a-b)+ \\ \hline \verb+METIS_TAC [divides_def, LEFT_SUB_DISTRIB]+ \\ \end{tabular} \item[\small{({\it DIVIDES\_ADDL\/})}] \begin{tabular}[t]{l} \verb|!d a b. d divides a /\ d divides (a+b) ==> d divides b| \\ \hline \verb+METIS_TAC [ADD_SUB, ADD_SYM, DIVIDES_SUB]+ \\ \end{tabular} \item[\small{({\it DIVIDES\_LMUL\/})}] \begin{tabular}[t]{l} \verb+!d a x. d divides a ==> d divides (x * a)+ \\ \hline \verb+METIS_TAC [divides_def, MULT_ASSOC, MULT_SYM]+ \\ \end{tabular} \item[\small{({\it DIVIDES\_RMUL\/})}] \begin{tabular}[t]{l} \verb+!d a x. d divides a ==> d divides (a * x)+ \\ \hline \verb+METIS_TAC [MULT_SYM, DIVIDES_LMUL]+ \\ \end{tabular} \end{description} \noindent Assumeremo che le dimostrazioni di sopra siano state eseguite, e che gli appropriati nomi ML siano stati dati a tutti i teoremi. Ora affrontiamo un lemma circa la divisibilità che non soccombe a una singola invocazione di \ml{METIS\_TAC}: \begin{description} \item [\small{({\it DIVIDES\_LE\/})}] \begin{tabular}[t]{l} \verb+!m n. m divides n ==> m <= n \/ (n = 0)+ \\ \hline \verb+RW_TAC arith_ss [divides_def]+ \\ \verb+ THEN Cases_on `x`+ \\ \verb+ THEN RW_TAC arith_ss [MULT_CLAUSES]+ \\ \end{tabular} \end{description} Vediamo come questo è dimostrato. Il modo più semplice è di iniziare a semplificare con la definizione di \ml{divides}: \begin{session} \begin{verbatim} - g `!m n . m divides n ==> m <= n \/ (n = 0)`; > ... - e (RW_TAC arith_ss [divides_def]); 1 subgoal: > val it = m <= m * x \/ (m * x = 0) \end{verbatim} \end{session} Considerando il goal, di base abbiamo tre possibilità: (1) trovare una collezione di lemmi che insieme implicano il goal e usare \ml{METIS\_TAC}; (2) fare un case split su $m$; oppure (3) fare un case split su $x$. La prima non sembra semplice, dal momento che il goal in realtà non calza nella `forma' di un qualsiasi teorema pre-dimostrato di cui l'autore sia a conoscenza. Benché l'opzione (2) sarà rigettata alla fine, proviamola comunque. Per eseguire il case split, usiamo \verb+Cases_on+, che sta per ``trova il termine dato nel goal ed esegui un'analisi dei casi sulle possibilità di costruzione previste dal costruttore del suo datatype'' [corrisponde al pattern matching di ML (ndt)]. Dal momento che l'occorrenza di $m$ nel goal ha il tipo $num$, i casi considerati saranno se $m$ è $0$ o un successore. \begin{session} \begin{verbatim} - e (Cases_on `m`); OK.. 2 subgoals: > val it = SUC n <= SUC n * x \/ (SUC n * x = 0) 0 <= 0 * x \/ (0 * x = 0) \end{verbatim} \end{session} \noindent Il primo subgoal (l'ultimo stampato) è banale: \begin{session} \begin{verbatim} - e (RW_TAC arith_ss []); OK.. Goal proved. ... Remaining subgoals: > val it = SUC n <= SUC n * x \/ (SUC n * x = 0) \end{verbatim} \end{session} \noindent Proviamo \ml{RW\_TAC} di nuovo: \begin{session} \begin{verbatim} - e (RW_TAC arith_ss []); OK.. 1 subgoal: > val it = SUC n <= x * SUC n \/ (x = 0) \end{verbatim} \end{session} Il disgiunto a destra è stato semplificato; comunque, il disgiunto a sinistra ha fallito di espandere la definizione della moltiplicazione nell'espressione $\mathtt{SUC}\ n * x$, cosa che sarebbe stata conveniente. Di fatto l'ha cambiata in $x * \mathtt{SUC}\ n$, che non è conveniente. Perché, visto che si suppone che \verb+arith_ss+ e di conseguenza \ml{RW\_TAC} siano esperte nell'aritmetica? La risposta è duplice: primo, le clausole ricorsive per l'addizione e la moltiplicazione non sono in \verb+arith_ss+ perché una loro applicazione senza controllo da parte del sistema di riscrittura sembra che, in generale, renda alcune dimostrazioni \emph{più} complicate, piuttosto che più semplici; in secondo luogo, il semplificatore riorganizzerà i termini aritmetici per rendere più semplici alcune dimostrazioni automatiche. Così l'assenza delle clausole ricorsive per la moltiplicazione significa che $\mathtt{SUC}\ n * x$ non si espande a $(n * x) + x$; piuttosto, la riorganizzazione porta a $x * \mathtt{SUC}\; n$. OK, aggiungiamo la definizione della moltiplicazione. Questo rivela un nuovo problema: come individuare questa definizione. La funzione \begin{holboxed} \begin{verbatim} DB.match : string list -> term -> ((string * string) * (thm * class)) list \end{verbatim} \end{holboxed} \noindent è spesso utile a questo scopo. Essa prende una lista di nomi di teorie, e un pattern, e cerca nella lista delle teorie un teorema, una definizione, o un assioma che ha un'istanza del pattern come sotto termine. Se la lista dei nomi di teorie è vuota, allora sono incluse nella ricerca tutte le teorie caricate. Cerchiamo nella teoria dell'aritmetica il sotto termine che deve essere riscritto. \begin{session} \begin{verbatim} - DB.match ["arithmetic"] ``SUC n * x``; > val it = [(("arithmetic", "FACT"), (|- (FACT 0 = 1) /\ !n. FACT (SUC n) = SUC n * FACT n, Def)), (("arithmetic", "LESS_MULT_MONO"), (|- !m i n. SUC n * m < SUC n * i = m < i, Thm)), (("arithmetic", "MULT"), (|- (!n. 0 * n = 0) /\ !m n. SUC m * n = m * n + n, Def)), (("arithmetic", "MULT_CLAUSES"), (|- !m n. (0 * m = 0) /\ (m * 0 = 0) /\ (1 * m = m) /\ (m * 1 = m) /\ (SUC m * n = m * n + n) /\ (m * SUC n = m + m * n), Thm)), (("arithmetic", "MULT_LESS_EQ_SUC"), (|- !m n p. m <= n = SUC p * m <= SUC p * n, Thm)), (("arithmetic", "MULT_MONO_EQ"), (|- !m i n. (SUC n * m = SUC n * i) = m = i, Thm)), (("arithmetic", "ODD_OR_EVEN"), (|- !n. ?m. (n = SUC (SUC 0) * m) \/ (n = SUC (SUC 0) * m + 1), Thm))] : ... \end{verbatim} \end{session} Per alcuni, questo restituisce un pò troppa informazione; comunque, possiamo focalizzare la ricerca stabilendo che il pattern appaia come una regola di riscrittura: \begin{session} \begin{verbatim} - DB.match [] ``SUC n * x = M``; > val it = [(("arithmetic", "MULT"), (|- (!n. 0 * n = 0) /\ !m n. SUC m * n = m * n + n, Def)), (("arithmetic", "MULT_CLAUSES"), (|- !m n. (0 * m = 0) /\ (m * 0 = 0) /\ (1 * m = m) /\ (m * 1 = m) /\ (SUC m * n = m * n + n) /\ (m * SUC n = m + m * n), Thm)), (("arithmetic", "MULT_MONO_EQ"), (|- !m i n. (SUC n * m = SUC n * i) = m = i, Thm))] : ... \end{verbatim} \end{session} Sia {\small\verb+arithmeticTheory.MULT+} sia {\small\verb+arithmeticTheory.MULT_CLAUSES+} sarebbero accettabili; scegliamo l'ultima. \begin{session} \begin{verbatim} - b(); ... e (RW_TAC arith_ss [MULT_CLAUSES]); OK.. 1 subgoal: > val it = SUC n <= x + n * x \/ (x = 0) \end{verbatim} \end{session} Ora vediamo che, al fine di fare progressi nella dimostrazione, dovremo fare un case split su $x$ comunque, e che avremmo dovuto fare lo split su di esso fin dall'inizio. Di conseguenza facciamo un backup. Dovremo fare un backup (undo) tre volte. \begin{session} \begin{verbatim} - b(); > val it = SUC n <= SUC n * x \/ (SUC n * x = 0) - b(); > val it = SUC n <= SUC n * x \/ (SUC n * x = 0) 0 <= 0 * x \/ (0 * x = 0) - b(); > val it = m <= m * x \/ (m * x = 0) \end{verbatim} \end{session} Ora possiamo andare avanti e fare un'analisi dei casi su $x$. Eseguiremo anche\linebreak l'invocazione a una tattica composta, dal momento che sappiamo già che dovremo invocare \ml{RW\_TAC} in entrambi i rami dell'analisi dei casi. Questo può essere fatto usando \ml{THEN}. Quando $t_1 \ \mbox{\ml{THEN}}\ t_2$ è applicato a un goal $g$, prima $t_1$ è applicato a $g$, dando una lista di nuovi subgoal, poi $t_2$ è applicato ad ogni membro della lista. Tutti i goal risultanti da queste applicazioni di $t_2$ sono raccolti insieme e restituiti. \begin{session} \begin{verbatim} - e (Cases_on `x` THEN RW_TAC arith_ss [MULT_CLAUSES]); OK.. Goal proved. |- m <= m * x \/ (m * x = 0) > val it = Initial goal proved. |- !m n. m divides n ==> m <= n \/ (n = 0) \end{verbatim} \end{session} Questo è stato facile! Ovviamente fare un case split su $x$ era la scelta giusta. Il processo di {\it trovare\/} la dimostrazione ora è terminato, e tutto ciò che rimane da fare è impacchettare la dimostrazione in una singola tattica come abbiamo visto di sopra. Al posto di usare \ml{top\_thm} e il goalstack, possiamo bypassarlo e usare la funzione \ml{store\_thm}. Questa funzione prende una stringa, un termine e una tattica e applica la tattica al termine per ottenere un teorema, e quindi archivia il teorema nella teoria corrente sotto il nome dato. \begin{session} \begin{verbatim} - val DIVIDES_LE = store_thm ( "DIVIDES_LE", ``!m n. m divides n ==> m <= n \/ (n = 0)``, RW_TAC arith_ss [divides_def] THEN Cases_on `x` THEN RW_TAC arith_ss [MULT_CLAUSES]); > val DIVIDES_LE = |- !m n. m divides n ==> m <= n \/ (n = 0) : thm \end{verbatim} \end{session} Archiviare i teoremi nel nostro script di salvataggio della sessione in questo stile (piuttosto che con il goalstack) risulta in uno script più conciso, e rende anche più facile trasformare il nostro script in un file di teoria, come facciamo nella sessione~\ref{sec:script-to-theory}. \subsection{Divisibilità e fattoriale} Il prossimo lemma, {\small{\it DIVIDES\_FACT\/}}, dice che ogni numero maggiore di $0$ e $\leq n$ divide il fattoriale di $n$. Il fattoriale si trova in \verb+arithmeticTheory.FACT+ ed è stato definito per ricorsione primitiva: \begin{description} \item [\small{({\it FACT\/})}] \begin{minipage}[t]{0.5\textwidth} \begin{verbatim} (FACT 0 = 1) /\ (!n. FACT (SUC n) = SUC n * FACT n) \end{verbatim} \end{minipage} \end{description} Una dimostrazione lucidata di {\small{\it DIVIDES\_FACT\/}} è la seguente\footnote{Questa e le successive dimostrazioni usano i teoremi dimostrati alla pagina~\pageref{euclid:extra-proofs}, che sono state aggiunte all'ambiente \ML{} dopo essere state dimostrate.}: \begin{description} \item [\small{({\it DIVIDES\_FACT\/})}] \begin{tabular}[t]{l} \verb+!m n. 0 < m /\ m <= n ==> m divides (FACT n)+ \\ \hline \verb+RW_TAC arith_ss [LESS_EQ_EXISTS]+ \\ \verb+ THEN Induct_on `p`+ \\ \verb+ THEN RW_TAC arith_ss [FACT,ADD_CLAUSES]+ \\ \verb+ THENL [Cases_on `m`, ALL_TAC]+ \\ \verb+ THEN METIS_TAC [FACT, DECIDE ``!x. ~(x < x)``,+ \\ \verb+ DIVIDES_RMUL, DIVIDES_LMUL, DIVIDES_REFL]+ \\ \end{tabular} \end{description} Esamineremo questa dimostrazione nel dettaglio, così dovremmo prima tentare di comprendere perché il teorema è vero. Qual è l'intuizione sottostante? Supponiamo che $0 < m \leq n$, e così $\mbox{\tt FACT}\ n = 1 * \cdots * m * \cdots * n$. Mostrare che $m\ \mbox{\tt divides}\ (\mbox{\tt FACT}\ n)$ significa esibire un $q$ tale che $q * m = \mbox{\tt FACT}\ n$. Così $q = \mbox{\tt FACT}\ n \div m$. Se dovessimo intraprendere questo approccio alla dimostrazione, finiremmo per dover trovare e applicare dei lemmi circa $\div$. Questo sembra portarci un pò fuori strada; non c'è una dimostrazione che non usa la divisione? Ebbene sì, possiamo dimostrare il teorema per induzione su $n - m$: nel caso migliore, dovremo dimostrare $n\; \mbox{\tt divides}\ (\mbox{\tt FACT}\; n)$, che dovrebbe essere facile; nel caso induttivo, l'ipotesi d'induzione sembrerebbe doverci dare ciò di cui abbiamo bisogno. Questa strategia per il caso induttivo è un pò vaga, perché stiamo provando a figurarci mentalmente una formula piuttosto complicata, ma possiamo contare sul fatto che il sistema calcoli in modo accurato il i casi d'induzione per noi. Se il caso induttivo si rivelerà non essere ciò che ci aspettavamo, dovremo ripensare il nostro approccio. \begin{session} \begin{verbatim} - g `!m n. 0 < m /\ m <= n ==> m divides (FACT n)`; > val it = Proof manager status: 1 proof. 1. Incomplete: Initial goal: !m n. 0 < m /\ m <= n ==> m divides FACT n \end{verbatim} \end{session} Al posto di eseguire un'induzione direttamente su $n-m$, faremo un'induzione su una variabile testimone, ottenuta usando il teorema \verb+LESS_EQ_EXISTS+. \begin{session} \begin{verbatim} - LESS_EQ_EXISTS; > val it = |- !m n. m <= n = (?p. n = m + p) : thm - e (RW_TAC arith_ss [LESS_EQ_EXISTS]); OK.. 1 subgoal: > val it = m divides FACT (m + p) ------------------------------------ 0 < m \end{verbatim} \end{session} \noindent Ora eseguiamo un'induzione su $p$: \begin{session} \begin{verbatim} - e (Induct_on `p`); OK.. 2 subgoals: > val it = m divides FACT (m + SUC p) ------------------------------------ 0. 0 < m 1. m divides FACT (m + p) m divides FACT (m + 0) ------------------------------------ 0 < m \end{verbatim} \end{session} \noindent Il primo goal può ovviamente essere semplificato: \begin{session} \begin{verbatim} - e (RW_TAC arith_ss []); OK.. 1 subgoal: > val it = m divides FACT m ------------------------------------ 0 < m \end{verbatim} \end{session} \noindent Ora possiamo fare un'analisi dei casi su $m$: se è $0$, abbiamo un goal banale; se è un successore, allora possiamo usare la definizione di \ml{FACT} e i teoremi \ml{DIVIDES\_RMUL} e \ml{DIVIDES\_REFL}. \begin{session} \begin{verbatim} - e (Cases_on `m`); OK.. 2 subgoals: > val it = SUC n divides FACT (SUC n) ------------------------------------ 0 < SUC n 0 divides FACT 0 ------------------------------------ 0 < 0 \end{verbatim} \end{session} Qui il primo subgoal ha un'assunzione che è falsa, Possiamo dimostrare questo al sistema usando la funzione \ml{DECIDE} per dimostrare un semplice fatto dell'aritmetica (cioè, che nessun numero $x$ è minore di sé stesso), e quindi passare il teorema risultante a \ml{METIS\_TAC}, che può combinare questo con l'assunzione contraddittoria\footnote{Si noti come il sistema interattivo stampi il teorema dimostrato con \ml{[.]} prima del turnstile. Questa notazione indica che un teorema ha un'assunzione (in questo caso l'asserzione falsa $0<0$).}. \begin{session} \begin{verbatim} - e (METIS_TAC [DECIDE ``!x. ~(x < x)``]); OK.. metis: r[+0+4]# Goal proved. [.] |- 0 divides FACT 0 Remaining subgoals: > val it = SUC n divides FACT (SUC n) ------------------------------------ 0 < SUC n \end{verbatim} \end{session} Usando i teoremi identificati di sopra, il subgoal rimanente può essere dimostrato con \ml{RW\_TAC}. \begin{session} \begin{verbatim} - e (RW_TAC arith_ss [FACT, DIVIDES_LMUL, DIVIDES_REFL]); OK.. Goal proved. ... Remaining subgoals: > val it = m divides FACT (m + SUC p) ------------------------------------ 0. 0 < m 1. m divides FACT (m + p) \end{verbatim} \end{session} Quest'ultimo passo, cioè l'invocazione di \ml{RW\_TAC}, potrebbe essere ottenuta anche con \ml{METIS\_TAC}. Si noti che l'unica differenza è l'uso di \ml{DIVIDES\_LMUL} nel semplificatore \emph{al posto di} \ml{DIVIDES\_RMUL} in \ml{METIS\_TAC}. Questo è dovuto al già menzionato riarrangiamento algebrico dei termini aritmetici nel semplificatore. \begin{session} \begin{verbatim} - b(); > ... - e (METIS_TAC [FACT, DIVIDES_RMUL, DIVIDES_REFL]); OK.. Goal proved. ... \end{verbatim} \end{session} Ora abbiamo completato il caso base dell'induzione e possiamo spostarci al caso di passo. Una cosa ovvia da provare è la semplificazione con le definizioni dell'addizione e del fattoriale: \begin{session} \begin{verbatim} - e (RW_TAC arith_ss [FACT, ADD_CLAUSES]); OK.. 1 subgoal: > val it = m divides FACT (m + p) * SUC (m + p) ------------------------------------ 0. 0 < m 1. m divides FACT (m + p) \end{verbatim} \end{session} \noindent E ora, per mezzo di \ml{DIVIDES\_RMUL} e l'ipotesi d'induzione, abbiamo finito: \begin{session} \begin{verbatim} - e (METIS_TAC [DIVIDES_RMUL]); OK.. metis: r[+0+5]+0+0+0+0+1# Goal proved. ... > val it = Initial goal proved. |- !m n. 0 < m /\ m <= n ==> m divides FACT n \end{verbatim} \end{session} Abbiamo concluso la ricerca della dimostrazione, e possiamo spostarci al compito di creare una singola tattica a partire dalla sequenza delle invocazioni delle tattiche che abbiamo appena fatto. Assumiamo che sia stata tenuta traccia della sequenza delle invocazioni in un file o nel buffer di un editor di testo. Avremmo dunque qualcosa di simile a ciò che segue: \begin{hol} \begin{verbatim} e (RW_TAC arith_ss [LESS_EQ_EXISTS]); e (Induct_on `p`); (*1*) e (RW_TAC arith_ss []); e (Cases_on `m`); (*1.1*) e (METIS_TAC [DECIDE ``!x. ~(x < x)``]); (*1.2*) e (RW_TAC arith_ss [FACT, DIVIDES_LMUL, DIVIDES_REFL]); (*2*) e (RW_TAC arith_ss [FACT, ADD_CLAUSES]); e (METIS_TAC [DIVIDES_RMUL]); \end{verbatim} \end{hol} \noindent Abbiamo aggiunto uno schema di numerazione per tenere traccia dei rami nella dimostrazione. Possiamo cucire insieme l'espressioni di sopra nella seguente tattica composta: \begin{hol} \begin{verbatim} RW_TAC arith_ss [LESS_EQ_EXISTS] THEN Induct_on `p` THENL [RW_TAC arith_ss [] THEN Cases_on `m` THENL [METIS_TAC [DECIDE ``!x. ~(x < x)``], RW_TAC arith_ss [FACT, DIVIDES_LMUL, DIVIDES_REFL]], RW_TAC arith_ss [FACT, ADD_CLAUSES] THEN METIS_TAC [DIVIDES_RMUL]] \end{verbatim} \end{hol} \noindent Questo può essere testato per vedere che non abbiamo fatto errori: \begin{session} \begin{verbatim} - restart(); > ... - e (RW_TAC arith_ss [LESS_EQ_EXISTS] THEN Induct_on `p` THENL [RW_TAC arith_ss [] THEN Cases_on `m` THENL [METIS_TAC [DECIDE ``!x. ~(x < x)``], RW_TAC arith_ss [FACT, DIVIDES_LMUL, DIVIDES_REFL]], RW_TAC arith_ss [FACT, ADD_CLAUSES] THEN METIS_TAC [DIVIDES_RMUL]]); OK.. metis: r[+0+5]+0+0+0+0+1# metis: r[+0+4]# > val it = Initial goal proved. |- !m n. 0 < m /\ m <= n ==> m divides FACT n \end{verbatim} \end{session} Per molti utenti, questa sarebbe la conclusione di questa dimostrazione: la tattica ora può essere impacchettata in un'invocazione di \ml{prove}\footnote{La funzione \ml{prove} prende un termine e una tattica e tenta di dimostrare il termine usando la tattica fornita. Essa restituisce il teorema dimostrato se la tattica ha successo. Essa non salva il teorema alla teoria sviluppata.} o di \ml{store\_thm} e quella sarebbe la conclusione. Comunque, un altro utente potrebbe notare che questa tattica potrebbe essere abbreviata. Per iniziare, entrambi i rami dell'induzione iniziano con un'invocazione di \ml{RW\_TAC}, e la semantica di \ml{THEN} ci permette di fondere le occorrenze di \ml{RW\_TAC} prima di \ml{THENL}. La tattica rimaneggiata è \begin{hol} \begin{verbatim} RW_TAC arith_ss [LESS_EQ_EXISTS] THEN Induct_on `p` THEN RW_TAC arith_ss [FACT, ADD_CLAUSES] THENL [Cases_on `m` THENL [METIS_TAC [DECIDE ``!x. ~(x < x)``], RW_TAC arith_ss [FACT, DIVIDES_LMUL, DIVIDES_REFL]], METIS_TAC [DIVIDES_RMUL]] \end{verbatim} \end{hol} (Naturalmente, quando una tattica è stata rivisitata, dovrebbe essere testata per vedere se continua a dimostrare il goal!) Ora si ricordi che l'uso di \ml{RW\_TAC} nel caso base potrebbe essere sostituito da una chiamata a \ml{METIS\_TAC}. Così sembra possibile fondere i due sotto casi del caso base in un'unica invocazione di \ml{METIS\_TAC}: \begin{hol} \begin{verbatim} RW_TAC arith_ss [LESS_EQ_EXISTS] THEN Induct_on `p` THEN RW_TAC arith_ss [FACT, ADD_CLAUSES] THENL [Cases_on `m` THEN METIS_TAC[DECIDE ``!x. ~(x val it = |- !m. (m = 0) \/ ?n. m = SUC n : thm - restart(); - e (RW_TAC arith_ss [LESS_EQ_EXISTS] THEN Induct_on `p` THEN METIS_TAC [DECIDE ``!x. ~(x < x)``, FACT, num_CASES, DIVIDES_RMUL, DIVIDES_LMUL, DIVIDES_REFL, ADD_CLAUSES]); \end{verbatim} \end{session} Ora abbiamo concluso il nostro esercizio di revisione delle tattiche. Certamente, sarebbe difficile indovinare che questa tattica finale dimostrerebbe il goal; i lemmi richiesti per l'invocazione finale di \ml{METIS\_TAC} sono stati trovati attraverso un processo incrementale di revisione. \subsection{Divisibilità e fattoriale (di nuovo!)} Nella precedente dimostrazione, abbiamo fatto un passo di semplificazione iniziale al fine di esporre una variabile su cui indurre. Comunque, la dimostrazione in realtà è su $n - m$. Possiamo esprimere questo in modo diretto? La risposta è un sì con riserva: l'induzione può essere dichiarata in modo naturale, ma porta a dei goal in qualche modo meno naturali. \begin{session} \begin{verbatim} - restart(); - e (Induct_on `n - m`); OK.. 2 subgoals: > val it = !n m. (SUC v = n - m) ==> 0 < m /\ m <= n ==> m divides FACT n ------------------------------------ !n m. (v = n - m) ==> 0 < m /\ m <= n ==> m divides FACT n !n m. (0 = n - m) ==> 0 < m /\ m <= n ==> m divides FACT n \end{verbatim} \end{session} Questo è leggermente complicato da leggere, così usiamo \ml{STRIP\_TAC} e \ml{REPEAT} per spostare gli antecedenti dei goal nelle assunzioni. L'uso di \ml{THEN} assicura che la tatticca venga applicata in entrambi i rami dell'induzione. \begin{session} \begin{verbatim} - b(); ... - e (Induct_on `n - m` THEN REPEAT STRIP_TAC); OK.. 2 subgoals: > val it = m divides FACT n ------------------------------------ 0. !n m. (v = n - m) ==> 0 < m /\ m <= n ==> m divides FACT n 1. SUC v = n - m 2. 0 < m 3. m <= n m divides FACT n ------------------------------------ 0. 0 = n - m 1. 0 < m 2. m <= n \end{verbatim} \end{session} Osservando il primo goal, ragioniamo che se $0 = n - m$ e $m \leq n$, allora $m = n$. Possiamo dimostrare questo fatto, usando \ml{DECIDE\_TAC}\footnote {\ml{DECIDE\_TAC} è una procedura di decisione per un'utile classe di formule aritmetiche.} e aggiungerla alle ipotesi per mezzo dell'uso dell'operatore infisso ``\ml{by}'': \begin{session} \begin{verbatim} - e (`m = n` by DECIDE_TAC); OK.. 1 subgoal: > val it = m divides FACT n ------------------------------------ 0. 0 = n - m 1. 0 < m 2. m <= n 3. m = n \end{verbatim} \end{session} \noindent Ora possiamo usare \ml{RW\_TAC} per propagare l'uguaglianza appena derivata attraverso il goal. \begin{session} \begin{verbatim} - e (RW_TAC arith_ss []); OK.. 1 subgoal: > val it = m divides FACT m ------------------------------------ 0. 0 = m - m 1. 0 < m 2. m <= m \end{verbatim} \end{session} A questo punto nella dimostrazione precedente abbiamo fatto un'analisi dei casi su $m$. Comunque, abbiamo già l'ipotesi che $m$ è positivo (insieme alle altre due ipotesi al momento inutili). Così sappiamo che $m$ è il successore di qualche numero $k$. Potremmo desiderare di affermare questo fatto con un'invocazione di ``\ml{by}'' nel modo seguente: \[ \mbox{\ml{`?k. m = SUC k` by }} \] Ma qual è la tattica? Se proviamo \ml{DECIDE\_TAC}, essa fallirà dal momento che non gestisce enunciati esistenziali. Anche un'applicazione di \ml{RW\_TAC} si dimostrerà insoddisfacente. Cosa fare? Quando capita una situazione di questo tipo, è spesso meglio iniziare una nuova dimostrazione per il lemma richiesto. Questo si può fare semplicemente invocando ``\ml{g}'' di nuovo. Un nuovo goalstack sarà creato e aggiunto a quello corrente\footnote{Questa aggiunta di tentativi di dimostrazione (goalstack) è differente dall'aggiunta di goal e giustificazioni all'interno di un particolare goalstack.} e sarà stampata una panoramica dei tentativi di dimostrazione esistenti: \begin{session} \begin{verbatim} - g `!m. 0 < m ==> ?k. m = SUC k`; > val it = Proof manager status: 2 proofs. 2. Incomplete: Initial goal: !m n. 0 < m /\ m <= n ==> m divides FACT n Current goal: m divides FACT m ------------------------------------ 0. 0 = m - m 1. 0 < m 2. m <= m 1. Incomplete: Initial goal: !m. 0 < m ==> ?k. m = SUC k \end{verbatim} \end{session} Il nostro nuovo goal può essere dimostrato abbastanza velocemente. Una volta che lo abbiamo dimostrato, possiamo legarlo a un nome ML e usarlo nella dimostrazione precedente, per mezzo di una specie di gioco di prestigio con la funzione ``\ml{before}''\footnote{Una versione infissa del combinatore {\tt K}, definito da {\tt fun (x before y) = x}.} \begin{session} \begin{verbatim} - e (Cases THEN RW_TAC arith_ss []); OK.. > val it = Initial goal proved. |- !m. 0 < m ==> ?k. m = SUC k - val lem = top_thm() before drop(); OK.. > val lem = |- !m. 0 < m ==> ?k. m = SUC k : thm \end{verbatim} \end{session} Ora possiamo ritornare al filo principale della dimostrazione. Lo stato del subgoal corrente della dimostrazione può essere mostrato usando la funzione ``\ml{p}''. \begin{session} \begin{verbatim} - p (); > val it = m divides FACT m ------------------------------------ 0. 0 = m - m 1. 0 < m 2. m <= m \end{verbatim} \end{session} Ora possiamo usare \ml{lem} nella dimostrazione. Un pò opportunisticamente, vireremo sull'invocazione usata nella precedente dimostrazione (più o meno) allo stesso punto, sperando che questo risolva il goal: \begin{session} \begin{verbatim} - e (`?k. m = SUC k` by METIS_TAC[lem] THEN RW_TAC arith_ss [FACT, DIVIDES_LMUL, DIVIDES_REFL]); OK.. metis: r[+0+6]+0+0+0+0+0+1# Goal proved. ... Remaining subgoals: > val it = m divides FACT n ------------------------------------ 0. !n m. (v = n - m) ==> 0 < m /\ m <= n ==> m divides FACT n 1. SUC v = n - m 2. 0 < m 3. m <= n \end{verbatim} \end{session} Funziona! Questo si occupa del caso base, Per il passo d'induzione, le cose sembrano un pò più complicate che nella dimostrazione precedente. Comunque, possiamo fare progressi realizzando che l'ipotesi implica che $0 < n$ e così, di nuovo per mezzo di \ml{lem}, possiamo trasformare $n$ in un successore, abilitando di conseguenza l'espansione di \ml{FACT}, come nella dimostrazione precedente: \begin{session} \begin{verbatim} - e (`0 < n` by DECIDE_TAC THEN `?k. n = SUC k` by METIS_TAC [lem]); OK.. metis: r[+0+8]+0+0+0+0+0+0+2# 1 subgoal: > val it = m divides FACT n ------------------------------------ 0. !n m. (v = n - m) ==> 0 < m /\ m <= n ==> m divides FACT n 1. SUC v = n - m 2. 0 < m 3. m <= n 4. 0 < n 5. n = SUC k \end{verbatim} \end{session} \noindent La dimostrazione ora si conclude nello stesso modo di quella precedente: \begin{session} \begin{verbatim} - e (RW_TAC arith_ss [FACT, DIVIDES_RMUL]); OK.. Goal proved. ... > val it = Initial goal proved. |- !m n. 0 < m /\ m <= n ==> m divides FACT n \end{verbatim} \end{session} \noindent Lasciamo i dettagli del cucire insieme la dimostrazione al lettore interessato. \section{Primarietà} Ora ci spostiamo a stabilire alcuni fatti circa la primarietà dei primi numeri: $0$ e $1$ non sono primi, ma $2$ lo è. Inoltre, tutti i primi sono positivi. Queste sono piuttosto semplici da dimostrare. \begin{description} \item [\small{({\it NOT\_PRIME\_0\/})}] \begin{tabular}[t]{l} \verb+~prime 0+ \\ \hline \verb+RW_TAC arith_ss [prime_def,DIVIDES_0]+ \\ \end{tabular} \item [\small{({\it NOT\_PRIME\_1\/})}] \begin{tabular}[t]{l} \verb+~prime 1+ \\ \hline \verb+RW_TAC arith_ss [prime_def]+ \\ \end{tabular} \item [\small{({\it PRIME\_2\/})}] \begin{tabular}[t]{l} \verb+prime 2+ \\ \hline \verb+RW_TAC arith_ss [prime_def]+ \\ \verb+ THEN METIS_TAC [DIVIDES_LE, DIVIDES_ZERO,+ \\ \verb+ DECIDE ``~(2=1)``, DECIDE ``~(2=0)``,+ \\ \verb+ DECIDE ``x <= 2 = (x=0) \/ (x=1) \/ (x=2)``]+ \\ \end{tabular} \item [\small{({\it PRIME\_POS\/})}] \begin{tabular}[t]{l} \verb+!p. prime p ==> 0 ?p. prime p /\ p divides n+ \\ \hline \verb+completeInduct_on `n`+ \\ \verb+ THEN RW_TAC arith_ss []+ \\ \verb+ THEN Cases `prime n` THENL+ \\ \verb+ [METIS_TAC [DIVIDES_REFL], + \\ \verb+ `?x. x divides n /\ ~(x=1) /\ ~(x=n)` + \\ \verb+ by METIS_TAC[prime_def]+ \\ \verb+ THEN METIS_TAC [LESS_OR_EQ, PRIME_2, +\\ \verb+ DIVIDES_LE,DIVIDES_TRANS,DIVIDES_0]]+ \\ \end{tabular} \end{description} Iniziamo invocando l'induzione completa. Questo ci dà un'ipotesi d'induzione che vale per ogni numero $m$ strettamente minore di $n$: \begin{session} \begin{verbatim} - g `!n. ~(n = 1) ==> ?p. prime p /\ p divides n`; - e (completeInduct_on `n`); OK.. 1 subgoal: > val it = ~(n = 1) ==> ?p. prime p /\ p divides n ------------------------------------ !m. m < n ==> ~(m = 1) ==> ?p. prime p /\ p divides m \end{verbatim} \end{session} Possiamo spostare l'antecedente nelle ipotesi e fare il nostro case split. Si noti che il termine dato a \ml{Cases\_on} non deve per forza essere nel goal: \begin{session} \begin{verbatim} - e (RW_TAC arith_ss [] THEN Cases_on `prime n`); OK.. 2 subgoals: > val it = ?p. prime p /\ p divides n ------------------------------------ 0. !m. m < n ==> ~(m = 1) ==> ?p. prime p /\ p divides m 1. ~(n = 1) 2. ~prime n ?p. prime p /\ p divides n ------------------------------------ 0. !m. m < n ==> ~(m = 1) ==> ?p. prime p /\ p divides m 1. ~(n = 1) 2. prime n \end{verbatim} \end{session} \noindent Come menzionato, il primo caso è dimostrato con la riflessività della divisibilità: \begin{session} \begin{verbatim} - e (METIS_TAC [DIVIDES_REFL]); OK.. metis: r[+0+7]+0+0+0+0+1# Goal proved. ... \end{verbatim} \end{session} \noindent Nel secondo caso, otteniamo un divisore di $n$ che non è $1$ né $n$ (dal momento che $n$ non è primo): \begin{session} \begin{verbatim} - e (`?x. x divides n /\ ~(x=1) /\ ~(x=n)` by METIS_TAC [prime_def]); OK.. metis: r[+0+11]+0+0+0+0+0+0+1+1+1+1+0+1+1# 1 subgoal: > val it = ?p. prime p /\ p divides n ------------------------------------ 0. !m. m < n ==> ~(m = 1) ==> ?p. prime p /\ p divides m 1. ~(n = 1) 2. ~prime n 3. x divides n 4. ~(x = 1) 5. ~(x = n) \end{verbatim} \end{session} A questo punto, la tattica ripulita invoca semplicemente \ml{METIS\_TAC} con una collezione di teoremi. Tenteremo un'esposizione più dettagliata. Data l'ipotesi, e {\small\it DIVIDES\_LE}, possiamo affermare $x < n \lor n = 0$ e così dividere la dimostrazione in due casi: \begin{session} \begin{verbatim} - e (`x < n \/ (n=0)` by METIS_TAC [DIVIDES_LE,LESS_OR_EQ]); OK.. metis: r[+0+14]+0+0+0+0+0+0+0+0+0+0+1+0+1# 2 subgoals: > val it = ?p. prime p /\ p divides n ------------------------------------ 0. !m. m < n ==> ~(m = 1) ==> ?p. prime p /\ p divides m 1. ~(n = 1) 2. ~prime n 3. x divides n 4. ~(x = 1) 5. ~(x = n) 6. n = 0 ?p. prime p /\ p divides n ------------------------------------ 0. !m. m < n ==> ~(m = 1) ==> ?p. prime p /\ p divides m 1. ~(n = 1) 2. ~prime n 3. x divides n 4. ~(x = 1) 5. ~(x = n) 6. x < n \end{verbatim} \end{session} Nel primo subgoal, possiamo vedere che gli antecedenti dell'ipotesi d'induzione sono soddisfatti e così $x$ ha un divisore primo. Possiamo quindi usare la transitività della divisibilità per ottenere il fatto che questo divisore di $x$ è anche un divisore di $n$, concludendo quindi questo ramo della dimostrazione: \begin{session} \begin{verbatim} - e (METIS_TAC [DIVIDES_TRANS]); OK.. metis: r[+0+11]+0+0+0+0+0+0+0+1+0+4+1+0+3+0+2+2+1# Goal proved. \end{verbatim} \end{session} \noindent I goal rimanenti possono essere chiariti per semplificazione: \begin{session} \begin{verbatim} - e (RW_TAC arith_ss []); OK.. 1 subgoal: > val it = ?p. prime p /\ p divides 0 ------------------------------------ 0. !m. m < 0 ==> ~(m = 1) ==> ?p. prime p /\ p divides m 1. ~(0 = 1) 2. ~prime 0 3. x divides 0 4. ~(x = 1) 5. ~(x = 0) - DIVIDES_0; > val it = |- !x. x divides 0 : thm - e (RW_TAC arith_ss [it]); OK.. 1 subgoal: > val it = ?p. prime p ------------------------------------ 0. !m. m < 0 ==> ~(m = 1) ==> ?p. prime p /\ p divides m 1. ~(0 = 1) 2. ~prime 0 3. x divides 0 4. ~(x = 1) 5. ~(x = 0) \end{verbatim} \end{session} I due passi della semplificazione esplorativa ci hanno portato a uno stato dove tutto ciò che dobbiamo fare è esibire un primo. E ne abbiamo già uno a portata di mano: \begin{session} \begin{verbatim} - e (METIS_TAC [PRIME_2]); OK.. metis: r[+0+6]# Goal proved. ... > val it = Initial goal proved. |- !n. ~(n = 1) ==> ?p. prime p /\ p divides n \end{verbatim} \end{session} Di nuovo, è necessario fare del lavoro per comporre e magari ripulire una singola tattica, dai passi individuali di dimostrazione, ma non lo descriveremo\footnote{ Di fatto, la tattica può essere semplificata in un'induzione completa seguita da un'invocazione di \ml{METIS\_TAC} con lemmi adeguati.}. Piuttosto andiamo avanti, perché il nostro obiettivo finale è a portata di mano. \section{Il teorema di Euclide} \noindent{\bf Teorema.} Ogni numero ha un primo più grande di lui.\\ \noindent {\it Dimostrazione Informale.} \\ \noindent Assumiamo l'opposto; allora c'è un $n$ tale che tutti i $p$ più grandi di $n$ non sono primi. Si consideri $\mbox{\tt FACT}(n) + 1$: esso non è uguale a 1 così, per {\small{\it PRIME\_FACTOR}}, c'è un primo $p$ che lo divide. Si noti che $p$ divide anche $\mbox{\tt FACT}(n)$ perché $p \leq n$\footnote{Per {\small{\it DIVIDES\_FACT}} [ndt].}. Per {\small{\it DIVIDES\_ADDL}}, abbiamo $p=1$. Ma allora $p$ non è primo, il che è una contraddizione. \\ \noindent {\it Fine della dimostrazione}. Una traduzione in HOL della dimostrazione può essere data nel modo seguente: \begin{description} \item [\small{({\it EUCLID\/})}] \begin{tabular}[t]{l} \verb+!n. ?p. n < p /\ prime p+ \\ \hline \verb+SPOSE_NOT_THEN STRIP_ASSUME_TAC+ \\ \verb! THEN MP_TAC (SPEC ``FACT n + 1`` PRIME_FACTOR)! \\ \verb+ THEN RW_TAC arith_ss [FACT_LESS, DECIDE ``~(x=0) = 0 val it = F ------------------------------------ !p. n < p ==> ~prime p \end{verbatim} \end{session} Così abbiamo l'ipotesi che tutti i $p$ al di sopra di un certo $n$ non specificato non sono primi, e il nostro compito è di mostrare che ciò non può essere. A questo punto approfittiamo della grande ispirazione di Euclide e costruiamo un termine esplicito da $n$. Nella dimostrazione informale ci viene chiesto di `considerare' il termine $\mbox{\tt FACT}\ n + 1$\footnote{Il parser di HOL pensa che $\mbox{\tt FACT}\ n + 1$ sia equivalente a $(\mbox{\tt FACT}\ n) + 1$.} Questo termine avrà certe proprietà (cioè, ha un fattore primo) che porta a una contraddizione. Domanda: come `consideriamo' questo termine nella dimostrazione formale HOL? Risposta: istanziando un lemma con esso e portando il lemma nella dimostrazione. Il lemma e la sua istanziazione sono \footnote{La funzione {\tt SPEC} implementa la regola della specializzazione universale.}: \begin{session} \begin{verbatim} - PRIME_FACTOR; > val it = |- !n. ~(n = 1) ==> (?p. prime p /\ p divides n) : thm - val th = SPEC ``FACT n + 1`` PRIME_FACTOR; > val th = |- ~(FACT n + 1 = 1) ==> (?p. prime p /\ p divides FACT n + 1) \end{verbatim} \end{session} E' evidente che l'antecedente di \ml{th} può essere eliminato. In \holn{}, si potrebbe fare questo in un cosiddetto stile di dimostrazione {\it in avanti\/} (dimostrando $\vdash \neg(\mbox{\tt FACT}\ n + 1 = 1)$ e poi applicando {\it modus ponens}, il cui risultato può poi essere usato nella dimostrazione), oppure si potrebbe portare \ml{th} nella dimostrazione e semplificarlo {\it in situ}. Scegliamo il secondo approccio. \begin{session} \begin{verbatim} - e (MP_TAC (SPEC ``FACT n + 1`` PRIME_FACTOR)); OK.. 1 subgoal: > val it = (~(FACT n + 1 = 1) ==> ?p. prime p /\ p divides FACT n + 1) ==> F ------------------------------------ !p. n < p ==> ~prime p \end{verbatim} \end{session} L'invocazione di \ml{MP\_TAC} ($\vdash M$) applicata a un goal $(\Delta, g)$ restituisce il goal $(\Delta, M \supset g)$. Ora semplifichiamo: \begin{session} \begin{verbatim} - e (RW_TAC arith_ss []); OK.. 2 subgoals: > val it = ~prime p \/ ~(p divides FACT n + 1) ------------------------------------ 0. !p. n < p ==> ~prime p 1. prime p ~(FACT n = 0) ------------------------------------ !p. n < p ==> ~prime p \end{verbatim} \end{session} Si ricordi che zero è minore di qualsiasi fattoriale, un fatto che si trova in \ml{arithmeticTheory} sotto il nome \ml{FACT\_LESS}. Così possiamo risolvere il goal principale per semplificazione: \begin{session} \begin{verbatim} - e (RW_TAC arith_ss [FACT_LESS, DECIDE ``!x. ~(x=0) = 0 < x``]); OK.. Goal proved. ... \end{verbatim} \end{session} Si noti l'uso `on-the-fly' di \verb+DECIDE+ per fornire una riscrittura \textit{ad hoc}. Osservando il goal rimanente, si potrebbe pensare che il nostro obiettivo, di dimostrare la falsità, è andato perso. Ma questo non è vero: un goal $\neg P \lor \neg Q$ è logicamente equivalente a $P \imp Q \imp \mathtt{F}$. Nell'invocazione seguente, usiamo l'uguaglianza $\vdash A \imp B = \neg A \lor B$ come una regola di riscrittura orientata da destra a sinistra per mezzo dell'uso di \ml{GSYM}\footnote{In parole povere, \ml{GSYM} scambia i lati sinistro e destro di qualsiasi equazione che trova.}. \begin{session} \begin{verbatim} - IMP_DISJ_THM; > val it = |- !A B. A ==> B = ~A \/ B : thm - e (RW_TAC arith_ss [GSYM IMP_DISJ_THM]); OK.. 1 subgoal: > val it = ~(p divides FACT n + 1) ------------------------------------ 0. !p. n < p ==> ~prime p 1. prime p : goalstack \end{verbatim} \end{session} Possiamo procedere velocemente per mostrare che $p \ \mathtt{divides}\ (\mathtt{FACT}\; n)$, e di conseguenza che $p = 1$, quindi che $p$ non è primo, a quel punto abbiamo finito. Questo può essere impacchettato tutto in una singola invocazione di \ml{METIS\_TAC}: \begin{session} \begin{verbatim} - e (METIS_TAC [DIVIDES_FACT, DIVIDES_ADDL, DIVIDES_ONE, NOT_PRIME_1, NOT_LESS, PRIME_POS]); metis: r[+0+12]+0+0+0+0+0+0+0+1+1+0+0+0+0+1+1+1+1+4# Goal proved. [..] |- ~(p divides FACT n + 1) Goal proved. [.] |- ~prime p \/ ~(p divides FACT n + 1) Goal proved. [.] |- (~(FACT n + 1 = 1) ==> ?p. prime p /\ p divides FACT n + 1) ==> F Goal proved. [.] |- F > val it = Initial goal proved. |- !n. ?p. n < p /\ prime p : goalstack \end{verbatim} \end{session} Il teorema di Euclide ora è dimostrato, e possiamo riposare. Comunque, questa presentazione della dimostrazione finale sarà insoddisfacente per alcuni, dal momento che la dimostrazione è completamente nascosta nell'invocazione del ragionatore automatico. Bene allora, proviamo un'altra dimostrazione, questa volta impiegando il cosiddetto stile `asserzionale'. Quando usato in modo uniforme, questo permette una presentazione leggibile e lineare che rispecchia la dimostrazione informale. Il seguente dimostra il teorema di Euclide nello stile asserzionale. Pensiamo che sia abbastanza leggibile, certamente molto di più della dimostrazione standard basata su tattiche appena data\footnote{Si noti che {\tt CCONTR\_TAC}, che è utilizzata per avviare la dimostrazione, istanzia una dimostrazione per contraddizione negando il goal e spostandolo nelle ipotesi, lasciando {\tt F} come il nuovo goal.}. \begin{description} \item [\small{({\it AGAIN\/})}] \begin{tabular}[t]{l} \verb+!n. ?p. n < p /\ prime p+ \\ \hline \verb|CCONTR_TAC THEN | \\ \verb|`?n. !p. n < p ==> ~prime p` by METIS_TAC [] THEN| \\ \verb|`~(FACT n + 1 = 1)` by RW_TAC arith_ss [FACT_LESS,| \\ \verb| DECIDE``~(x=0)=0/examples/euclid.sml}} \] come nostra linea di base. Questo file è già vicino ad essere nella forma corretta. Ha tutte le dimostrazioni dei teoremi in una forma ``cucita'' così che quando eseguito, non coinvolge per nulla il goal-stack. Nella sua forma data, può essere eseguito come input per \textsf{hol} così: \setcounter{sessioncount}{0} \begin{session} \begin{verbatim} $ cd examples/ $ ../bin/hol < euclid.sml ... > val EUCLID = |- !n. ?p. n < p /\ prime p : thm ... > val EUCLID_AGAIN = |- !n. ?p. n < p /\ prime p : thm - \end{verbatim} \end{session} Comunque, vogliamo creare una \ml{euclidTheory} che possiamo caricare in altre sessioni interattive. Così, il nostro primo passo è creare un file \ml{euclidScript.sml}, e copiare in esso il corpo di \ml{euclid.sml} La prima linea non-commentata apre \ml{arithmeticTheory}. Comunque, quando si scrive per il compilatore, è necessario menzionare esplicitamente gli altri moduli \HOL{} da cui dipende. Dobbiamo aggiungere \[ \mbox{\ml{open HolKernel boolLib Parse bossLib}} \] La linea successiva che presenta una difficoltà è \[ \mbox{\ml{set\_fixity "divides" (Infixr 450);}} \] Mentre è legittimo digitare espressioni direttamente nel sistema interattivo, il compilatore richiede che ogni espressione di primo livello sia una dichiarazione. Soddisfiamo questo requisito alterando questa linea in una dichiarazione ``non fare nulla'' che non registra il risultato dell'espressione: \[ \mbox{\ml{val \_{} = set\_fixity "divides" (Infixr 450)}} \] Gli unici altri cambiamenti extra sono inserire tra parentesi il resto del testo dello script con le chiamate a \ml{new\_theory} e \ml{export\_theory}. Così, prima della definizione di \ml{divides}, aggiungiamo: \[ \mbox{\ml{val \_{} = new\_theory "euclid";}} \] and at the end of the file: \[ \mbox{\ml{val \_{} = export\_theory();}} \] Ora, possiamo compilare lo script che abbiamo creato usando lo strumento \textsf{Holmake}. Per mantenere le cose un pò più ordinate, per prima cosa spostiamo il nostro script in una nuova directory. \begin{session} \begin{verbatim} $ mkdir euclid $ mv euclidScript.sml euclid $ cd euclid $ ../../bin/Holmake Analysing euclidScript.sml Trying to create directory .HOLMK for dependency files Compiling euclidScript.sml Linking euclidScript.uo to produce theory-builder executable <> Definition has been stored under "divides_def". Definition has been stored under "prime_def". Meson search level: ..... Meson search level: ................. ... Exporting theory "euclid" ... done. Analysing euclidTheory.sml Analysing euclidTheory.sig Compiling euclidTheory.sig Compiling euclidTheory.sml \end{verbatim} \end{session} Ora abbiamo creato quattro nuovi file, varie forme di \ml{euclidTheory} con quattro differenti suffissi. Solo \ml{euclidTheory.sig} è realmente adatta per un consumo da parte dell'uomo. Mentre siamo ancora nella directory \ml{euclid} che abbiamo creato, possiamo provare: \begin{session} \begin{alltt} \$ ../../bin/hol [...] [closing file "/local/scratch/mn200/Work/hol98/tools/end-init-boss.sml"] - load "euclidTheory"; > val it = () : unit - open euclidTheory; > type thm = thm val DIVIDES_TRANS = |- !a b c. a divides b /\ b divides c ==> a divides c : thm ... val DIVIDES_REFL = |- !x. x divides x : thm val DIVIDES_0 = |- !x. x divides 0 : thm \end{alltt} \end{session} \section{Sommario} Il lettore ora ha visto un teorema interessante dimostrato, in grande dettaglio, in \holn{}. La discussione ha illustrato gli strumenti di alto livello forniti in \ml{bossLib} e ha toccato questioni che includono la selezione degli strumenti, l'undo, la `levigatura delle tattiche', la semplificazione esplorativa, e la `biforcazione' di nuovi tentativi di dimostrazione. Abbiamo anche tentato di dare un'idea dei processi di pensiero che un utente potrebbe impiegare. Ciò che segue è una raccolta più o meno casuale di altre osservazioni. \begin{itemize} \item Nonostante la dimostrazione del teorema di Euclide sia breve e semplice da comprendere quando presentata in modo informale, è stata richiesta una quantità forse sorprendente di sviluppo a supporto dell'impostazione dell'argomento classico di Euclide. \item Il supporto alla dimostrazione fornito da \ml{bossLib} (\verb+RW_TAC+, \ml{METIS\_TAC}, \ml{DECIDE\_TAC}, \ml{DECIDE}, \ml{Cases\_on}, \ml{Induct\_on}, e dal costrutto ``\ml{by}'') è stato quasi completo per questo esempio: raramente è stato necessario ricorrere a tattiche di basso livello. \item La semplificazione è un cavallo di battaglia delle tattiche; persino quando è stato usato un ragionatore automatico come \ml{METIS\_TAC}, la sua applicazione è stata spesso preparata da qualche semplificazione esplorativa. Vale la pena quindi prendere confidenza con i punti di forza e di debolezza del semplificatore. \item Un problema comune con i sistemi interattivi di dimostrazione è trattare con le ipotesi. Spesso \ml{METIS\_TAC} e il costrutto ``\ml{by}'' permettono di usare le ipotesi senza ricorrere direttamente alla loro indicizzazione (o al dar loro un nome, che equivale alla stessa cosa). Questo è desiderabile, dal momento che le ipotesi sono concettualmente un {\it insieme}, e inoltre, l'esperienza ha mostrato che il proliferare dell'indicizzazione nelle ipotesi risulta in script di dimostrazione difficili da manutenere. Comunque, può confondere lavorare con un grande insieme di ipotesi, nel qual caso i seguenti approcci possono essere utili. Ci si può riferire direttamente alle ipotesi usando \ml{UNDISCH\_TAC} (rende l'ipotesi designata l'antecedente del goal), \ml{ASSUM\_LIST} (fornisce l'intera lista delle ipotesi a una tattica), \ml{POP\_ASSUM} (da la prima ipotesi a una tattica), e \ml{PAT\_ASSUM} (da a una tattica la prima ipotesi {\it che soddisfa il matching\/}). (Si veda \REFERENCE{} per maggiori dettagli su tutte queste cose.) I numeri associati alle ipotesi dal gestore di dimostrazione verosimilmente potrebbero essere usati per accedere alle ipotesi (sarebbe piuttosto semplice scrivere una tattica simile). Comunque, iniziare una nuova dimostrazione è a volte la cosa più illuminante da fare. In alcuni casi, è utile essere in grado di cancellare un'ipotesi. Questo si può ottenere passando l'ipotesi a una tattica che la ignora. Per esempio, per scartare la prima ipotesi, si può invocare \ml{POP\_ASSUM (K ALL\_TAC)}. \item Nell'esempio, non abbiamo usato le caratteristiche più avanzate di \ml{bossLib}, in gran parte perché non forniscono, finora, molte più funzionalità rispetto alla semplice sequenza di semplificazione, procedure di decisione, e ragionamento automatico al primo ordine. Il tatticale \ml{THEN} è dunque servito come una sostituzione adeguata. Nel futuro, questi punti d'ingresso dovrebbero diventare più potenti. \item E' quasi sempre necessario avere un'idea della dimostrazione {\it informale\/} al fine di avere successo quando di fa una dimostrazione formale. Tuttavia, troppo spesso i novizi adottano la seguente strategia: (1) riscrivere il goal con alcune definizioni rilevanti, e quindi (2) fare affidamento sulla sintassi del goal risultante per guidare la selezione delle tattiche successive. Un tale approccio costituisce un chiaro caso di cane che si morde la coda, ed è una strategia povera da adottare. La comprensione della struttura di alto livello della dimostrazione è uno dei fattori più importanti negli esercizi di verifica di successo. L'autore ha notato che molti degli esperti di verifica più di successo lavorano usando un foglio di carta per tenere traccia dei passi principali che devono essere fatti. Forse distogliere lo sguardo sulla carte aiuta a rompere l'effetto ipnotizzante dello schermo del computer. Dall'altra parte, uno dei vantaggi di avere una logica meccanizzata è che la macchina può essere usata come un calcolatore di espressioni formali, e così l'utente lo può usare per esplorare velocemente e accuratamente varie possibilità di dimostrazione. \item Gli strumenti molto potenti come \ml{METIS\_TAC}, \ml{DECIDE\_TAC}, e \ml{RW\_TAC} sono il modo principale per fare progressi in una dimostrazione in \ml{bossLib}. In molti casi, essi fanno ciò che si desidera, o addirittura riescono a stupire l'utente con la loro potenza. Nella formalizzazione del teorema di Euclide, gli strumenti hanno funzionato piuttosto bene. Tuttavia, a volte sono eccessivamente aggressivi, o semplicemente fanno errori. In questi casi, si devono usare, o persino scrivere, degli strumenti di dimostrazione più specializzati, e di conseguenza alla fine bisogna imparare il supporto sottostante a \ml{bossLib}. \item Avere una buona conoscenza dei lemmi disponibile, e di dove essi si trovano, è una parte essenziale per avere successo. Spesso gli strumenti potenti possono sostituire i lemmi in un dominio ristretto, ma in generale, si deve sapere cosa è stato già dimostrato. Abbiamo visto che i punti di ingresso in \verb+DB+ aiutano a trovare velocemente i lemmi. \end{itemize} %%% Local Variables: %%% mode: latex %%% TeX-master: "tutorial" %%% End: