SSiS Veneto Fondamenti s.e. dell’Informatica I Note del corso A.A. 2001/2002 Ugo Solitro 0 U. Solitro Università degli Studi di Verona Dipartimento di Informatica Strada le Grazie, 15 – 37134 Verona telefono +39 045 802-79.77, e.mail [email protected] CAPITOLO 5 La logica di Hoare Dopo aver affrontato la questione generale della correttezza, inquadrandola matematicamente e accennato ai problemi generali legati alle dimostrazioni di correttezza, proponiamo in questo capitolo una tecnica per le dimostrazioni di correttezza parziale che usa le tecniche proprie della teoria della dimostrazione. Alla luce dei principali risultati di indecidibilità che vengono della teoria matematica della computabilità, sappiamo che nessuna metodologia generale è in grado di dare una soluzione definitiva alle suddette questioni. Il sistema formale che qui presenteremo è ugualmente importante perché da una parte mostra, nel caso ce ne fosse bisogno, come strumenti di tipo logico-matematico possono fornire l’ambiente adeguato per la trattazione delle questioni legate alla correttezza e perché consente di dare una semantica alternativa (nota come semantica assiomatica, in un certo senso più elementare al nostro linguaggio di programmazione imperativo. 1. Sistemi deduttivi formalizzati Un sistema deduttivo1 è sostanzialmente un modo di presentare una teoria matematica per mezzo di assiomi e regole. Esempi notevoli di sistemi deduttivi sono la Geometria di Euclide, l’Aritmetica di Peano, la Meccanica Razionale e molte altre ancora. In tutti i casi citati si tratta di teorie matematiche delle quali sono noti (l’insieme de) gli assiomi e (l’insieme de) le regole per dedurre asserzioni vere della teoria. Si noti che una stessa teoria matematica può esser presentata in molti modi diversi! Prendiamo ad esempio la teoria degli insiemi; è noto che la prima presentazione (o assiomatizzazione) della teoria, quella di Cantor, è insoddisfacente, furtunatamente ci sono presentazioni alternative: fra le più famose citiamo quelle note come teoria di ZermeloFraenkel (zf) e teoria di Gödel-Bernays; ambedue sono di fatto sistemi deduttivi per la stessa teoria matematica, ma differiscono nella scelta degli assiomi e delle regole2. Per l’appunto la stessa logica matematica nelle sue differenti formulazioni può esser descritta per mezzo di assiomi e regole di deduzione. 1.1. Regole di Inferenza. Quello che a noi qui interessa è che tali sistemi deduttivi possono esser “formalizzati”, cioè possono essere descritti per mezzo di opportuno linguaggio formale, fatto questo che riveste una certa importanza dal momento che ci occupiamo di linguaggi formalizzati per scrivere algoritmi. Per capire meglio di cosa stiamo parlando cominciamo con qualche esempio concreto. Esempio 30. Teoria dell’uguaglianza. Vogliamo descrivere le proprietà fondamentali dell’uguaglianza per mezzo di un sistema deduttivo formalizzato. Per fissare le idee possiamo assumere di voler trattare espressioni del linguaggio while. 1 Questo è campo di studi per la Logica Matematica e più precisamente per la Teoria della Dimostrazione (Proof Theory). 2 Un analisi più profonda delle due assiomatizzazioni rivela differenze tecnicamente apprezzabili, sulle quali tuttavia non ci possiamo soffermare. 45 46 5. LA LOGICA DI HOARE Ecco le ben note proprietà dell’uguaglianza riflessiva: t = t, simmetrica: t = t0 ⇒ t0 = t, transitiva: t = t0 , t0 = t00 ⇒ t = t00 . La prima proprietà può esser vista come un assioma che asserisce ogni espressione è uguale a sé stessa, mentre le altre due possono esser lette come regole che permettono di dedurre una certa conclusione ogni volta che siano soddisfatte le premesse. Una regola di inferenza con premesse A1 , . . . , An e conclusione A si può scrivere in può essere scritta in modo graficamente efficace nel seguente modo: A1 , . . . , A n A Esempio 31. (continuazione) Le proprietà dell’uguaglianza possono esser descritte dalle regole formalizzate seguenti x=y x=y y=z x=x y=x x=z alle quali però bisogna aggiungere un’ulteriore regola che consenta di costruire espressioni uguali non banali a partire da espressioni più semplici: t1 = t01 . . . tn = t0n f (t1 , . . . , tn ) = f (t01 , . . . , t0n ) (Esercizio: che differenza c’è – a parte la forma grafica – tra la prima formulazione delle proprietà dell’uguaglianza e quella formalizzata? Esercizio 63. Estendere il sistema deduttivo dell’ugualianza introducendo alcune regole per il calcolo della somma e il prodotto. Esercizio 64. Abbiamo già visto che un linguaggio formalizzato può esser per mezzo di una grammatica bnf; ma è possibile (e in alcune situazioni utile) presentarlo come sistema deduttivo. Ad esempio, se volessimo descrivere le formule della logica proposizionale ptremmo procedere nel che segue. • Sia P = {pi | i ∈ N} un insieme numerabile di variabili proposizionali; • Siano →, ∨, ∧ simboli per operazioni (logiche) binarie e ¬ un simbolo per un’operazione logica unaria; • L’insieme F delle formule del calcolo proposizionale son tutte e sole quelle “deducibili” con le regole seguenti: p∈P A∈P p∈F A, B ∈ F ¬A ∈ F A, B ∈ F A, B ∈ F A→B∈F A∨B ∈F A∧B ∈F dove con A e B si indica una generica formula proposizionale. (1) Scrivere un sistema deduttivo per la scrittura di programmi in un qualche senso “corretti”. (2) Esiste un procedimento uniforme per trasformare una grammatica bnf in un sistema deduttivo? Quale è l’utilità di un tale procedimento? 1. SISTEMI DEDUTTIVI FORMALIZZATI 47 1.2. Il sistema di Deduzione Naturale. Un sistema deduttivo che si rispetti deve esser in grado di usare le regole fondamentali della logica; ad esempio, la regola di inferenza che permette di far uso dei lemmi può esser scritta in forma di regola di inferenza: A A→B (Modus Ponens) B Un’altra regola interessante è quella che permette di dedurre un’implicazione. Intuitivamente (senza necessariamente pensare alle tabelle di verità) per dimostrare un’asserzione del tipo A → B è necessario esibire una deduzione della conclusione B a partire dall’ipotesi A; questo è quello che normalmente accade in matematica, a meno che non si faccia ricorso alla riduzione all’assurdo. Questa situazione può esser cosı̀ schematizzata dalla seguente regola [A] .. . B A→B che intuitivamente si legge: se assumendo A si deduce B, si è dimostrato A → B indipendentemente dalla “verità” dell’ipotesi A. Questo tipo di regola è a volte chiamata regola di deduzione perché pone delle condizioni o descrive degli effetti sull’intera deduzione3. In particolare si dirà l’assunzione A è chiusa dalla regola per l’implicazione appena descritta; le assunzioni che non sono chiuse da nessuna regola verranno per contrasto dette aperte. È possibile descrivere la logica proposizionale (e predicativa) con un sistema deduttivo che abbia al massimo tre regole per connettivo (operazione logica). Nella Tabella 1 è rappresentato il sistema deduttivo completo4 per la logica proposizionale. Si noti che si tratta di un sistema deduttivo privo di assiomi! Osservazione 13. Dimostrazioni, Dimostrabilità e Verità (interludio). Abbiamo introdotto in fretta e furia i sistemi deduttivi e mostrato un sistema deduttivo per la logica proposizionale; tale sistema può esser esteso alle logiche di ordine superiore, cominciando dalla logica dei predicati (I ordine). In questi sistemi deduttivi si possono dimostrare teoremi senza far riferimento ad alcun concetto di “verità”, apparentemente riducendo le dimostrazioni matematiche rappresentatabili in tali sistemi ad una manipolazione di simboli a livello sintattico. È possibile introdurre un concetto di modello e quindi definire che cosa si intende per verità. Uno studio approfondito potrebbe mostrare che verità e dimostrabilità non vanno sempre d’accordo5, ma questo va oltre gli limiti di queste note. Definizione 20. Sia D un sistema deduttivo per espressioni (o formule) in un linguaggio L. Diremo che A è deducibile dall’insieme di assunzioni Γ, e scriveremo Γ ` A, 3 Tecnicamente le regole di inferenza, a differenza di quelle dette di deduzione, sono regole locali perché si possono applicare senza sapere nulla dell’intera deduzione. 4 Il sistema in questione è noto come Deduzione Naturale e si deve la sua prima formulazione a Gentzen [Gen69], ma è stato studiato più a fondo solo nella seconda metà del XX secolo a partire da Prawitz [?]. 5 Ricordiamo per tutti il caso dell’Aritmetica di Peano e i teoremi di Gödel [G8̈6]. 48 5. LA LOGICA DI HOARE [A] .. . A B A→B A A→B B B A∧B A B A∨B A∨B A∧B A∧B A B .. . A∨B [A] .. . [B] .. . C C C ¬A .. . A .. . ⊥ ⊥ ⊥ ¬A A A Tabella 1. Sistema di Deduzione Naturale proposizionale. se esiste una deduzione in D di A le cui assunzioni aperte sono un sottoinsieme di Γ. Inoltre diremo che A è dimostrabile in D se ∅ ` A; in tal caso scriveremo ` A Le asserzioni formali corrispondenti ai concetti appena definiti sono dette giudizi. 2. La logica dei programmi. Finalmente arriviamo al sistema deduttivo per i programmi, cioè la cosiddetta Logica di Hoare. Vogliamo costruire un sistema logico-deduttivo a partire dalle asserzioni di correttezza. Illustreremo alcuni casi semplici e da questi estrapoleremo un sistema deduttivo che sarà tutto sommato piuttosto semplice. Nel capitolo precedente avevamo introdotto le asserzioni di correttezza e ne avevamo fissato il significato per via semantica: in particolare avevamo detto che {P } C {Q} è valida (cioè soddisfatta in ogni stato) se per ogni stato σ σ |= P ⇒ φC (σ) |= Q L’idea è quella di scoprire se ci sono asserzioni che possano giocare il ruolo di assioma e se è possibile fissare regole semplici, ma allo stesso tempo efficaci, che permettano di costruire significative asserzioni di correttezza per induzione sulla struttura dell’algoritmo. Definizione 21. Una regola A1 . . . An A 2. LA LOGICA DEI PROGRAMMI. 49 si dice corretta se conserva la validità, cioè se |= A1 . . . |= An ⇒ |= A 2.1. Il comando skip. Qual è un’asserzione di correttezza vera per il comando skip con premesse minime? Chiaramente se prima dell’esecuzione del comando è valida una certa proprietà A la stessa vale in ogni caso anche dopo; possiamo descrivere questo fatto mediante una regola senza premesse (assioma) (skip) {A} skip {A} qualunque sia A. La regola è giustificata semanticamente perché |= {A} skip {A}. Esercizio 65. Dimostrare che la per il comando skip regola è corretta. La regola è sufficiente per trattare il comando skip in tutte le situazioni? Di sicuro è abbastanza espressiva, ma ci sono situazioni che non riusciamo a catturare; ad esempio, non permette di dimostrare asserzioni del tipo {false} skip {A} che sono sicuramente validi. Questo problema si ripresenta anche per le altre regole che introdurremmo, e lo risolveremo alla fine. 2.2. L’assegnamento. Consideriamo il seguente comando di assegnamento x := E e assumiamo che il valore di E sia definito. Se prima dell’esecuzione del comando vale A che cosa possiamo dire dopo la sua esecuzione? La locazione x ha cambiato il suo valore, ma come si può esprimere questo fatto? Stranamente è un poco più semplice “pensare a rovescio”: qual è la precondizione minima che mi consente di dire che dopo l’esecuzione del comando di assegnamento vale A? Non è difficile convincersi se prima dell’assegnamento vale la proprietà A(E/x) (l’asserzione ottenuta sostituendo il valore di E per ogni occorrenza di x) allora A vale dopo; ad esempio, sono valide le seguenti asserzioni: {E = 5} x := E {x = 5}, {E < t} x := E {x < t}, {E = y} x := E {x = y}. e, più in generale, {A(E)} x := E {A(x)}. Esercizio 66. Dimostrare la validità delle asserzioni per esercizio. Dunque possiamo formulare una nuova regola senza premesse: (assegnamento) {A(E/x)} x := E {A} Esercizio 67. Dimostrare che la regola di assegnamento è corretta: |= {A(E/x)} x := E {A} 2.3. Composizione sequenziale. Si tratta ora di fornire regole anche per i casi in cui i comandi siano strutturati, cioè commposti di comandi più elementari. Cominciamo dalla composizione sequenziale dei comandi; si tratta di trovare una regola corretta, cioè che conservi la validità delle asserzioni di correttezza; trattandosi questa volta di un comando strutturato, le premesse non saranno vuote: {A} I1 {B} {B} I2 {C} (composizione) {A} I1 ; I2 {C} La regola è abbastanza intuitiva, ma si siggerisce lo stesso di verificarne la correttezza per esercizio. 50 5. LA LOGICA DI HOARE 2.4. Condizionale. Il caso del comando condizionale è appena un poco più complicato perché la condizione di verità della condizione determina l’esecuzione di comandi diversi che danno origine in linea di principio a stati diversi. (condizionale) {A ∧ E} I1 {B} {B ∧ ¬E} I2 {B} {A} if E then I1 else I2 fi {B} La regola appare paradossale ad un prima lettura: sembra infatti che, indipendentemente dalla valutazione della “guardia” E il risultato, descritto dalla asserzione B, si a sempre lo stesso. In realtà dobbiamo ricordare che B è “semplicemente” una asserzione nella logica dei predicati e, di conseguenza, può esser abbastanza espressiva da descrivere correttamente la postcondizione. Esercizio 68. Dimostrare che il seguente comando è parzialmente corretto: { x <> 0 } if x>0 then y := 1 else y := -1 { x>0 => y = 1, x<0 => y=-1 } Suggerimento: dimostrare la validità della corrispondente asserzione di correttezza. Provare inoltre ad istanziare la regola deduttiva corrispondente in modo da ottenere come conclusione esattamente l’asserzione di correttezza necessaria alla dimostrazione di correttezza parziale. Se siete riusciti a svolgere completamente l’esercizio precedente vi sarete anche convinti che la regola non adeguata a descrivere gli effetti del condizionale. Esercizio 69. Dimostrare che la regola di inferenza del condizionale è corretta. 2.5. Iterazione. Il comando di iterazione è il più delicato (l’abbiamo già osservato nei capitoli precedenti) perché in questo caso non sappiamo quante volte il comando verrà iterato. La soluzione è rappresentata da una regola che coinvolga una proprietà in grado di descrivere adeguatamente il comportamento dell’iterazione in ogni momento: {A ∧ E} I {A} (iterazione) {A} while E do I end {A ∧ ¬E} Anche in questo caso inizialmente l’intuizione non aiuta un gran ché! Un esercizio può anche in questo caso aiutare. Esercizio 70. Si consideri il seguente algoritmo (che calcola la divisione intera): { a >= 0, b > 0 } q := 0; r := a; { a = q*b + r, r >= 0 } while r => b do r := r-b; q := q+1 end { a = q*b + r, 0 <= r < b } 2. LA LOGICA DEI PROGRAMMI. 51 {A} skip {A} {A} I1 {B} {A(E/x)} x := E {A} {A ∧ E} I1 {B} {A} I1 ; I2 {C} {B ∧ ¬E} I2 {B} {A ∧ E} I {A} {A} if E then I1 else I2 fi {B} A → A0 {B} I2 {C} {A} while E do I end {A ∧ ¬E} {A0 } C {B} B0 → B {A} C {B} {A} C {B 0 } {A} C {B} Tabella 2. La logica di Hoare. • dimostrarne la correttezza parziale; • Quale proprietà può essere messa al posto di A nella regola dell’iterazione? La proprietà A che compare nella regola diinferenza è chiamata usualmente invariante del ciclo per ovvi motivi. Esercizio 71. Dimostrare la correttezza della regola di inferenza per l’iterazione. 2.6. Conseguenza. Avevamo già accennato che alcune asserzioni ovvie non possono esser ottenute dalle regole che stiamo costruendo: a volte la premessa è “più debole”, altre volte la conseguenza è “più forte”. Per risolvere la questione è sufficiente estendere il sistema deduttivo con un paio di regole che consentono di indebolire le premesse o rafforzare le conseguenze: A → A0 B0 → B {A0 } C {B} {A} C {B} {A} C {B 0 } {A} C {B} di cui, al solito, lasciamo la dimostrazione di validità per esercizio. 2.7. La logica di Hoare. La collezione di regole che abbiamo descritto (vedi Tabella 2) (integrato con la logica dei predicati) costituisce un sistema sistema logico-deduttivo per le dimostrazioni di correttezza parziale del linguaggio di programmazione while. La questione che non abbiamo ancora chiarito è se il sistema sia sufficientemente espressivo. Tecnicamente questa questione si riduce a dimostrare due risultati: correttezza: ` {A} I {B} ⇒ |= {A} I {B} |= {A} I {B} ⇒ ` {A} I {B} validità: Il primo risultato è essenzialmente ottenuto per induzione sulla struttura dell’algoritmo usando le dimostrazioni di correttezza delle regole; il secondo invece richiede una dimostrazione più articolata che si può trovare in [Win99]. 52 5. LA LOGICA DI HOARE 2.8. E la terminazione? In tutti i discorsi fatti in questo capitolo è rimasta in sospeso la questione relativa alla terminazione. Per dimostrare la correttezza totale di un algoritmo è necessario sapere che termina sempre. Per raggiungere lo scopo si potrebbe estendere il sistema logico-deduttivo in modo da includere la questione della correttezza; il sistema che si ottiene tuttavia fa perdere la caratteristica di semplicità. Oppure si può trattare la questione separatamente cercando con metodi puramente matematici di scoprire se un algoritmo (o anche semplicemente un’iterazione) termina. Se consideriamo l’iterazione while E do I end possiamo cercare di associare a tale comando una funzione (detta funzione di terminazione) τ : S −→ N tale che • σ 0 = φI (σ) allora τ (σ 0 ) < τ (0 σ); • esiste k ∈ N tale che per ogni σ ∈ S, τ (σ) ≤ k ⇒ ησ (E) = false È facile dimostrare che se un iterazione ammette una funzione di terminazione allora termina. Esercizio 72. Trovare una funzione di terminazione per l’algoritmo della divisione intera. Bibliografia [81] Enciclopedia Garzanti di Filosofia. Garzanti, 1981. a cura di L. Boni. [88] Dizionario Italiano Ragionato. Sintesi, Firenze, 1988. a cura di G. D’Anna. [AHU74] Alfred V. Aho, John E. Hopcroft, and Jeffrey D. Ullman. The Design and Analysis of Computer Algorithms. addison, 1974. [CLR90] T.H. Cormen, C.E. Leiserson, and R.L. Rivest. Introduction to Algorithms. MIT press, 1990. [CLR94] T.H. Cormen, C.E. Leiserson, and R.L. Rivest. Introduzione agli Algoritmi. Jackson Libri, 1994. Traduzione italiana di [CLR90]. [G8̈6] Kurt Gödel. Collected Works. Oxford University Press, 1986. [Gen69] Gerhard Gentzen. The collected Papers of Gerhard Gentzen. North-Holland, Amsterdam, 1969. edited by M. E. Szabo. [HU79] J. E. Hopcroft and J. D. Ullman. Introduction to Automata Theory, Languages and Computations. Addison-Wesley, 1979. [Man78] Zohar Manna. Teoria Matematica della Computazione. Boringhieri, 1978. [Rog87] Hartley Rogers. Theory of recursive functions and effective computability. MIT Press, 1987. [Win93] Glynn Winskel. The Formal Semantics of Programming Languages. An Introduction. MIT Press, 1993. [Win99] Glynn Winskel. La semantica formale dei linguaggi di programmazione. UTET Libreria, 1999. Traduzione italiana di [Win93]. [Wir77] Niklaus Wirth. Principi di Programmazione Strutturata. ISEDI, 1977. 53