Grundlagen der Programmierung 2 (Kap 6) Operationale Semantik Prof. Dr. Manfred Schmidt-Schauß Künstliche Intelligenz und Softwaretechnologie 23. März 2011 Semantik von Programmiersprachen Semantik = Bedeutung eines Programms (Programmtextes) ausgehend vom Syntaxbaum des Programms. Methoden der Semantik-Definition: Operationale Semantik Denotationale Semantik Transformations-Semantik axiomatische Semantik Grundlagen der Programmierung 2 - 2 - Operationale Semantik Spezifikation von Wirkung und Ablauf: Zustand: Pro Programmkonstrukt: Haskell: Python: Zustand Übergänge: Zustand Übergänge: = = = = Speicherbelegung als Datenstruktur oder . . . Angabe des Zustandsübergangs Ausdruck Reduktionen, d.h. Änderungen der Ausdrücke. Umgebung Veränderungen des Speicherinhalts. operationale Semantik = Spezifikation eines Interpreters Vorteil: Prinzipiell immer durchführbar Grundlagen der Programmierung 2 - 3 - Denotationale Semantik Zuordnung: Programm 7→ Funktion Domain D: Menge der möglichen Funktionen und Objekte Pro Programmkonstrukt: rekursive Angabe der Konstruktion einer Funktion in D. Hürden: Mathematisches Vorwissen notwendig Domainkonstruktion kann schwierig sein Meist nur für kleines Fragment einer Programmiersprache Grundlagen der Programmierung 2 - 4 - Transformations-Semantik Vorgehen zur Definition der Semantik eines Programms P , wenn die Semantik für Kernsprachen-Programme bereits definiert ist • • Transformiere P → . . . → P 0 (in Kernsprache) Nehme die Semantik von P 0. Semantik von Haskell nutzt dieses Prinzip (zB Pattern, List Comprehensions) Vorteile: Vereinfacht Semantik-Definition Analog zur Arbeitsweise von Compilern Grundlagen der Programmierung 2 - 5 - Axiomatische Semantik Beschreibung der Programmkonstrukte und Eigenschaften von Programmen mittels logischer Axiome Herleiten von Programmeigenschaften durch logisches Schließen Z.B. in Prädikatenlogik: Für alle Eingaben n von natürlichen Zahlen liefert quadrat n das Ergebnis n2. Als Formel: ∀n : quadrat(n) = n2 Grundlagen der Programmierung 2 - 6 - Axiomatische Semantik Hoare-Logik: {x > 2} x := x+1 {x > 3} Vorbedingung Nachbedingung Programmbefehl Grundlagen der Programmierung 2 - 7 - Auswertung Programmen: von einfachen Haskell- Ziel: operationale Semantik von Haskell • formal saubere Definition der Auswertung • auch für Funktionen höherer Ordnung • Unabhängigkeit von Compilern • Unabhängigkeit vom Rechnertyp (Portabilität) Grundlagen der Programmierung 2 - 8 - Einfache Haskell-Programme Definition: Basiswert ist entweder eine Zahl, ein Zeichen oder True,False. Einfache Haskell-Programme: dürfen benutzen: • • • • Basiswerte und entsprechende vordefinierte Operatoren Funktionsdefinitionen if-then-else Anwendung von Funktionen auf Argumente Grundlagen der Programmierung 2 - 9 - Einfache Haskell-Programme (2) Definition: Ein (einfaches) Haskell-Programm besteht aus: • Menge von Funktionsdefinitionen; entsprechend obiger Beschränkungen • Ausdruck (main) vom Typ eines Basiswertes. Wert des Programms = Wert von main Beispiel quadrat x = x * x kubik x = x * x * x w = 2 main = if w >= 0 then quadrat w else kubik w Grundlagen der Programmierung 2 - 10 - Berechnung und Auswertung Prinzip der Berechnung für einfache Haskell-Programme Folge von Transformationen Auswertung = von main bis ein Basiswert erreicht ist main → t1 → t2 → . . . → tn → . . . Es gibt drei Fälle: 1. Die Folge endet mit einem Basiswert 2. Die Folge endet, aber nicht mit einem Basiswert (kommt nicht vor wegen des Typsystems) 3. Die Folge endet nicht Bei 2. und 3.: Wert undefiniert. Grundlagen der Programmierung 2 - 11 - Auswertung Einfache Haskell-Programm haben drei verschiedene Arten von Auswertungsschritten 1. Definitionseinsetzung (δ-Reduktion) 2. Arithmetische Auswertung 3. Auswertung von Fallunterscheidungen Grundlagen der Programmierung 2 - 12 - Satz von Church und Rosser Satz (Church-Rosser 1) Sei P ein einfaches Haskell-Programm, R1, R2 zwei verschiedene Reduktionsfolgen für main mit jeweiligen Resultat-Basiswerten e1 bzw. e2 dann sind diese Basiswerte gleich, d.h. e1 = e2 Grundlagen der Programmierung 2 - 13 - Werte von Programmen Definition: Sei P ein einfaches Haskell-Programm und main von numerischem oder Booleschem Typ. Der Wert des Programms P ist: e wenn es eine terminierende Reduktionsfolge, ausgehend von main gibt, die mit dem Basiswert e endet, ⊥ ( undefiniert ) wenn es keine mit einem Basiswert terminierende Reduktionsfolge ausgehend von main gibt. Aus dem Satz von Church-Rosser-1 folgt: Ein einfaches Haskell-Programm hat einen eindeutig definierten Wert unabhängig von der Art der Auswertung Grundlagen der Programmierung 2 - 14 - Satz 2 von Church und Rosser SATZ (Church-Rosser-2) Sei P ein einfaches Haskell-Programm. Wenn es irgendeine Reduktionsfolge für main gibt, die mit einem Basiswert e terminiert, dann terminiert auch die normale Reihenfolge der Auswertung von main und liefert als Basiswert (Resultat) genau e. Die normale Reihenfolge ist ausreichend zur Auswertung Haskell benutzt normale Reihenfolge der Auswertung Grundlagen der Programmierung 2 - 15 - Gegenbeispiel zu C.R. 2 bei applikativ Church-Rosser-2 gilt nicht für die applikative Reihenfolge: nt x = nt x proj x y = x main = proj 0 (nt 1) Applikative Reihenfolge für main terminiert nicht: (nt 1) → (nt 1) → (nt 1) → . . . . . . Deshalb: proj 0 (nt 1) → proj 0 (nt 1) → . . . . . . Die normale Reihenfolge liefert sofort 0. Grundlagen der Programmierung 2 - 16 - Programmtransformationen Optimierung: Programm P wird zur Compilezeit mittels Transformationen in ein anderes Programm P 0 umgewandelt, welches weniger Ressourcen benötigt, aber den gleichen Effekt hat. gleicher Effekt“ (∼) : ” • ∼ ist mithilfe der operationalen Semantik definierbar • Äquivalenzrelation ∼ auf Programmen und Funktionen vom gleichen Typ • Verschiedene Zahlen und Boolesche Werte sind verschieden unter ∼. Grundlagen der Programmierung 2 - 17 - Programmtransformationen Aussage: Für einfache Haskellprogramme gilt: Alle bisher betrachteten Reduktionen, auch deren Umkehrung, sind auch als Compilezeit-Transformationen verwendbar. D.h. Wenn P → P 0 mit einer Reduktion, dann gilt auch P ∼ P 0. Begründung: 1. Satz von Church-Rosser: Wenn P zu e auswertet und P → P 0 ist eine Transformation, dann wertet auch P 0 zu e aus. Grundlagen der Programmierung 2 - 18 - Programmtransformationen [] ++ ys = ys (x:xs) ++ ys = x : (xs ++ ys) Beispiel: → → =⇒ [a] ++ as a:([] ++ as) a:as [x]++xs Grundlagen der Programmierung 2 kann zu Reduktion für append, da [a] = a:[] Transformation mittels append x:xs vereinfacht werden - 19 - Programmtransformationen Bei Beschränkung auf applikativer Reihenfolge der Auswertung sowie in Programmiersprachen, die die appl. Reihenfolge verwenden, sind diese Transformationen nicht korrekt, Beispiel: Ein terminierendes Programm kann zu einem nichtterminierenden werden: meinif b x y = if b then x else y main = meinf True True bot Nach der Transformation: main = if True then True else bot D.h. auch: ein nichtterminierendes Programm kann zu einem terminierenden werden. Grundlagen der Programmierung 2 - 20 - Church-Rosser-Sätze auch für Konstruktoren Verallgemeinerter Wert statt Basiswert; auch Listen sind Werte: Ein (verallgemeinerter) Wert ist entweder 1. ein Basiswert d.h. eine Zahl oder einer der Wahrheitswerte True, False, oder 2. eine Applikation (c t1 . . . tn), wobei c ein Konstruktor ist mit der Stelligkeit n Beispiele: [1,2,3,4] 1: (map quadrat [2,3,4]) Beachte: verallgemeinerte Werte können unausgewertete Unterausdrücke haben Grundlagen der Programmierung 2 - 21 - Church-Rosser-Sätze mit Listen für Haskell-Programme Satz(Church-Rosser-1) Sei P ein Haskell-Programm, wobei Konstruktoren und case erlaubt sind, und main den Typ eines verallgemeinerten Basiswertes hat. Wenn zwei verschiedene Reduktionsfolgen jeweils verallgemeinerte Werte e1 bzw. e2 ergeben, dann gibt es einen weiteren verallgemeinerten Wert e3, so dass sowohl e1 als auch e2 zu e3 (in keinem, einem oder mehreren Schritten) reduziert werden können: | || | || || | || || | | |~ | P BBB BB BB BB BB BB BB BB ∗ e1B B B B ∗ B B B B B! e3 Grundlagen der Programmierung 2 ∗ e2 }| | | | | | | | | ∗ - 22 - Church-Rosser-Sätze; Beispiele Beispiel: Der Ausdruck [3+4,5+6] ist schon ein Wert, auf zwei Arten reduzierbar: [3 +n 4, 5 + 6] PP PPP PPP PPP PPP PPP PPP PPP PPP PP( nn nnn n n nnn nnn n n nnn nnn n n nw nn [7, 5 + 6] P PP PP PP PP PP PP P' [3n+ 4, 11] n n n n n n n n n n n n nv [7, 11] Grundlagen der Programmierung 2 - 23 - Church-Rosser-Sätze; Beispiele Beachte: Normalform muss nicht existieren Beispiel (3 + 4)i : ((5 + 6) : [1..] UU iiii iiii i i i iiii iiii i i i iiii iiii i i i i t iii i 7 : ((5 + 6) :UU[1..] UUUU UUUU UUUU UUUU UUUU UUUU UUUU UUUU UUUU U* UUUU UUUU UUUU UUUU UUUU UUUU UUUU UUUU UUUU UU* (3 + i4) : (11 : [1..]) iii iiii i i i iiii iiii i i i iiii iiii i i i iii it iii 7 : 11 : [1..] Grundlagen der Programmierung 2 - 24 - Church-Rosser-Satz 2 verallgemeinerte Werte e1, e e1> > > > > > > > > > P > > bel. Auswertung ∃ n A orm us w ale er R tu . ng - Satz (Church-Rosser-2) Sei P ein let-freies Haskell-Programm. Wenn irgendeine Reduktionsfolge main zu einem verallg. Wert e reduziert, dann terminiert auch die normale Reihenfolge der Auswertung von main mit einem verallg. Wert e1, und e1 ist reduzierbar zu e. > > e Grundlagen der Programmierung 2 - 25 - Church-Rosser-Satz 2. Beispiel [3 + 4]++[5 + 6] . sw e u A al m r no [3 + 4, 5 + 6] [3 + 4, 11] [3 + 4, 5 + 6] ist ein verallgemeinerter Wert Grundlagen der Programmierung 2 - 26 - Haskell mit rekursivem let Church-Rosser-Sätze gelten nicht in vollem Haskell Grund: rekursives let. Church-Rosser-Sätze sind zu syntaktisch! Grundlagen der Programmierung 2 - 27 - Church-Rosser-Sätze: Verallgemeinerung Notwendig: Konzept der Verhaltensgleichheit ∼ von Ausdrücken: • • verschiedene Basiswerte a, b sind nicht verhaltensgleich: a 6= b ⇒ a 6∼ b man kann Gleiches durch Gleiches ersetzen a ∼ b ⇒ P [a] ∼ P [b] Nachzuweisen ist dann: • Reduktionen (bzw. Optimierungen) erhalten die Verhaltensgleichheit: s→t⇒s∼t Grundlagen der Programmierung 2 - 28 - Induktion und Co-Induktion zum Nachweis der Gleichheit von Ausdrücken Induktionsschema für endliche Listen xs: 1. 2. Zeige die Behauptung für xs = []. Zeige, dass die Behauptung für x : xs gilt unter der Annahme, dass sie für xs gilt. Grundlagen der Programmierung 2 - 29 - Co-Induktion für Listen Co-Induktionsschema für endliche und unendliche Listen xs: 1. 2. 3. Zeige die Behauptung für xs = []. Zeige die Behauptung für xs = ⊥. Zeige, dass die Behauptung für x : xs gilt unter der Annahme, dass sie für xs gilt. Grundlagen der Programmierung 2 - 30 - Induktion für endliche Listen. Beispiel Zeige: length(xs ++ ys) = length(xs) + length(ys) für alle endlichen Listen xs,ys Basisfall: length([] ++ ys) = length(ys), und length([]) + length(ys) = length(ys) Grundlagen der Programmierung 2 - 31 - Induktion. Beispiel Zeige: length(xs ++ ys) = length(xs) + length(ys) für alle endlichen Listen xs,ys Induktionsfall: xs ist eine Liste der Länge n + 1. Auswertung beider Seiten, wobei xs = x:xs’. length(xs ++ ys) → length((x:xs’ ++ ys) → length((x:(xs’ ++ ys)) → 1+ length((xs’ ++ ys)) = 1+ length(xs’)+ length(ys)). length(xs) + length(ys) = 1+ length(xs’) + length(ys). Grundlagen der Programmierung 2 → length(x:xs’) + length(ys) - 32 -