\newcommand{\eos}{\hfill{}$\cdots\diamond\cdots$\hfill{}\vspace{5mm}} \newcommand{\mathpredn}{\mathbin{-\!\!\!\mid\mid\!\rightarrow}} \newcommand{\KC}{\con{K}} \newcommand{\SC}{\con{S}} \newcommand{\bk}{\char'134} \chapter{Esempio: Logica Combinatoria} \label{chap:combin} \section{Introduzione} \label{sec:Introduction} Questo piccolo caso di studio è una formalizzazione di una logica combinatoria (priva di variabili). Questa logica è d'importanza fondamentale nell'informatica teorica, ed ha una ricca teoria. L'esempio si basa principalmente su uno sviluppo fatto da Tom Melham. Lo script completo per lo sviluppo è disponibile come \texttt{clScript.sml} nella directory \texttt{examples/ind\_def} della distribuzione. E' autosufficiente e così include le risposte agli esercizi fissati al termine di questo documento. Le sessioni HOL assumono che la traccia Unicode sia \emph{accesa} (così come è di default), il che significa che benché gli input possono essere scritti in puro ASCII, l'output usa tuttavia degli eleganti caratteri Unicode (simboli come $\forall$ e $\Rightarrow$). I simboli Unicode potrebbero essere usati anche nell'input. \section{Il tipo dei combinatori} \label{sec:Type-Combinators} La prima cosa che dobbiamo fare è definire il tipo dei \emph{combinatori}. Di questi ce ne sono soltanto due, \KC{} e \SC, ma dobbiamo anche essere in grado di \emph{combinarli}, e per questo abbiamo bisogno di introdurre la nozione di applicazione. In mancanza di un simbolo ASCII migliore, useremo il cancelletto (\#) per rappresentare questo nella logica: \setcounter{sessioncount}{0} \begin{session} \begin{verbatim} - Hol_datatype `cl = K | S | # of cl => cl`; > val it = () : unit \end{verbatim} \end{session} Vogliamo anche che \# sia un infisso, così lo impostiamo come uno stretto infisso associativo a sinistra: \begin{session} \begin{verbatim} - set_fixity "#" (Infixl 1100); > val it = () : unit \end{verbatim} \end{session} % C = S (S (K S) (S (K K) S)) (K K) \section{Regole di riduzione dei combinatori} \label{sec:Comb-Reduct} La logica combinatoria è lo studio di come valori di questo tipo possono evolversi date varie regole che descrivono come cambiano. Di conseguenza, il nostro prossimo passo è di definire le riduzioni che i combinatori possono subire. Ci sono due regole base: \[\begin{array}{l@{\;\;\rightarrow\;\;}l} \KC\;x\;y & x\\ \SC\;f\;g\;x & (f x)(g x) \end{array}\] Qui, nella nostra descrizione al di fuori di HOL, usiamo la giustapposizione al posto del \#. Inoltre, la giustapposizione è anche associativa a sinistra, così che $\con{K}\;x\;y$ dovrebbe essere letto come $\con{K}\;\#\;x\;\#\;y$ che è a sua volta $(\con{K}\;\#\;x)\;\#\;y$. Dato un termine nella logica, vogliamo che queste riduzioni siano in grado di essere eseguite in qualsiasi punto, non solo al top level, così abbiamo bisogno di due ulteriori regole di congruenza:\[ \begin{array}{l} \infer{x\;y\;\;\rightarrow\;\;x'\;y}{x\;\;\rightarrow\;\;x'}\\[5mm] \infer{x\;y\;\;\rightarrow\;\;x\;y'}{y\;\;\rightarrow\;\;y'} \end{array}\] In HOL, possiamo catturare questa relazione con una definizione induttiva. Per prima cosa abbiamo bisogno di impostare il nostro simbolo freccia come un infisso per rendere ogni cosa un pò più elegante \begin{session} \begin{verbatim} - set_fixity "-->" (Infix(NONASSOC, 450)); > val it = () : unit \end{verbatim} \end{session} Rendiamo il nostro simbolo freccia non-associativo, rendendo di conseguenza un errore di parsing scrivere \verb!x --> y --> z!. Sarebbe carino essere in grado di scrivere questo e averlo con il significato di \verb!x --> y /\ y --> z!, ma questo al momento non è possibile con il parser di HOL. Il nostro prossimo passo è di definire di fatto la relazione con la funzione \ml{xHol\_reln}. In aggiunta a una quotation che specifica le regole per la nuova relazione, essa richiede un nome da usare come la radice per i teoremi che essa dimostra. Utilizziamo la stringa \ml{"redn"}\footnote{La funzione correlata \ml{Hol\_reln} può essere usata se la scelta della radice da parte del sistema è accettabile. In questo caso, \ml{Hol\_reln} non può gestire i caratteri non-alfanumerici in \holtxt{-->} e solleverà un errore.}. La funzione \ml{xHol\_reln} per fare questo restituisce tre teoremi separati, e leghiamo il primo (il teorema ``rules'') e il terzo (il teorema ``cases'') \begin{session} \begin{alltt} val (redn_rules, _, redn_cases) = xHol_reln "redn" `(!x y f. x --> y ==> f # x --> f # y) /\bs (!f g x. f --> g ==> f # x --> g # x) /\bs (!x y. K # x # y --> x) /\bs (!f g x. S # f # g # x --> (f # x) # (g # x))`; > val redn_rules = |- (\(\forall\)x y f. x --> y \(\Rightarrow\) f # x --> f # y) \(\land\) (\(\forall\)f g x. f --> g \(\Rightarrow\) f # x --> g # x) \(\land\) (\(\forall\)x y. K # x # y --> x) \(\land\) \(\forall\)f g x. S # f # g # x --> f # x # (g # x) : thm val redn_cases = |- \(\forall\)a0 a1. a0 --> a1 \(\Leftrightarrow\) (\(\exists\)x y f. (a0 = f # x) \(\land\) (a1 = f # y) \(\land\) x --> y) \(\lor\) (\(\exists\)f g x. (a0 = f # x) \(\land\) (a1 = g # x) \(\land\) f --> g) \(\lor\) (\(\exists\)y. a0 = K # a1 # y) \(\lor\) \(\exists\)f g x. (a0 = S # f # g # x) \(\land\) (a1 = f # x # (g # x)) : thm \end{alltt} \end{session} Oltre a dimostrare questi tre teoremi per noi, il pacchetto di definizioni induttiva li ha anche salvati sul disco. Ora, usando il nostro teorema \texttt{redn\_rules} possiamo dimostrare singoli passi della nostra relazione di riduzione: \begin{session} \begin{verbatim} - PROVE [redn_rules] ``S # (K # x # x) --> S # x``; Meson search level: ... > val it = |- S # (K # x # x) --> S # x : thm \end{verbatim} \end{session} Il sistema che abbiamo appena definito è potente quanto il $\lambda$-calcolo, le macchine di Turing, e tutti gli altri modelli standard di computazione. Un utile risultato circa la logica combinatoria è che è \emph{confluente}. Si consideri il termine $\SC\;z\;(\KC\;\KC)\;(\KC\; y\; x)$. Esso può subire due riduzioni, a $\SC\;z\;(\KC\;\KC)\;y$ e anche a $(z\;(\KC\;y\;x))\,(\KC\;\KC\;(\KC\;y\;x))$. Fare queste due scelte di riduzione significa che da questo punto in poi i termini hanno due storie completamente separate? In parole povere, essere confluente significa che la risposta a questa domanda è \emph{no}. \section{Chiusura transitiva e confluenza} \label{sec:Transitive-Clos-Conf} Una nozione cruciale a quella di confluenza è quella di \emph{chiusura transitiva}. Abbiamo definito un sistema che si evolve specificando come un valore algebrico si può evolvere in possibili valori successivi in un singolo passo. La prossima domanda naturale è di chiedere per una caratterizzazione dell'evoluzione in uno o più passi della relazione $\rightarrow$. Di fatto, definiremo una relazione che vale tra due valori se il secondo può essere raggiunto dal primo in zero o più passi. Questa è la \emph{chiusura riflessiva, transitiva} della nostra relazione originaria. Comunque, piuttosto che legare la nostra definizione alla nostra relazione originaria, svilupperemo questa nozione in modo indipendente e dimostreremo una varietà di risultati che sono veri di ogni sistema, non soltanto del nostro sistema di logica combinatoria. Così, iniziamo la nostra digressione astratta con un'altra definizione induttiva. La nostra nuova costante è \con{RTC}, tale che $\con{RTC}\;R\;x\;y$ è vera se è possibile andare da $x$ a $y$ con zero o più ``passi'' della relazione $R$. (La notazione standard per $\con{RTC}\;R$ è $R^*$.) Possiamo esprimere quest'idea con solo due regole. La prima \[ \infer{\con{RTC}\;R\;x\;x}{} \] dice che è sempre possibile andare da $x$ a $x$ in zero o più passi. La seconda \[ \infer{\con{RTC}\;R\;x\;z}{R\;x\;y\qquad\con{RTC}\;R\;y\;z} \] dice che se si può fare un singolo passo da $x$ a $y$, e poi zero o più passi per andare da $y$ a $z$, allora è possibile fare zero o più passi per andare da $x$ a $z$. La realizzazione di queste regole in HOL è di nuovo diretta. (Come accade, \con{RTC} è una costante già definita nel conteso in cui stiamo lavorando (si trova in \texttt{relationTheory}), così la nasconderemo dalla vista prima di iniziare. Evitiamo così messaggi che ci dicono che stiamo immettendo termini ambigui. Le ambiguità verrebbero sempre risolte in favore della definizione più recente, ma i warning sono fastidiosi.) \begin{session} \begin{alltt} val _ = hide "RTC"; val (RTC_rules, _, RTC_cases) = Hol_reln ` (!x. RTC R x x) /\bs (!x y z. R x y /\bs RTC R y z ==> RTC R x z)`; <> > val RTC_rules = |- \(\forall\)R. (\(\forall\)x. RTC R x x) \(\land\) \(\forall\)x y z. R x y \(\land\) RTC R y z \(\Rightarrow\) RTC R x z : thm val RTC_cases = |- \(\forall\)R a0 a1. RTC R a0 a1 \(\Leftrightarrow\) (a1 = a0) \(\lor\) \(\exists\)y. R a0 y \(\land\) RTC R y a1 : thm \end{alltt} \end{session} Ora torniamo indietro alla nozione di confluenza. Vogliamo che questo significhi qualcosa del tipo: ``benché un sistema possa prendere differenti percorsi a breve scadenza, questi due percorsi possono sempre finire nello stesso posto''. Questo suggerisce che definiamo confluente nel modo seguente: \begin{session} \begin{verbatim} - val confluent_def = Define `confluent R = !x y z. RTC R x y /\ RTC R x z ==> ?u. RTC R y u /\ RTC R z u`; \end{verbatim} \end{session} Questa proprietà afferma di $R$ che possiamo ``completare il diamante''; se abbiamo \begin{center} \begin{tikzpicture}[scale=.8,transform shape] \node (middle) {}; \node (x) [above=of middle] {$x$}; \node (y) [left=of middle] {$y$}; \node (z) [right=of middle] {$z$}; \path (x) edge [->,thick] node[auto,swap] {$*$} (y); \path (x) edge [->,thick] node[auto] {$*$} (z); \end{tikzpicture} \end{center} allora possiamo completare con un nuovo valore $u$: \begin{center} \begin{tikzpicture}[scale=.8,transform shape] \node (middle) {}; \node (x) [above=of middle] {$x$}; \node (y) [left=of middle] {$y$}; \node (z) [right=of middle] {$z$}; \node (u) [below=of middle] {$u$}; \path (x) edge [->,thick] node[auto,swap] {$*$} (y); \path (x) edge [->,thick] node[auto] {$*$} (z); \path (y) edge [->,thick,densely dotted] node[auto,swap] {$*$} (u); \path (z) edge [->,thick,densely dotted] node[auto] {$*$} (u); \end{tikzpicture} \end{center} Un'interessante proprietà delle relazioni confluenti è che da qualsiasi punto iniziale esse producono non più di una \emph{forma normale}, dove una forma normale è un valore da cui non si possono più fare ulteriori passi. \begin{session} \begin{alltt} - val normform_def = Define`normform R x = !y. ~(R x y)`; <> Definition has been stored under "normform_def". > val normform_def = |- \(\forall\)R x. normform R x \(\Leftrightarrow\) \(\forall\)y. \(\neg\)R x y : thm \end{alltt} \end{session} In altre parole, un sistema ha un $R$-forma normale in $x$ se non ci sono connessioni attraverso $R$ ad altri valori. (Avremmo potuto scrivere \verb!~?y. R x y! come nostro lato destro per la definizione di sopra.) Possiamo ora dimostrare il seguente: \begin{session} \begin{alltt} - g `!R. confluent R ==> !x y z. RTC R x y /\bs normform R y /\bs RTC R x z /\bs normform R z ==> (y = z)`; <> > val it = Proof manager status: 1 proof. 1. Incomplete: Initial goal: \(\forall\)R. confluent R \(\Rightarrow\) \(\forall\)x y z. RTC R x y \(\land\) normform R y \(\land\) RTC R x z \(\land\) normform R z \(\Rightarrow\) (y = z) \end{alltt} \end{session} Riscriviamo la definizione di confluenza: \begin{session} \begin{alltt} - e (RW_TAC std_ss [confluent_def]); OK.. 1 subgoal: > val it = y = z ------------------------------------ 0. \(\forall\)x y z. RTC R x y \(\land\) RTC R x z \(\Rightarrow\) \(\exists\)u. RTC R y u \(\land\) RTC R z u 1. RTC R x y 2. normform R y 3. RTC R x z 4. normform R z \end{alltt} \end{session} La nostra proprietà confluenza è ora l'assunzione 0, e possiamo usarla per dedurre che c'è un $u$ alla base del diamante: \begin{session} \begin{alltt} - e (`?u. RTC R y u /\bs RTC R z u` by PROVE_TAC []); OK.. Meson search level: ......... 1 subgoal: > val it = y = z ------------------------------------ 0. \(\forall\)x y z. RTC R x y \(\land\) RTC R x z \(\Rightarrow\) \(\exists\)u. RTC R y u \(\land\) RTC R z u 1. RTC R x y 2. normform R y 3. RTC R x z 4. normform R z 5. RTC R y u 6. RTC R z u \end{alltt} \end{session} Così, da $y$ possiamo fare zero o più passi per arrivare a $u$ e analogamente da $z$. Ma, sappiamo anche che siamo in una $R$-forma normale sia in $y$ sia in $z$. Non possiamo fare alcun passo da questi valori. Possiamo concludere sia che $u = y$ sia che $u = z$, e questo a sua volta significa che $y = z$, che è il nostro goal. Così possiamo concludere con \begin{session} \begin{alltt} - e (PROVE_TAC [normform_def, RTC_cases]); OK.. Meson search level: .......... Goal proved. [...] > val it = Initial goal proved. |- \(\forall\)R. confluent R \(\Rightarrow\) \(\forall\)x y z. RTC R x y \(\land\) normform R y \(\land\) RTC R x z \(\land\) normform R z \(\Rightarrow\) (y = z) \end{alltt} \end{session} Impacchettando così da rimuovere i comandi del pacchetto sub-goal, possiamo dimostrare e salvare il teorema per l'uso futuro con: \begin{session} \begin{verbatim} val confluent_normforms_unique = store_thm( "confluent_normforms_unique", ``!R. confluent R ==> !x y z. RTC R x y /\ normform R y /\ RTC R x z /\ normform R z ==> (y = z)``, RW_TAC std_ss [confluent_def] THEN `?u. RTC R y u /\ RTC R z u` by PROVE_TAC [] THEN PROVE_TAC [normform_def, RTC_cases]); \end{verbatim} \end{session} \eos{} Chiaramente la confluenza è una proprietà interessante per un sistema. La domanda è come potremmo riuscire a dimostrarla. Iniziamo definendo la proprietà diamante che abbiamo usato nella definizione di confluenza. \begin{session} \begin{alltt} - val diamond_def = Define `diamond R = !x y z. R x y /\bs R x z ==> ?u. R y u /\bs R z u`; <> Definition has been stored under "diamond_def". > val diamond_def = |- \(\forall\)R. diamond R \(\Leftrightarrow\) \(\forall\)x y z. R x y \(\land\) R x z \(\Rightarrow\) \(\exists\)u. R y u \(\land\) R z u : thm \end{alltt} \end{session} Ora chiaramente abbiamo che la confluenza di una relazione è equivalente alla chiusura riflessiva, transitiva di quella relazione che ha la proprietà diamante. \begin{session} \begin{verbatim} val confluent_diamond_RTC = store_thm( "confluent_diamond_RTC", ``!R. confluent R = diamond (RTC R)``, RW_TAC std_ss [confluent_def, diamond_def]); \end{verbatim} \end{session} Fin qui tutto bene. Come possiamo poi mostrare la proprietà diamante per $\con{RTC}\;R$? La risposta che salta alla mente è di sperare che se la relazione originale ha la proprietà diamante, allora magari anche la chiusura riflessiva, transitiva l'avrà. Il teorema che vogliamo è \[ \con{diamond}\;R \supset \con{diamond}\,(\con{RTC}\;R)\] Graficmente, questo è sperare che da \[\xymatrix @R=5mm @C=2.5mm { & x \ar[dl] \ar[dr] & \\ y \ar@{.>}[dr] & & z \ar@{.>}[dl] \\ & u}\] saremo in grado di concludere \[\xymatrix @R=4mm @C=2mm { & & & x \ar[dl] \ar[dr] & \\ & & y \ar@{.>}[dr] \ar@{-->}[ddll] & & z \ar@{.>}[dl] \ar@{-->}[ddrr] \\ & & & u \\ p \ar@{.>}[dddrrr] & & & & & & q \ar@{.>}[dddlll] \\ \\ \\ & & & r}\] dove le linee tratteggiate indicano che questi passi (da $x$ a $p$, per esempio) stanno usando $\con{RTC}\;R$. La presenza di due istanze di $\con{RTC}\;R$ è un'indicazione che questa dimostrazione richiederà due induzioni. Con la prima dimostreremo \[\xymatrix @R=4mm @C=2mm { & & & x \ar[dl] \ar[dr] & \\ & & y \ar@{.>}[dr] \ar@{-->}[ddll] & & z \ar@{.>}[dl] \\ & & & u \ar@{.>}[ddll] \\ p \ar@{.>}[dr] \\ & r}\] In altre parole, vogliamo mostrare che se facciamo un passo in una direzione (verso $z$) e molti altri passi in un'altra (verso $p$), allora la proprietà diamante per $R$ ci garantirà l'esistenza di $r$, verso cui saremo in grado di fare molti passi sia da $p$ sia da $z$. Facciamo un pò d'attenzione ad esprimere il goal così che dopo aver eliminato l'assunzione più esterna (che $R$ ha la proprietà diamante), esso si accorderà con il principio di induzione per \con{RTC}\footnote{In questa e nelle seguenti dimostrazioni usando il pacchetto sub-goal, presenteremo il gestore di dimostrazione come se il goal da dimostrare fosse il primo in questo stack. In altre parole, abbiamo fatto una \texttt{dropn 1;} dopo ogni dimostrazione di successo per rimuovere l'evidenza del goal vecchio. In pratica, non c'è nulla di male nel lasciare questi goal nello stack del gestore di dimostrazione.}. \begin{session} \begin{alltt} - g `!R. diamond R ==> !x p. RTC R x p ==> !z. R x z ==> ?u. RTC R p u /\bs RTC R z u`; <> > val it = Proof manager status: 1 proof. 1. Incomplete: Initial goal: \(\forall\)R. diamond R \(\Rightarrow\) \(\forall\)x p. RTC R x p \(\Rightarrow\) \(\forall\)z. R x z \(\Rightarrow\) \(\exists\)u. RTC R p u \(\land\) RTC R z u \end{alltt} \end{session} Per prima cosa, eliminiamo l'assunzione della proprietà diamante (due cose devono essere eliminate: il quantificatore universale più esterno e l'antecedente dell'implicazione): \begin{session} \begin{alltt} - e (GEN_TAC THEN STRIP_TAC); OK.. 1 subgoal: > val it = \(\forall\)x p. RTC R x p \(\Rightarrow\) \(\forall\)z. R x z \(\Rightarrow\) \(\exists\)u. RTC R p u \(\land\) RTC R z u ------------------------------------ diamond R \end{alltt} \end{session} Ora possiamo usare il principio d'induzione per la chiusura riflessiva e transitiva (in alternativa, eseguiamo una ``regola d'induzione''). Per far questo, usiamo il comando \ml{Induct\_on} che è usato anche per fare un'induzione strutturale su tipi di dati algebrici (come numeri e liste). Forniamo il nome della costante il cui principio d'induzione vogliamo usare, e la tattica fa il resto: \begin{session} \begin{alltt} - e (Induct_on `RTC`); OK.. 1 subgoal: > val it = (\(\forall\)x z. R x z \(\Rightarrow\) \(\exists\)u. RTC R x u \(\land\) RTC R z u) \(\land\) \(\forall\)x x' p. R x x' \(\land\) RTC R x' p \(\land\) (\(\forall\)z. R x' z \(\Rightarrow\) \(\exists\)u. RTC R p u \(\land\) RTC R z u) \(\Rightarrow\) \(\forall\)z. R x z \(\Rightarrow\) \(\exists\)u. RTC R p u \(\land\) RTC R z u ------------------------------------ diamond R \end{alltt} \end{session} Eseguiamo uno strip del goal il più possibile con l'obiettivo di rendere ciò che rimane da dimostrare più facile da vedere: \begin{session} \begin{alltt} - e (REPEAT STRIP_TAC); OK.. 2 subgoals: > val it = \(\exists\)u. RTC R p u \(\land\) RTC R z u ------------------------------------ 0. diamond R 1. R x x' 2. RTC R x' p 3. \(\forall\)z. R x' z \(\Rightarrow\) \(\exists\)u. RTC R p u \(\land\) RTC R z u 4. R x z \(\exists\)u. RTC R x u \(\land\) RTC R z u ------------------------------------ 0. diamond R 1. R x z \end{alltt} \end{session} Questo primo goal è semplice. Esso corrisponde al caso dove i passi principali da $x$ a $p$ sono di fatto nessun passo del tutto, e $p$ e $x$ sono di fatto lo stesso posto. Nell'altra direzione, $x$ ha fatto un passo verso $z$, e abbiamo bisogno di trovare un qualche posto raggiungibile in zero o più passi sia da $x$ sia da $z$. Dato quello che conosciamo fino ad ora, l'unico candidato è lo stesso $z$. Di fatto, non abbiamo nemmeno bisogno di fornire questo testimone in modo esplicito. \texttt{PROVE\_TAC} lo troverà per noi, purché noi gli diciamo quali sono le regole che governano \con{RTC}: \begin{session} \begin{alltt} - e (PROVE_TAC [RTC_rules]); OK.. Meson search level: ..... Goal proved. [..] |- \(\exists\)u. RTC R p u \(\land\) RTC R z u Remaining subgoals: > val it = \(\exists\)u. RTC R p u \(\land\) RTC R z u ------------------------------------ 0. diamond R 1. R x x' 2. RTC R x' p 3. \(\forall\)z. R x' z \(\Rightarrow\) \(\exists\)u. RTC R p u \(\land\) RTC R z u 4. R x z \end{alltt} \end{session} E cosa facciamo di questo goal restante? Le assunzioni uno e quattro tra di loro sono la cima di un $R$-diamante. Usiamo il fatto che abbiamo la proprietà diamante per $R$ e deduciamo che esiste un $v$ verso cui $x'$ e $z$ possono fare entrambi singoli passi: \begin{session} \begin{alltt} - e (`?v. R x' v /\bs R z v` by PROVE_TAC [diamond_def]); OK.. Meson search level: ............ 1 subgoal: > val it = \(\exists\)u. RTC R p u \(\land\) RTC R z u ------------------------------------ 0. diamond R 1. R x x' 2. RTC R x' p 3. \(\forall\)z. R x' z \(\Rightarrow\) \(\exists\)u. RTC R p u \(\land\) RTC R z u 4. R x z 5. R x' v 6. R z v \end{alltt} \end{session} Ora possiamo applicare le nostre ipotesi d'induzione (assunzione 3) per completare la lunga, striscia asimmetrica del diamante. Concluderemo che c'è un $u$ tale che $\con{RTC}\;R\;p\;u$ e $\con{RTC}\;R\;v\;u$. Di fatto abbiamo bisogno un $u$ tale che $\con{RTC}\;R\;z\;u$, ma poiché c'è un singolo passo-$R$ tra $z$ e $v$ abbiamo anche quello allo stesso modo. Tutto ciò di cui abbiamo bisogno di fornire a \texttt{PROVE\_TAC} sono le regole per \con{RTC}: \begin{session} \begin{alltt} - e (PROVE_TAC [RTC_rules]); OK.. Meson search level: ....... Goal proved. [...] > val it = Initial goal proved. |- \(\forall\)R. diamond R \(\Rightarrow\) \(\forall\)x p. RTC R x p \(\Rightarrow\) \(\forall\)z. R x z \(\Rightarrow\) \(\exists\)u. RTC R p u \(\land\) RTC R z u \end{alltt} \end{session} Di nuovo possiamo (e dovremmo) impacchettare il lemma, evitando i comandi del pacchetto sub-goal: \begin{session} \begin{verbatim} val R_RTC_diamond = store_thm( "R_RTC_diamond", ``!R. diamond R ==> !x p. RTC R x p ==> !z. R x z ==> ?u. RTC R p u /\ RTC R z u``, GEN_TAC THEN STRIP_TAC THEN Induct_on `RTC` THEN REPEAT STRIP_TAC THENL [ PROVE_TAC [RTC_rules], `?v. R x' v /\ R z v` by PROVE_TAC [diamond_def] THEN PROVE_TAC [RTC_rules] ]); \end{verbatim} \end{session} \eos{} Ora possiamo spostarci a dimostrare che se $R$ ha la proprietà diamante, allora anche $\con{RTC}\;R$. Vogliamo dimostrare questo di nuovo per induzione. E' molto forte la tentazione di affermare il goal come l'ovvio \[ \con{diamond}\;R\supset\con{diamond}\,(\con{RTC}\;R) \] ma così facendo renderà di fatto più difficile applicare il principio d'induzione quando è il momento giusto. E' meglio partire con una dichiarazione del goal che è molto vicina nella forma al principio d'induzione. Così, espandiamo manualmente il significato di \con{diamond} e dichiariamo il nostro prossimo goal così: \begin{session} \begin{alltt} - g `!R. diamond R ==> !x y. RTC R x y ==> !z. RTC R x z ==> ?u. RTC R y u /\bs RTC R z u`; <> > val it = Proof manager status: 1 proof. 1. Incomplete: Initial goal: \(\forall\)R. diamond R \(\Rightarrow\) \(\forall\)x y. RTC R x y \(\Rightarrow\) \(\forall\)z. RTC R x z \(\Rightarrow\) \(\exists\)u. RTC R y u \(\land\) RTC R z u \end{alltt} \end{session} Di nuovo, eliminiamo l'assunzione della proprietà diamante, applichiamo il principio d'induzione, ed eseguiamo \ml{STRIP_TAC} ripetutamente: \begin{session} \begin{alltt} - e (GEN_TAC THEN STRIP_TAC THEN Induct_on `RTC` THEN REPEAT STRIP_TAC); OK.. 2 subgoals: > val it = \(\exists\)u. RTC R y u \(\land\) RTC R z u ------------------------------------ 0. diamond R 1. R x x' 2. RTC R x' y 3. \(\forall\)z. RTC R x' z \(\Rightarrow\) \(\exists\)u. RTC R y u \(\land\) RTC R z u 4. RTC R x z \(\exists\)u. RTC R x u \(\land\) RTC R z u ------------------------------------ 0. diamond R 1. RTC R x z \end{alltt} \end{session} Il primo goal è di nuovo uno facile, corrispondendo al caso in cui il percorso da $x$ a $y$ è stato uno senza alcun passo. \begin{session} \begin{alltt} - e (PROVE_TAC [RTC_rules]); OK.. Meson search level: ... Goal proved. [...] Remaining subgoals: > val it = \(\exists\)u. RTC R y u \(\land\) RTC R z u ------------------------------------ 0. diamond R 1. R x x' 2. RTC R x' y 3. \(\forall\)z. RTC R x' z \(\Rightarrow\) \(\exists\)u. RTC R y u \(\land\) RTC R z u 4. RTC R x z \end{alltt} \end{session} Il goal è molto simile a quello che abbiamo visto in precedenza. Abbiamo la punta di un diamante (``asimmetrico'') nelle assunzioni 1 e 4, così possiamo dedurre l'esistenza di una destinazione comune per $x'$ e $z$: \begin{session} \begin{alltt} - e (`?v. RTC R x' v /\bs RTC R z v` by PROVE_TAC [R_RTC_diamond]); OK.. Meson search level: ............ 1 subgoal: > val it = \(\exists\)u. RTC R y u \(\land\) RTC R z u ------------------------------------ 0. diamond R 1. R x x' 2. RTC R x' y 3. \(\forall\)z. RTC R x' z \(\Rightarrow\) \(\exists\)u. RTC R y u \(\land\) RTC R z u 4. RTC R x z 5. RTC R x' v 6. RTC R z v \end{alltt} \end{session} A questo punto nell'ultima dimostrazione eravamo stati in grado di concludere il tutto appellandoci soltanto alle regole per \con{RTC}. Questa volta non è così semplice. Quando usiamo l'ipotesi d'induzione (assunzione 3), possiamo concludere che c'è un $u$ per cui sia $y$ sia $v$ si possono connettere in zero o più passi, ma al fine di mostrare che questo $u$ è raggiungibile da $z$, abbiamo bisogno di essere in grado di concludere $\con{RTC}\;R\;z\;u$ quando conosciamo che $\con{RTC}\;R\;z\;v$ (assunzione 6 di sopra) e $\con{RTC}\;R\;v\;u$ (la nostra conseguenza dell'ipotesi d'induzione). Lasciamo la dimostrazione di questo risultato generale come un esercizio, e qui assumiamo che essa è già stata dimostrato come il teorema \texttt{RTC\_RTC}. \begin{session} \begin{alltt} - e (PROVE_TAC [RTC_rules, RTC_RTC]); Meson search level: ....... Goal proved. [...] > val it = Initial goal proved. |- \(\forall\)R. diamond R \(\Rightarrow\) \(\forall\)x y. RTC R x y \(\Rightarrow\) \(\forall\)z. RTC R x z \(\Rightarrow\) \(\exists\)u. RTC R y u \(\land\) RTC R z u \end{alltt} \end{session} Possiamo impacchettare questo risultato come un lemma e poi dimostrare la versione più elegante direttamente: \begin{session} \begin{verbatim} val diamond_RTC_lemma = prove( ``!R. diamond R ==> !x y. RTC R x y ==> !z. RTC R x z ==> ?u. RTC R y u /\ RTC R z u``, GEN_TAC THEN STRIP_TAC THEN Induct_on `RTC` THEN REPEAT STRIP_TAC THENL [ PROVE_TAC [RTC_rules], `?v. RTC R x' v /\ RTC R z v` by PROVE_TAC [R_RTC_diamond] THEN PROVE_TAC [RTC_RTC, RTC_rules] ]); val diamond_RTC = store_thm( "diamond_RTC", ``!R. diamond R ==> diamond (RTC R)``, PROVE_TAC [diamond_def,diamond_RTC_lemma]); \end{verbatim} \end{session} \section{Di nuovo sui combinatori} \label{sec:Return-to-Land} Ora, siamo nella posizione di ritornare all'oggetto reale dello studio e dimostrare la confluenza per la logica combinatoria. Abbiamo fatto uno sviluppo astratto e stabilito che\[ \begin{array}{ccccc} \con{diamond}\;R & \supset & \con{diamond}\,(\con{RTC}\;R)\\ & & \land\\ & & \con{diamond}\,(\con{RTC}\;R) & \equiv & \con{confluent}\;R\\ \end{array} \] (Abbiamo anche stabilito un paio di utili risultati lungo la strada.) \newcommand{\topk}{\KC\;\SC\;(\KC\;\KC\;\KC)} Purtroppo, non si da il caso che $\rightarrow$, la nostra relazione di un passo per i combinatori, abbia la proprietà diamante. Un controesempio è $\topk$. La sua evoluzione possibile può essere descritta graficamente: \[\xymatrix @R=5mm @C=2.5mm { & \topk \ar[dl] \ar[dr] & \\ \SC & & \KC\;\SC\;\KC \ar[dl] \\ & \SC}\] Se avessimo la proprietà diamante, sarebbe possibile trovare una destinazione comune per $\KC\;\SC\;\KC$ e $\SC$. Tuttavia, \SC{} non ammette alcuna riduzione di sorta, così non c'è una destinazione comune\footnote{Di fatto il nostro contro esempio è più complicato del necessario. Il fatto che $\KC\;\SC\;\KC$ abbia una riduzione alla forma normale $\SC$ agisce anche come un contro esempio. Puoi vedere perché?}. Questo è un problema. Dovremo prendere un altro approccio. Definiremo un'altra strategia di riduzione (\emph{riduzione parallela}), e dimostreremo che la sua chiusura riflessiva, transitiva è di fatto la stessa relazione della nostra chiusura riflessiva, transitiva originaria. Poi mostreremo anche che la riduzione parallela ha la proprietà diamante. Questo stabilirà che anche la sua chiusura riflessiva transitiva ce l'ha. Poi, dal momento che sono la stessa relazione, avremo che la chiusura riflessiva, transitiva della nostra relazione originaria ha la proprietà diamante, e di conseguenza, la nostra relazione originaria sarà confluente. \subsection{Riduzione parallela} \label{sec:Parallel-Reduction} La nostra nuova relazione permette l'occorrenza di un qualsiasi numero di riduzioni in parallelo. Usiamo il simbolo \texttt{-||->} per indicare la riduzione parallela a causa delle sue linee parallele: \begin{session} \begin{verbatim} - set_fixity "-||->" (Infix(NONASSOC, 450)); > val it = () : unit \end{verbatim} \end{session} Poi possiamo definire la riduzione parallela stessa. Le regole appaiono molto simili a quelle per $\rightarrow$. La differenza è che permettiamo la transizione riflessiva, e diciamo che un'applicazione di $x\;u$ può essere trasformata in $y\;v$ se ci sono trasformazioni che portano $x$ in $y$ e $u$ in $v$. Questo è il motivo per cui dobbiamo avere incidentalmente la riflessività. Senza di essa, un termine come $(\KC\;x\;y)\,\KC$ non si potrebbe ridurre perché mentre il lato sinistro dell'applicazione ($\KC\;x\;y$) si può ridurre, il suo lato destro non può. \begin{session} \begin{alltt} - val (predn_rules, _, predn_cases) = xHol_reln "predn" `(!x. x -||-> x) /\bs (!x y u v. x -||-> y /\bs u -||-> v ==> x # u -||-> y # v) /\bs (!x y. K # x # y -||-> x) /\bs (!f g x. S # f # g # x -||-> (f # x) # (g # x))`; > val predn_rules = |- (\(\forall\)x. x -||-> x) \(\land\) (\(\forall\)x y u v. x -||-> y \(\land\) u -||-> v \(\Rightarrow\) x # u -||-> y # v) \(\land\) (\(\forall\)x y. K # x # y -||-> x) \(\land\) \(\forall\)f g x. S # f # g # x -||-> f # x # (g # x) : thm val predn_cases = |- \(\forall\)a0 a1. a0 -||-> a1 \(\Leftrightarrow\) (a1 = a0) \(\lor\) (\(\exists\)x y u v. (a0 = x # u) \(\land\) (a1 = y # v) \(\land\) x -||-> y \(\land\) u -||-> v) \(\lor\) (\(\exists\)y. a0 = K # a1 # y) \(\lor\) \(\exists\)f g x. (a0 = S # f # g # x) \(\land\) (a1 = f # x # (g # x)) : thm \end{alltt} \end{session} \subsection{Usare \con{RTC}} \label{sec:Using-RTC} Ora possiamo impostare un'elegante sintassi per le chiusure riflessiva e transitiva delle nostre due relazioni. Useremo per entrambe dei simbolo ASCII che consistono del simbolo originario seguito da un asterisco. Si noti anche come, definendo le due relazioni, dobbiamo usare il carattere \texttt{\$} per effettuare l'``escape'' delle normali fixity dei simboli. Questo è esattamente analogo al modo in cui è usata la parola chiave \texttt{op} dell'ML. Per prima cosa creiamo il simbolo desiderato per la sintassi concreta, e quindi ne effettuiamo l'``overload'' così che il parser lo espanderà alla forma desiderata. \begin{session} \begin{verbatim} - set_fixity "-->*" (Infix(NONASSOC, 450)); > val it = () : unit - overload_on ("-->*", ``RTC $-->``); > val it = () : unit \end{verbatim} \end{session} We do exactly the same thing for the reflexive and transitive closure of our parallel reduction. \begin{session} \begin{verbatim} - set_fixity "-||->*" (Infix(NONASSOC, 450)); > val it = () : unit - overload_on ("-||->*", ``RTC $-||->``); > val it = () : unit \end{verbatim} \end{session} Incidentalmente, in congiunzione con \texttt{PROVE} possiamo ora dimostrare automaticamente catene di riduzioni relativamente lunghe. \begin{session} \begin{verbatim} - PROVE [RTC_rules, redn_rules] ``S # K # K # x -->* x``; Meson search level: ...... > val it = |- S # K # K # x -->* x : thm - PROVE [RTC_rules, redn_rules] ``S # (S # (K # S) # K) # (S # K # K) # f # x -->* f # (f # x)``; Meson search level: ........................... > val it = |- S # (S # (K # S) # K) # (S # K # K) # f # x -->* f # (f # x) : thm \end{verbatim} \end{session} (L'ultima è una sequenza lunga sette riduzioni.) \subsection{Dimostrare che le \con{RTC} sono le stesse} \label{sec:Proving-RTCs-same} Iniziamo con la direzione più facile, e mostriamo che ogni cosa che è in $\rightarrow^*$ è in $\mathpredn^*$. Poiché \con{RTC} è monotona (fatto che è lasciato da dimostrare al lettore), possiamo ridurre questo per mostrare che $x\rightarrow y\supset x\mathpredn y$. Il nostro goal: \begin{session} \begin{alltt} - g `!x y. x -->* y ==> x -||->* y`; > val it = Proof manager status: 1 proof. 1. Incomplete: Initial goal: \(\forall\)x y. x -->* y \(\Rightarrow\) x -||->* y \end{alltt} \end{session} Effettuiamo il back-chain usando il nostro risultato di monotonicità \begin{session} \begin{alltt} - e (MATCH_MP_TAC RTC_monotone); OK.. 1 subgoal: > val it = \(\forall\)x y. x --> y \(\Rightarrow\) x -||-> y \end{alltt} \end{session} Ora possiamo eseguire l'induzione sulle regole per $\rightarrow$: \begin{session} \begin{alltt} - e (Induct_on `$-->`); OK.. 1 subgoal: > val it = (\(\forall\)x y f. x --> y \(\land\) x -||-> y \(\Rightarrow\) f # x -||-> f # y) \(\land\) (\(\forall\)f g x. f --> g \(\land\) f -||-> g \(\Rightarrow\) f # x -||-> g # x) \(\land\) (\(\forall\)x y. K # x # y -||-> x) \(\land\) \(\forall\)f g x. S # f # g # x -||-> f # x # (g # x) \end{alltt} \end{session} Avremmo potuto dividere la congiunzione di 4 congiunti in quattro goal, ma non c'è una vera necessità. E' abbastanza chiaro che ciascuno segue immediatamente dalle regole per la riduzione parallela. \begin{session} \begin{alltt} - e (PROVE_TAC [predn_rules]); OK.. Meson search level: ............ Goal proved. [...] > val it = Initial goal proved. |- \(\forall\)x y. x -->* y \(\Rightarrow\) x -||->* y : goalstack \end{alltt} \end{session} Impacchettata in un piccolo pacchetto libero dal pacchetto subgoal, la nostra dimostrazione è \begin{session} \begin{verbatim} val RTCredn_RTCpredn = store_thm( "RTCredn_RTCpredn", ``!x y. x -->* y ==> x -||->* y``, MATCH_MP_TAC RTC_monotone THEN Induct_on `$-->` THEN PROVE_TAC [predn_rules]); \end{verbatim} \end{session} \eos{} La nostra prossima dimostrazione è nell'altra direzione. Dovrebbe essere chiaro che questa volta non saremo in grado di ricorrere semplicemente alla monotonicità di \con{RTC}; un passo della relazione di riduzione parallela non può essere riflesso da un passo della relazione di riduzione originale. E' chiaro che rispecchiare un singolo passo della relazione di riduzione parallela potrebbe richiedere molti passi della relazione originale. Dimostriamo dunque questo: \begin{session} \begin{alltt} - g `!x y. x -||-> y ==> x -->* y`; > val it = Proof manager status: 1 proof. 1. Incomplete: Initial goal: \(\forall\)x y. x -||-> y \(\Rightarrow\) x -->* y \end{alltt} \end{session} Questa volta la nostra induzione sarà sulle regole che definiscono la relazione di riduzione parallela. \begin{session} \begin{alltt} - e (Induct_on `$-||->`); OK.. 1 subgoal: > val it = (\(\forall\)x. x -->* x) \(\land\) (\(\forall\)x y x' y'. x -||-> y \(\land\) x -->* y \(\land\) x' -||-> y' \(\land\) x' -->* y' \(\Rightarrow\) x # x' -->* y # y') \(\land\) (\(\forall\)x y. K # x # y -->* x) \(\land\) \(\forall\)f g x. S # f # g # x -->* f # x # (g # x) \end{alltt} \end{session} Qui ci sono quattro congiunti, e dovrebbe essere chiaro che nessuno tranne il secondo può essere dimostrato immediatamente ricorrendo alle regole per la chiusura transitiva e per la stessa $\rightarrow$. Potremmo dividere le congiunzioni ed inserire un ramo \texttt{THENL}. Tuttavia, avremmo bisogno di ripetere la stessa tattica tre volte per chiudere velocemente tre dei quattro rami. Piuttosto, usiamo il tatticale \texttt{TRY} per provare ad applicare la stessa tattica a tutti e quattro i rami. Se la nostra tattica fallisce sul ramo \#2, come ci aspettiamo, \texttt{TRY} ci proteggerà da questo fallimento e ci permetterà di procedere. \begin{session} \begin{alltt} e (REPEAT CONJ_TAC THEN TRY (PROVE_TAC [RTC_rules, redn_rules])); OK.. Meson search level: .... Meson search level: .... Meson search level: ............................... Meson search level: .. 1 subgoal: > val it = \(\forall\)x y x' y'. x -||-> y \(\land\) x -->* y \(\land\) x' -||-> y' \(\land\) x' -->* y' \(\Rightarrow\) x # x' -->* y # y' \end{alltt} \end{session} Si noti che avvolgere \texttt{TRY} intorno a \texttt{PROVE\_TAC} non è sempre saggio. Spesso la tattica \texttt{PROVE\_TAC} richiede un tempo estremamente lungo per esaurire il suo spazio di ricerca, e poi arrendersi con un fallimento. Qui, ``siamo stati fortunati''. In ogni caso, che cosa ne facciamo di quest'ultimo subgoal? Se lo osserviamo per un tempo sufficiente, dovremmo vedere che è un altro fatto di monotonicità. Più precisamente, abbiamo bisogno di ciò che è chiamato un risultato di \emph{congruenza} per \holtxt{-->*}. In questa forma, non è molto adatto per una dimostrazione semplice. Andiamo via e dimostriamo \texttt{RTCredn\_ap\_monotonic} separatamente. (Un altro esercizio!) Il nostro nuovo teorema dovrebbe stabilire \begin{session} \begin{verbatim} val RTCredn_ap_congruence = store_thm( "RTCredn_ap_congruence", ``!x y. x -->* y ==> !z. x # z -->* y # z /\ z # x -->* z # y``, ...); \end{verbatim} \end{session} Ora che abbiamo questo, il nostro sub-goal è quasi immediatamente dimostrabile. Usandolo, sappiamo che \[\begin{array}{c} x\;x' \rightarrow^* y\;x' \\ y\;x' \rightarrow^* y\;y' \end{array}\] Tutto ciò che dobbiamo fare è ``cucire insieme'' le due transizioni di sopra e andiamo da $x\;x'$ a $y\;y'$. Possiamo fare questo ricorrendo al nostro precedente risultato \texttt{RTC\_RTC}. \begin{session} \begin{alltt} e (PROVE_TAC [RTC_RTC, RTCredn_ap_congruence]); OK.. Meson search level: ....... Goal proved. [...] > val it = Initial goal proved. |- \(\forall\)x y. x -||-> y \(\Rightarrow\) x -->* y : goalstack \end{alltt} \end{session} Ma dato che possiamo rifinire ciò che pensavamo essere un ramo scomodo con solo un'altra applicazione di \texttt{PROVE\_TAC}, non abbiamo bisogno di usare il nostro footwork con \texttt{TRY} al passo precedente. Piuttosto, possiamo semplicemente fondere le liste di teoremi passate ad entrambe le invocazioni, distribuire con \texttt{REPEAT CONJ\_TAC} e avere di fatto una dimostrazione con una tattica molto corta: \begin{session} \begin{verbatim} val predn_RTCredn = store_thm( "predn_RTCredn", ``!x y. x -||-> y ==> x -->* y``, Induct_on `$-||->` THEN PROVE_TAC [RTC_rules, redn_rules, RTC_RTC, RTCredn_ap_congruence]); \end{verbatim} \end{session} \eos{} Ora è il momento di dimostrare che se un numero di passi di riduzione parallela sono concatenati insieme, allora possiamo riflettere questo con qualche numero di passi usando la relazione di riduzione originale. Il nostro goal: \begin{session} \begin{alltt} - g `!x y. x -||->* y ==> x -->* y`; > val it = Proof manager status: 1 proof. 1. Incomplete: Initial goal: \(\forall\)x y. x -||->* y \(\Rightarrow\) x -->* y \end{alltt} \end{session} Usiamo l'appropriato principio di induzione per andare a: \begin{session} \begin{alltt} - e (Induct_on `RTC`); OK.. 1 subgoal: > val it = (\(\forall\)x. x -->* x) \(\land\) \(\forall\)x x' y. x -||-> x' \(\land\) x' -||-> y* \(\land\) x' -->* y \(\Rightarrow\) x -->* z \end{alltt} \end{session} Possiamo concludere questo in un singolo passo. La prima congiunzione è ovvia, e nella seconda \verb!x -||-> y! e il nostro ultimo risultato si combinano per dirci che \verb!x -->* y!. Poi questo può essere concatenato insieme con l'altra assunzione nel secondo congiunto e abbiamo finito. \begin{session} \begin{alltt} - e (PROVE_TAC [RTC_rules, predn_RTCredn, RTC_RTC]); OK.. Meson search level: ....... Goal proved.[...] > val it = Initial goal proved. |- \(\forall\)x y. x -||->* y \(\Rightarrow\) x -->* y : proof \end{alltt} \end{session} Impacchettata, questa dimostrazione è: \begin{session} \begin{verbatim} val RTCpredn_RTCredn = store_thm( "RTCpredn_RTCredn", ``!x y. x -||->* y ==> x -->* y``, Induct_on `RTC` THEN PROVE_TAC [predn_RTCredn, RTC_RTC, RTC_rules]); \end{verbatim} \end{session} \eos{} La nostra ultima azione è di usare ciò che abbiamo finora per concludere che $\rightarrow^*$ e $\mathpredn^*$ sono uguali. Dichiariamo il nostro goal: \begin{session} \begin{verbatim} - g `$-||->* = $-->*`; > val it = Proof manager status: 1 proof. 1. Incomplete: Initial goal: $-||->* = $-->* \end{verbatim} \end{session} Vogliamo ora ricorrere all'estensionalità. Il modo più semplice per far questo è di riscrivere con il teorema \texttt{FUN\_EQ\_THM}: \begin{session} \begin{alltt} - FUN_EQ_THM; > val it = |- \(\forall\)f g. (f = g) \(\Leftrightarrow\) \(\forall\)x. f x = g x : thm \end{alltt} \end{session} Così, riscriviamo: \begin{session} \begin{alltt} - e (SIMP_TAC std_ss [FUN_EQ_THM]); OK.. 1 subgoal: > val it = \(\forall\)x x'. x -||->* x' = x -->* x' \end{alltt} \end{session} Questo goal è una semplice conseguenza delle nostre implicazioni precedenti. \begin{session} \begin{verbatim} - e (PROVE_TAC [RTCpredn_RTCredn, RTCredn_RTCpredn]); OK.. Meson search level: ...... Goal proved. [...] > val it = Initial goal proved. |- $-||->* = $-->* : goalstack \end{verbatim} \end{session} Impacchettata, la dimostrazione è: \begin{session} \begin{verbatim} val RTCpredn_EQ_RTCredn = store_thm( "RTCpredn_EQ_RTCredn", ``$-||->* = $-->*``, SIMP_TAC std_ss [FUN_EQ_THM] THEN PROVE_TAC [RTCpredn_RTCredn, RTCredn_RTCpredn]); \end{verbatim} \end{session} \subsection{Dimostrare una proprietà diamante per la riduzione parallela} \label{sec:predn-diamond} Ora non ci resta che una dimostrazione sostanziale da fare. Prima ancora di poter cominciare, c'è un numero di lemmi minori che abbiamo bisogno di dimostrare prima. Questi sono essenzialmente delle specializzazioni del teorema \texttt{predn\_cases}. Vogliamo delle caratterizzazioni esaustive delle possibilità che si possono presentare quando i seguenti termini subiscono una riduzione parallela: $x\;y$, \KC, \SC, $\KC\;x$, $\SC\;x$, $\KC\;x\;y$, $\SC\;x\;y$ e $\SC\;x\;y\;z$. Per far questo, scriveremo una piccola funzione che deriva le caratterizzazioni automaticamente: \begin{session} \begin{verbatim} - fun characterise t = SIMP_RULE (srw_ss()) [] (SPEC t predn_cases); > val characterise = fn : term -> thm \end{verbatim} \end{session} La funzione \ml{characterise} specializza il teorema \ml{predn\_cases} con il termine di input, e quindi semplifica. Il simpset \ml{srw\_ss()} include informazioni circa l'iniettività e la disgiunzione [disjointness ndt] dei costruttori ed elimina le ovvie impossibilità. Per esempio, \begin{session} \begin{alltt} - val K_predn = characterise ``K``; <> > val K_predn = |- \(\forall\)a1. K -||-> a1 = (a1 = K) : thm - val S_predn = characterise ``S``; <> > val S_predn = |- \(\forall\)a1. S -||-> a1 = (a1 = S) : thm \end{alltt} \end{session} Sfortunatamente, ciò che otteniamo indietro dagli altri input non è così buono: \begin{session} \begin{alltt} - val Sx_predn0 = characterise ``S # x``; > val Sx_predn0 = |- \(\forall\)a1. S # x -||-> a1 = (a1 = S # x) \(\lor\) \(\exists\)y v. (a1 = y # v) \(\land\) S -||-> y \(\land\) x -||-> v : thm \end{alltt} \end{session} Quel primo disgiunto è ridondante, come dimostra il seguente: \begin{session} \begin{verbatim} val Sx_predn = prove( ``!x y. S # x -||-> y = ?z. (y = S # z) /\ (x -||-> z)``, REPEAT GEN_TAC THEN EQ_TAC THEN RW_TAC std_ss [Sx_predn0, predn_rules, S_predn]); \end{verbatim} \end{session} La nostra funzione \texttt{characterise} dovrà solo aiutarci nelle dimostrazioni che seguono. \begin{session} \begin{verbatim} val Kx_predn = prove( ``!x y. K # x -||-> y = ?z. (y = K # z) /\ (x -||-> z)``, REPEAT GEN_TAC THEN EQ_TAC THEN RW_TAC std_ss [characterise ``K # x``, predn_rules, K_predn]); \end{verbatim} \end{session} Cosa facciamo di $\KC\;x\;y$? Un poco di ragionamento dimostra che ci sono in realtà due casi questa volta. \begin{session} \begin{verbatim} val Kxy_predn = prove( ``!x y z. K # x # y -||-> z = (?u v. (z = K # u # v) /\ (x -||-> u) /\ (y -||-> v)) \/ (z = x)``, REPEAT GEN_TAC THEN EQ_TAC THEN RW_TAC std_ss [characterise ``K # x # y``, predn_rules, Kx_predn]); \end{verbatim} \end{session} Per contro, c'è solo un caso per $\SC\;x\;y$ perché esso non è ancora un ``redex'' al livello alto. \begin{session} \begin{verbatim} val Sxy_predn = prove( ``!x y z. S # x # y -||-> z = ?u v. (z = S # u # v) /\ (x -||-> u) /\ (y -||-> v)``, REPEAT GEN_TAC THEN EQ_TAC THEN RW_TAC std_ss [characterise ``S # x # y``, predn_rules, Sx_predn]); \end{verbatim} \end{session} Poi, la caratterizzazione per $\SC\;x\;y\;z$: \begin{session} \begin{verbatim} val Sxyz_predn = prove( ``!w x y z. S # w # x # y -||-> z = (?p q r. (z = S # p # q # r) /\ w -||-> p /\ x -||-> q /\ y -||-> r) \/ (z = (w # y) # (x # y))``, REPEAT GEN_TAC THEN EQ_TAC THEN RW_TAC std_ss [characterise ``S # w # x # y``, predn_rules, Sxy_predn]); \end{verbatim} \end{session} Infine, vogliamo una caratterizzazione per $x\;y$. Ciò che \texttt{characterise} ci da questa volta non può essere migliorato, per quello che potremmo considerare a partire dai quattro disgiunti. \begin{session} \begin{alltt} - val x_ap_y_predn = characterise ``x # y``; > val x_ap_y_predn = |- \(\forall\)a1. x # y -||-> a1 = (a1 = x # y) \(\lor\) (\(\exists\)y' v. (a1 = y' # v) \(\land\) x -||-> y' \(\land\) y -||-> v) \(\lor\) (x = K # a1) \(\lor\) \(\exists\)f g. (x = S # f # g) \(\land\) (a1 = f # y # (g # y)) : thm \end{alltt} \end{session} \eos{} \noindent Ora siamo pronti per dimostrare il goal finale. Esso è \begin{session} \begin{alltt} - g `!x y. x -||-> y ==> !z. x -||-> z ==> ?u. y -||-> u /\bs z -||-> u`; > val it = Proof manager status: 1 proof. 1. Incomplete: Initial goal: \(\forall\)x y. x -||-> y \(\Rightarrow\) \(\forall\)z. x -||-> z \(\Rightarrow\) \(\exists\)u. y -||-> u \(\land\) z -||-> u \end{alltt} \end{session} Possiamo ora condurre un'induzione e suddividere il goal nei suoi congiunti individuali: \begin{session} \begin{alltt} - e (Induct_on `$-||->` THEN REPEAT CONJ_TAC); OK.. 4 subgoals: > val it = \(\forall\)f g x z. S # f # g # x -||-> z \(\Rightarrow\) \(\exists\)u. f # x # (g # x) -||-> u \(\land\) z -||-> u \(\forall\)x y z. K # x # y -||-> z \(\Rightarrow\) \(\exists\)u. x -||-> u \(\land\) z -||-> u \(\forall\)x y u v. x -||-> y \(\land\) (\(\forall\)z. x -||-> z \(\Rightarrow\) \(\exists\)u. y -||-> u \(\land\) z -||-> u) \(\land\) u -||-> v \(\land\) (\(\forall\)z. u -||-> z \(\Rightarrow\) \(\exists\)u. v -||-> u \(\land\) z -||-> u) \(\Rightarrow\) \(\forall\)z. x # u -||-> z \(\Rightarrow\) \(\exists\)u. y # v -||-> u \(\land\) z -||-> u \(\forall\)x z. x -||-> z \(\Rightarrow\) \(\exists\)u. x -||-> u \(\land\) z -||-> u \end{alltt} \end{session} Il primo goal è facilmente smaltito. Il testimone che forniremmo per questo caso è semplicemente \texttt{z}, ma \texttt{PROVE\_TAC} farà il lavoro per noi: \begin{session} \begin{verbatim} - e (PROVE_TAC [predn_rules]); OK.. Meson search level: ... Goal proved. [...] \end{verbatim} \end{session} Il goal successivo include due istanze di termine della forma \verb!x # y -||-> z!. Possiamo ora usare il nostro teorema \verb!x_ap_y_predn! qui. Tuttavia, se riscriviamo in modo indiscriminato con esso, confonderemo in realtà il goal. Vogliamo riscrivere soltanto l'assunzione, non l'istanza al di sotto del quantificatore esistenziale. Iniziare il tutto applicando ripetutamente \ml{STRIP\_TAC} non ci può portare troppo fuori strada. \begin{session} \begin{alltt} - e (REPEAT STRIP_TAC); OK.. 1 subgoal: > val it = \(\exists\)u. y # v -||-> u \(\land\) z -||-> u ------------------------------------ 0. x -||-> y 1. \(\forall\)z. x -||-> z \(\Rightarrow\) \(\exists\)u. y -||-> u \(\land\) z -||-> u 2. u -||-> v 3. \(\forall\)z. u -||-> z \(\Rightarrow\) \(\exists\)u. v -||-> u \(\land\) z -||-> u 4. x # u -||-> z \end{alltt} \end{session} Abbiamo bisogno di suddividere l'assunzione 4. Siamo in grado di estrarla dalla lista delle assunzioni usando il theorem-tactical \texttt{Q.PAT\_ASSUM}. Scriveremo. \begin{verbatim} Q.PAT_ASSUM `x # y -||-> z` (STRIP_ASSUME_TAC o SIMP_RULE std_ss [x_ap_y_predn]) \end{verbatim} La quotation specifica il pattern su cui vogliamo effettuare il matching. Il secondo argomento specifica come trasformeremo il teorema. Leggendo le composizioni da destra a sinistra, prima semplificheremo con il teorema \verb!x_ap_y_predn! e poi assumeremo il risultato indietro nelle assunzioni, eliminando le disgiunzioni e gli esistenziali mentre andiamo avanti\footnote{Un'alternativa alo posto di usare \texttt{PAT\_ASSUM} è di usare \texttt{by}: ci sarebbe da dichiarare per proprio conto la disgiunzione di quattro disgiunti, ma la dimostrazione sarebbe più ``dichiarativa'' nello stile, e benché più verbosa, sarebbe più manutenibile.}. Sappiamo già che fare questo produrrà quattro nuovi sub-goal (c'erano quattro disgiunti nel teorema \verb!x_ap_y_predn!). Al meno uno di questi dovrebbe essere banale perché corrisponderà al caso in cui la riduzione parallela è semplicemente un passo ``non fare nulla''. Proviamo ad eliminare i casi semplici con una chiamata ``speculativa'' a \texttt{PROVE\_TAC} avvolta all'interno di un \texttt{TRY}. E prima di far questo, dovremmo fare alcune riscritture per essere sicuri che le uguaglianze nelle assunzioni sono eliminate. So: \begin{session} \begin{alltt} - e (Q.PAT_ASSUM `x # y -||-> z` (STRIP_ASSUME_TAC o SIMP_RULE std_ss [x_ap_y_predn]) THEN RW_TAC std_ss [] THEN TRY (PROVE_TAC [predn_rules])); OK.. Meson search level: ............................... Meson search level: ............................... Meson search level: .................. Meson search level: ..... 2 subgoals: > val it = \(\exists\)u'. y # v -||-> u' \(\land\) f # u # (g # u) -||-> u' ------------------------------------ 0. S # f # g -||-> y 1. \(\forall\)z. S # f # g -||-> z \(\Rightarrow\) \(\exists\)u. y -||-> u \(\land\) z -||-> u 2. u -||-> v 3. \(\forall\)z. u -||-> z \(\Rightarrow\) \(\exists\)u. v -||-> u \(\land\) z -||-> u \(\exists\)u. y # v -||-> u \(\land\) z -||-> u ------------------------------------ 0. K # z -||-> y 1. \(\forall\)z'. K # z -||-> z' \(\Rightarrow\) \(\exists\)u. y -||-> u \(\land\) z' -||-> u 2. u -||-> v 3. \(\forall\)z. u -||-> z \(\Rightarrow\) \(\exists\)u. v -||-> u \(\land\) z -||-> u \end{alltt} \end{session} Splendido! Abbiamo già eliminato due dei quattro disgiunti. Ora il nostro prossimo goal rappresenta un termine \verb!K # z -||-> y! nelle assunzioni. Abbiamo un teorema che riguarda proprio questa situazione. Ma prima di applicarlo volenti o nolenti, proviamo ad immaginare esattamente qual è la situazione. Un diagramma della situazione corrente potrebbe apparire come \shorthandoff{"} \[\xymatrix @C=2cm { *+<4pt>[F--]\txt{\fbox{\texttt{K\kern2mm \#\kern2mm z}} \texttt{\#} \fbox{\texttt{u}}} \ar[r] & \txt{\texttt{z}} \ar@{.>}[d] \\ *+<4pt>[F--]\txt{\fbox{\texttt{y}} \texttt{\#} \fbox{\texttt{v}}} \ar@{.>}[r] & \txt{\texttt{?u?}} \ar "1,1"!<-12pt,0pt>;"2,1"!<-12pt,0pt> \ar "1,1"!<28pt,0pt>;"2,1"!<11pt,0pt> } \] \shorthandon{"} Il nostro teorema ci dice che \texttt{y} deve di fatto essere della forma \verb!K # w! per qualche \texttt{w}, e che ci deve essere una freccia tra \texttt{z} e \texttt{w}. Così: \begin{session} \begin{alltt} - e (`?w. (y = K # w) /\bs (z -||-> w)` by PROVE_TAC [Kx_predn]); OK.. Meson search level: ...... 1 subgoal: > val it = \(\exists\)u. y # v -||-> u \(\land\) z -||-> u ------------------------------------ 0. K # z -||-> y 1. \(\forall\)z'. K # z -||-> z' \(\Rightarrow\) \(\exists\)u. y -||-> u \(\land\) z' -||-> u 2. u -||-> v 3. \(\forall\)z. u -||-> z \(\Rightarrow\) \(\exists\)u. v -||-> u \(\land\) z -||-> u 4. y = K # w 5. z -||-> w \end{alltt} \end{session} Ispezionando, appare chiaro che \texttt{u} deve essere \texttt{w}. Il primo congiunto richiede \verb!K # w # v -||-> w!, che abbiamo perché questo è ciò che fa \KC{}, e il secondo congiunto è già nella lista delle assunzioni. Riscrivendo (eliminando prima quell'uguaglianza nella lista delle assunzioni renderà il lavoro di \texttt{PROVE\_TAC} molto più facile), e poi il ragionamento al primo ordine risolverà questo goal: \begin{session} \begin{alltt} - e (RW_TAC std_ss [] THEN PROVE_TAC [predn_rules]); OK.. Meson search level: ... Goal proved. [...] Remaining subgoals: > val it = \(\exists\)u'. y # v -||-> u' \(\land\) f # u # (g # u) -||-> u' ------------------------------------ 0. S # f # g -||-> y 1. \(\forall\)z. S # f # g -||-> z \(\Rightarrow\) \(\exists\)u. y -||-> u \(\land\) z -||-> u 2. u -||-> v 3. \(\forall\)z. u -||-> z \(\Rightarrow\) \(\exists\)u. v -||-> u \(\land\) z -||-> u \end{alltt} \end{session} This case involving \SC{} is analogous. Here's the tactic to apply: \begin{session} \begin{alltt} - e (`?p q. (y = S # p # q) /\bs (f -||-> p) /\bs (g -||-> q)` by PROVE_TAC [Sxy_predn] THEN RW_TAC std_ss [] THEN PROVE_TAC [predn_rules]); OK.. Meson search level: ........ Meson search level: ........... Goal proved.[...] Remaining subgoals: > val it = \(\forall\)f g x z. S # f # g # x -||-> z \(\Rightarrow\) \(\exists\)u. f # x # (g # x) -||-> u \(\land\) z -||-> u \(\forall\)x y z. K # x # y -||-> z \(\Rightarrow\) \(\exists\)u. x -||-> u \(\land\) z -||-> u \end{alltt} \end{session} Questo prossimo goal rappresenta un termine \verb!K # x # y -||-> z! per cui abbiamo già un teorema. E di nuovo, usiamo in modo speculativo una chiamata a \texttt{PROVE\_TAC} per eliminare i casi semplici immediatamente (\verb!Kxy_predn! è un disgiunto così otteniamo due sub goal se non eliminiamo nulla). \begin{session} \begin{alltt} - e (RW_TAC std_ss [Kxy_predn] THEN TRY (PROVE_TAC [predn_rules])); OK.. Meson search level: .. Meson search level: ... Goal proved. [...] Remaining subgoals: > val it = \(\forall\)f g x z. S # f # g # x -||-> z \(\Rightarrow\) \(\exists\)u. f # x # (g # x) -||-> u \(\land\) z -||-> u \end{alltt} \end{session} Ancora meglio! Abbiamo ottenuto entrambi i casi immediatamente, e ci siamo spostati sull'ultimo caso. Possiamo provare la stessa strategia. \begin{session} \begin{alltt} - e (RW_TAC std_ss [Sxyz_predn] THEN PROVE_TAC [predn_rules]); OK.. Meson search level: .. Meson search level: ........... Goal proved.[...] > val it = Initial goal proved. |- \(\forall\)x y. x -||-> y \(\Rightarrow\) \(\forall\)z. x -||-> z \(\Rightarrow\) \(\exists\)u. y -||-> u \(\land\) z -||-> u : goalstack \end{alltt} \end{session} La dimostrazione finale del goal può essere impacchettata in: \begin{session} \begin{verbatim} val predn_diamond_lemma = prove( ``!x y. x -||-> y ==> !z. x -||-> z ==> ?u. y -||-> u /\ z -||-> u``, Induct_on `$-||->` THEN REPEAT CONJ_TAC THENL [ PROVE_TAC [predn_rules], REPEAT STRIP_TAC THEN Q.PAT_ASSUM `x # y -||-> z` (STRIP_ASSUME_TAC o SIMP_RULE std_ss [x_ap_y_predn]) THEN RW_TAC std_ss [] THEN TRY (PROVE_TAC [predn_rules]) THENL [ `?w. (y = K # w) /\ (z -||-> w)` by PROVE_TAC [Kx_predn] THEN RW_TAC std_ss [] THEN PROVE_TAC [predn_rules], `?p q. (y = S # p # q) /\ (f -||-> p) /\ (g -||-> q)` by PROVE_TAC [Sxy_predn] THEN RW_TAC std_ss [] THEN PROVE_TAC [predn_rules] ], RW_TAC std_ss [Kxy_predn] THEN PROVE_TAC [predn_rules], RW_TAC std_ss [Sxyz_predn] THEN PROVE_TAC [predn_rules] ]); \end{verbatim} \end{session} \eos{} Siamo in dirittura di arrivo. Il lemma può essere trasformato in una dichiarazione che coinvolge direttamente la costante \con{diamond}: \begin{session} \begin{verbatim} val predn_diamond = store_thm( "predn_diamond", ``diamond $-||->``, PROVE_TAC [diamond_def, predn_diamond_lemma]); \end{verbatim} \end{session} Ed ora possiamo dimostrare che la nostra relazione originale è confluente in un modo analogo: \begin{session} \begin{verbatim} val confluent_redn = store_thm( "confluent_redn", ``confluent $-->``, PROVE_TAC [predn_diamond, confluent_diamond_RTC, RTCpredn_EQ_RTCredn, diamond_RTC]); \end{verbatim} \end{session} \section{Esercizi} Se necessario, le risposte ai primi tre esercizi si possono trovare esaminando il file sorgente in \texttt{examples/ind\_def/clScript.sml}. \begin{enumerate} \item Dimostrare che \[\con{RTC}\;R \;x\; y \;\;\land \;\; \con{RTC}\;R\;y\;z\;\;\;\supset\;\;\; \con{RTC}\;R\;x\;z \] Si avrà bisogno di dimostrare il goal per induzione, e si avrà probabilmente bisogno di massaggiarlo prima leggermente per ottenere che si accordi all'appropriato principio d'induzione. Archiviare il teorema sotto il nome \texttt{RTC\_RTC}. \item Un'altra induzione. Mostrare che \[ (\forall x\,y.\; R_1\;x\;y\supset R_2\;x\;y) \supset (\forall x\,y.\; \con{RTC}\;R_1\;x\;y \supset \con{RTC}\;R_2\;x\;y) \] Chiamare il teorema risultante \texttt{RTC\_monotone}. \item Ancora un'altra induzione \con{RTC}, ma dove $R$ non è più astratta, ed è piuttosto la relazione di riduzione originale. Dimostrare \[ x \rightarrow^* y \;\;\;\supset\;\;\; \forall z.\;\; x\;z \rightarrow^* y \;z \land z\;x \rightarrow^* z\;y \] Chiamarla \texttt{RTCredn\_ap\_congruence}. \item Fornire un controesempio per la seguente proprietà: \[ \begin{array}{c} \left( \begin{array}{ll} \forall x\,y\,z. & R\;x\;y\;\;\land\;\; R\;x\;z \;\;\;\supset\\ & \exists u.\;\con{RTC}\;R\;y\;u \;\;\land\;\;\con{RTC}\;R\;z\;u \end{array}\right)\\ \supset\\ \con{diamond}\;(\con{RTC}\;R) \end{array} \] \end{enumerate} %%% Local Variables: %%% mode: latex %%% TeX-master: "tutorial" %%% End: