Informatik III - Zusamenfassung Patrick Pletscher 15. September 2004 1 Einführung in Haskell Man kann das Ganze aber auch Infix schreiben: 1.1 Grundbegriffe der funktionalen Programmierung ? 7 ’mod’ 2 • Funktionen und Werte Eine Infix Binary-Funktion heisst ”Operator”. – Funktionen berechnen Werte Typ Bool – Funktionen können selbst als Werte betrachtet werden • Werte: True, False • Keine Seiteneffekte: f(x) ergibt immer das gleiche Ergebnis • Operatoren (infix) &&, ||, not Typ Tupel 1.2 Syntax Beispiel : Student hat Name, Martikelnummer, Anfangsjahr ggT als funktionales Programm ggt | | | x y x == y = x y > x = ggt y x otherwise = ggt (x-y) y Rekord-Typ: mit Element: (String, Int, Int) ("Fritz", 1234, 2002) Muster und Funktionsdefinition Anstatt anzugeben, wie etwas berechnet wird, gibt Funktionsdefinition mit Muster mi und Guards gi kann kombiniert werden: man an, was berechnet werden soll. fun m1 m2 ... mn | g1 = e1 : | gm = em | otherwise = e Das Programm besteht aus mehreren Fällen: Name x1 ... xn | guard1 = fall1 : | guradm = fallm ---optional! Lokale Reichweite mit let und where Typen • let Entweder der User gibt Typen mit FunktionsDefinitionen an, z.B. let x1 = e1 : xn = en in e ggt :: Int -> Int -> Int oder das System berechnet die Typen selbst. Typ Int let baut einen Ausdruck von anderen aus: • Beschränkt auf 32bit – xi kann sowohl Variable oder Funktionen (lokal) binden • Funktionen sind meist Präfixform, also: – Eine Definition kann auch andere benutzen ? mod 7 2 Beispiel 1 Korrektheit - Richtiges Verhalten f x = let sq y = y * y in sq x + sq x Basiert auf einfacher Idee: Funktionen sind Gleichungen, man kann also mathematische Methoden und auch logische Kalküle benutzen, so z.Bsp.: Wir können f auswerten, aber nicht sq. • ”Excluded Middle”: für alle Aussagen P gilt immer: P∨ 6 P • Where f p1 p2 .. pk | g1 = e1 | g2 = e2 : | gk = ek where v1 a1 ... an = r1 v2 = r2 : • Fallunterscheidung: Gegeben sei Q ∨ R. Für P müssen wir zeigen: 1. Aus der Annahme Q folgt P und 2. Aus der Annahme R folgt P • Induktion. 2 Listen – where wird direkt nach einer Funktionsdefinition gegeben 2.1 Listentypen – Man kann Bindings über mehrere Guards definieren Listentyp, aufgebaut mit Listentyp-Konstruktor: Wenn T ein Typ ist, dann ist [T ] ein Typ. Beispiel Elemente von [T ]: maxThreeOccurs n m p = (maxVal, eqCount) where maxVal = maxiThree n m p eqCount = equalCount maxVal n m p • Leere Liste [] :: [T ] • Wenn a :: T und l :: [T ], dann (a : l) :: [T ] maxiThree a b c = max a (max b c) equalCount where isN isM isP val n m p = if n == = if m == = if p == Abkürzung: 1 : (2 : (3 : [])) wird als [1, 2, 3] geschrieben. = isN + isM + isP val then 1 else 0 val then 1 else 0 val then 1 else 0 Weitere Abkürzungen: ? [3..6] [3,4,5,6]::[Int] 1.3 Korrektheit ? [6..3] []::[Int] Terminierung Falls die Funktion f durch andere Funktionen g1 , . . . , gk definiert wird und diese gi terminieren, [n,p..m] heisst Zahlen von n zu m in Schritten p−n: dann terminiert f . ? [7,6..3] [7,6,5,4,3]::[Int] Das Problem sind Rekursionen: Hinreichende Terminationsbedingung: Argumen? [0.0, 0.3 .. 1.0] te werden entlang einer wohlfundierten Ordnung [0.0, 0.3, 0.6, 0.9] :: [Double] kleiner. Wobei eine Ordnung > wohlfundiert ist, wenn es keine unendlich absteigende Ketten gibt x1 > x2 > x3 > . . .. Beispiel: > N, Gegenbeispiele: 2.2 Standardfunktion über Listen > Z, > R length Beispiel: Fakultät einer Zahl length [] = 0 length (a:x) = 1 + length x fac 0 = 1 fac n = n * fac (n-1) Append (nicht nur für Strings) fac n hat nur fac (n-1) als rekursiven Aufruf und n > n−1, wobei > die Ordnung über den natürlichen Zahlen ist. [] ++ y = y (a:x) ++ y = a:(x++y) 2 2.3 Muster 3 Abstraktion Ein Muster wird wie folgt induktiv definiert: 3.1 Funktionen höherer Stufe Konstante Argumente dürfen selber Funktionen sein. Variable (Int -> Int) -> [Int] -> [Int] Tupel : (p1 , . . . , pk ), wobei auch pi Muster sind map Liste : (p1 : p2 ) wobei auch pi Muster sind -- higher order (function f is an argument) map :: (a -> b) -> [a] -> [b] ’Wild cards’ : map f [] = [] map f (a:x) = f a : map f x Aber ein Muster muss linear sein: jede Variable darf nur maximal ein Mal vorkommen. Ähnlichkeit mit Listenkomprehension: 2.4 Musteranpassung map f l = [f a | a <- l] Argument a wird einem Muster p erfolgreich angefoldr passt gdw p ist: rechts-assoziatives Fold: Konstante : a = p f oldr(⊕) e [l1 , . . . , ln ] = l1 ⊕ (l2 , . . . ⊕ (ln ⊕ e)) Variable x: erfolgt immer mit Zuordnung x = a foldr :: (a -> b -> b) -> b -> [a] -> b Tupel (p1 , . . . , pk ): a = (a1 , . . . , ak ) und ai an pi anfoldr f e [] = e gepasst wird. foldr f e (x:xs) = f x (foldr f e xs) Liste (p1 : p2 ): a ist eine nicht leere Liste und p1 wird Damit kann man z.Bsp. concat definieren: dem Kopf von a angepasst und p2 wird dem Rest von a angepasst. concat xs = foldr (++) [] xs ? concat [[1,2,3],[4],[5,6]] ’Wild cards’ : immer, aber keine Bedingung (fun[1,2,3,4,5,6] :: [Int] giert nur als Test) foldl zip (a:x) (b:y) = (a,b) : zip x y zip _ _ = [] links-assoziatives Fold: f oldl(⊕) e [l1 , . . . , ln ] = ((e ⊕ l1 ) ⊕ l2 ) . . . ⊕ ln Insertion Sort foldl :: (a -> b -> a) -> a -> [b] -> a foldl f e [] = e foldl f e (x:xs) = foldl f (f e x) xs isort :: [Int] -> [Int] isort [] = [] isort (a:x) = ins a (isort x) ins :: Int -> [Int] -> [Int] ins a [] = [a] ins a (b:y) | a <= b = a:(b:y) | otherwise = b:(ins a y) 3.2 Typen und Polymorphismus Polymorphe Typen enthalten Typvariablen: length :: [t] -> Int Definition: Ein Typ w für f ist ein allgemeiner (auch prinzipaler ) Typ gdw. für alle Typen s für f , s eine Instanz von w ist. 2.5 Listen-Komprehension Notation für sequenzielle Verarbeitung der Listen, welche mit Tests kombiniert werden kann. 3.3 Funktionen als Werte [2*a | a <- l, b1(a), ..] Funktionen können als Werte geliefert werden. So z.Bsp. Funktionskomposition: Quicksort (f ◦ g)x = f (gx) q [] = [] q (a:x) = q [y | y<-x, y <=a] ++ [a] ++ q[y |y<-x, y>a] (.) :: (b -> c) -> (a -> b) -> (a -> c) (f . g) x = f (g x) 3 3.4 λ-Ausdrücke Fold: Kombination von Elementen Beispiel: 4.3 Map und Filter versus Komprehension ? map (\x -> x * 2) [2,3,4] [4,6,8] :: Int Map und Filter können mit Komprehension implementiert werden ? foldr (\x y -> x * y) 1 [1,2,3,4] 24 :: Int map f l = [f x | x <- l] filter p l = [x | x <- l, p x] 3.5 Partielle Anwendung Auch die Umkehrung gilt auch: [e | p <- s] Jede Funktion nimmt genau ein Argument multiply :: Int -> Int -> Int bedeutet multiply :: Int -> (Int -> Int) let fn p = e in map fn s Guards erfordern Filter: [e | p <- s, g] übersetzt als (multiply 2) ist z.Bsp. eine Funktion, die ein Argument nimmt und als Resultat die Verdoppelung ist. let fn p = e pred p = g in map fn (filter pred s) 3.6 Mehrere Argumente vs. Tupeln 4.4 zipWith f :: (Int,Int) -> Int f (x,y) = x * y + 17 zipWith :: (a -> b -> c) -> [a] -> [b] -> [c] zipWith f (x:xs) (y:ys) = f x y : zipWith f xs ys zipWith f _ _ = [] g :: Int -> Int -> Int g x y = x * y +17 Tupel-Argumente: keine partielle Auswertung. 5 Typklassen und Polymorphismus Aber äquivalent im folgenden Sinn: 5.1 Typklassen curry :: ((a,b) -> c) -> a -> b -> c uncurry :: a -> b -> c -> (a,b) -> c allEqual :: Eq a => a -> a -> a -> Bool allEqual x y z = (x == y) && (y == z) Funktioniert für genau die Typen a, die zur Klasse Eq gehören. curry f = f’ where f’ x1 x2 = f (x1,x2) uncurry f’ = f where f(x1,x2) = f’ x1 x2 Beispiele Eq ist die Gleichheitsklasse, eine Klasse definiert eine Menge von Typen. ? f (3,4) 29 :: Int Definition der Eq Klasse ? curry f 3 4 29 :: Int class Eq a where (==), (/=) :: a -> a -> Bool x /= y = not (x==y) 4 Programmierung höherer Ordnung Eine Definition beeinhaltet: Klassenname: Eq Signatur: Liste von Namen und Typen 4.1 Filter (Optional Standard-) Definition: kann später überschrieben werden filter :: (a -> Bool) -> [a] -> [a] filter p [] = [] filter p (h:t) | p h = h:rest otherwise = rest where rest = filter p t Elemente von Klassen heissen Instanzen. Instanzen Durch eine Definition von Signaturfunktionen wird ein Typ in einer Klasse instanziert. 4.2 Abstrahieren allgemeiner Operationen instance Eq Bool where True == True = True False == False = True _ == _ = False Map: Funktionale Anwendung auf jedes Element. Filter: Selektion 4 Ageleitete Klassen Regeln für Kern λ-Kalkül • Symboltabelle Axiom: . . . , x : τ, . . . ` x :: τ class Eq a => Ord a where (<), (>), (<=), (>=) :: a -> a -> Bool max, min :: a -> a -> a • Abstraktion (x 6∈ A): A, x : σ ` t :: τ A ` λx.t :: σ → τ x < y = (x <= y && x /= y) x >= y = y <= x x > y = y <= x && x/= y max x y | | min x y | | x <= y otherwise x <= y otherwise = = = = • Applikation: A ` t1 :: σ → τ A ` t2 :: σ A ` (t1 t2 ) :: τ y x x y Die Applikation vereingt sich links. Bsp. xyz steht für (xy)z Wenn a zu Ord gehört, dann muss a auch zu Eq gehören. Funktionen für Eq werden vererbt, einige neue müssen gegeben werde: Weitere Typregeln für mini-Haskell • Basistypen A ` n :: Int Instance Ord Int where (<=) = primeLeInt A ` true :: Bool A ` f alse :: Bool 5.2 Typisierung • Operationen (op ∈ {+, ×}) Ziel von Typisierung: A ` t :: Int A ` iszero(t) :: Bool • schnell entscheidbare statische Analyse A ` t1 :: Int A ` t2 :: Int A ` t1 op t2 :: Int • erlaubt so viel Allgemeinheit und Wiederverwendbarkeit wie möglich. A ` t0 :: Bool A ` t1 :: τ A ` t2 :: τ A ` if t0 then t1 else t2 :: τ • Keine Laufzeitfehler: Subjektreduktion Sei e ,→ e0 und ` e :: τ . Dann gilt auch ` e0 :: τ • Tupeln A ` t1 :: τ1 A ` t2 :: τ2 A ` (t1 , t2 ) :: (τ1 , τ2 ) Mini-Haskell - Syntax Programme sind Terme (Sie Variablen V und Zahlen Z gegeben) A ` t :: (τ1 , τ2 ) A ` fst(t) :: τ1 ::= V | λx.t | (t1 t2 ) | true | f alse | iszero(t) | Z | t1 + t2 | t1 × t2 | if t0 then t1 else t2 | (t1 , t2 ) | fst(t) | snd(t) A ` t :: (τ1 , τ2 ) A ` snd(t) :: τ2 t 6 (Algebraische) Datentypen 6.1 Algebraische Datentypen Typisierung Anstatt gegebene Typen zu verwenden, kann ein Typ Typen (VT ist die Menge von Typ Variablen, a, b, . . .) deklariert werden, der die Objekte ’direkt’ modelliert. τ ::= VT | Bool | Int | (τ, τ ) | τ → τ Aufzählungstypen (Verbunde) data Season = Spring | Summer | Fall | Winter Notation des Typsystems basiert auf Typurteilen A ` e :: τ Syntax: • fängt mit Keyword data an • A ist ’Symboltabelle’: Abbildung von Identifikatoren auf Typen • gibt verschiedene eindeutig genannte Konstruktoren • e ist ein Ausdruck • erster Buchstabe von Konstruktoren muss gross sein • τ ist ein Typ 5 6.4 Programmierung höherer Ordnung mit Datentypen Produkttypen data People = Person Name Age type Name = String type Age = Int Vorgehensweise vieler Funktionen höherer Ordnung verallgemeinbar mapTree :: (t -> u) -> Tree t -> Tree u mapTree f Leaf = Leaf mapTree f (Node x t1 t2) = Node (f x) (mapTree f t1) (mapTree f t2) Aufzählungs- und Produkttypen Kombinationen möglich data Shape = Circle Float | Rectangle Float Float 6.5 treeFold • Konstruktoren dienen als Funktionen treeFold :: (a -> b -> b -> b) -> b -> Tree a -> b ? Rectangle <<function>> :: Float -> Float -> Shape treeFold f e Leaf = e treeFold f e (Node x l r) = f x (treeFold f e l) (treeFold f e r) • Beispiele von Fuktionen: area :: Shape -> Float area (Circle r) = pi * r * r area (Rectangle h w) = h * w Von Bäumen zu Listen preorder t = treeFold (\x y z -> [x] ++ y ++ z) [] t inorder t = treeFold (\x y z -> y ++ [x] ++ z) [] t Integration mit Klassen postorder t = treeFold (\x y z -> y++ z ++ [x]) [] t Klasseninstanz kann explizit kreiert werden: data Foo = D1 | D2 | D3 7 Programmierung mit verzögerter Auswertung instance Eq Foo where D1 == D1 = True D2 == D2 = True D3 == D3 = True _ == _ = False Haskell basiert auf verzögerter Auswertung: Ein Argument wird nur ausgeführt, wenn es gebraucht wird. In einigen Fällen können Klasseninstanzen automatisch abgeleitet werden. Mögliches Problem: Duplizierte Berechnung, z.B. h x = x + x. data Foo = D1 | D2 | D3 deriving (Eq, Ord, Enum, Show) • Dieselben Ausdrücke würden hier zwei Mal ausgewertet. 6.2 Allgemeine Definition data T • Kann vermieden werden: Beide können gleichzeitig reduziert werden. = • Implementierungsidee: Ausdrücke werden als Graphen anstatt als Bäume modelliert. Con1 T11 . . . T1k1 |Con1 T21 . . . T2k2 .. . Conn Tn1 . . . Tnkn 7.1 Auswertung - Diverses Funktionen werden von aussen nach innen ausgeführt und sonst von links nach rechts. Tij sind andere Typen, möglicherweise auch T (rekursiv). 8 Substitution Rekursivtypen f ree(x) = {x} f ree(λx.M ) = f ree(M ) − {x} f ree(M N ) = f ree(M ) ∪ f ree(N ) data Expr = Lit Int | Add Expr Expr | Sub Expr Expr 6.3 Bäume 8.1 λ-Kalkül - Substitution data Tree t = Leaf | Node t (Tree t) (Tree t) deriving (Eq, Ord, Show) M [x ← N ] bedeutet: x wird durch N in M ersetzt. 6 1. x[x ← N ] = N Operationelle Semantik 2. y[x ← N ] = y, falls y 6= x Varianten: • Natürliche Semantik (Big-Step Semantics) 3. (P Q)[x ← N ] = (P [x ← N ]Q[x ← N ]) • SOS (Structural Operational Semantics) 4. (λx.P )[x ← N ] = λx.P • ASMs (Gurevich Abstract State Machines) 5. (λy.P )[x ← N ] = λy.(P [x ← N ]), falls y 6= x und y 6∈ F V (N ) 9.3 Eine einfache imperative Programmiersprache 6. (λy.P )[x ← N ] = λz.((P [y ← z])[x ← N ]) falls y 6= x und y ∈ F V (N ), wobei z 6∈ F V (N P ) Nur zwei Typen: boolean, integer Bedeutung der Anweisungen (informale Semantik) 9 Semantik von Programmiersprachen Übersicht skip x:= e s1 ; s2 9.1 Semantik (statisch vs. dynamisch) if b then s1 else s2 end Statische Semantik while b do s end Was muss zur Compilezeit überprüft werden? var x := e in s end • Typregeln, Typüberprüfung p(~e, ~z) • statische Analyse (z.B. Definite Assignment) • Auflösung von Namen Mache nichts (leere Anweisung) Weise x den Wert von e zu. Führe zuerst s1 aus, danach s2 Falls b wahr ist, führe s1 aus, sont s2 . Solange b wahr ist, führe s aus. Erzeuge eine neue lokale Variable x mit dem Wert e und führe s aus. Rufe die Prozedur p mit den Werten von ~e und den Variablen ~z auf. Bedingung: Die Variablen in ~z müssen paarweise verschieden sein. • Auflösung von Methoden (für überladene Methoden) 10 Natürliche Semantik von Programmen Dynamische Semantik Was geschieht zur Laufzeit? Idee: Zu einem Programm π definiert man induktiv Tripel σ −[s]→ σ 0 • Ausführung des Programms • Was definiert den Zustand eines Programms? mit der Bedeutung: • Was ist der Effekt eines Programms auf den Zu- Die Ausführung der Anweisung s terminiert für π und führt den Anfangszustand σ über in den Endzustand stand? σ0 . 9.2 Dynamische Semantik 10.1 Regeln der natürlichen Semantik Denationelle Semantik Die natürliche Semantik eines Programms wird durch Regeln ϕ1 . . . ϕ n Condition ψ Programme = mathematische Objekte • Ein Programm wird betrachtet als (partielle) Dabei sind ϕ1 , . . . , ϕn und ψ Tripel Funktion in einem mathematischen Raum. σ −[s]→ σ 0 • Gut für deklarative Sprachen (funktionale Programmierung). wobei σ, σ 0 ∈ State und s ∈ Stm. Bedeutung der Regel: • Zu kompliziert für objekt-orientierte Sprachen Falls Condition und ϕ1 , . . . , ϕn , dann ψ. mit Vererbung, Exception Handling, Threads. 7 10.2 Natürliche Semantik σ −[skip]→ σ σ −[x:=e]→ σ[x 7→ A[e]σ] σ −[s1 ]→ σ 0 σ 0 −[s2 ]→ σ 00 σ −[s1 ; s2 ]→ σ 00 σ −[s1 ]→ σ 0 falls B[b]σ = 1 σ −[if b then s1 else s2 end]→ σ 0 σ −[s2 ]→ σ 0 falls B[b]σ = 0 σ −[if b then s1 else s2 end]→ σ 0 σ −[s]→ σ 0 σ 0 −[while b do s end]→ σ 00 falls B[b]σ = 1 σ −[while b do s end]→ σ 00 σ −[while b do s end]→ σ falls B[b]σ = 0 σ[x 7→ A[e]σ] −[s]→ σ 0 falls x ∈ dom(σ) σ −[var x := e in s end]→ σ 0 [x 7→ σ(x)] σ[x 7→ A[e]σ] −[s]→ σ 0 falls x 6∈ dom(σ) σ −[var x := e in s end]→ σ 0 \{x 7→ σ 0 (x)} {~x 7→ A[~e]σ, ~y 7→ σ(~z)} −[s]→ σ 0 σ −[p(~e; ~z)]→ σ[~z 7→ σ 0 (~y )] für procedure p(~x; ~y ) begin s end 8 10.3 Die zugewiesenen Variablen 11.1 Transitionsregeln der ASM-Semantik AV(skip) = ∅ Leere Anweisung AV(x := e) = {x} H AV(s1 ; s2 ) = AV(s1 ) ∪ AV(s2 ) AV(if b then s1 else s2 end) = AV(s1 ) ∪ AV(s2 ) skip →Nskip Zuweisung AV(while b do s end) = AV(s) H AV(var x := e in s end) = AV(s)\{x} (x := e) → N (x := e) store(env(x)) := A[e](store ◦ env) AV(p(~e; ~z)) = {~z} Die Menge AV(s) ist eine Obermenge der Variablen, Sequenzielle Komposition die von der Anweisung s zur Laufzeit tatsächlich H (s ; s ) → H s 1 2 1 geändert werden. (N s1 ; s2 ) → H s2 (s1 ; N s2 ) → N (s1 ; s2 ) 10.4 Technische Details If-Then-Else-Anweisung Die natürliche Semantik erhält den Definitionsbereich der Zustände. Lemma 10.1. Falls σ −[s]→ σ 0 , dom(σ) = dom(σ 0 ). H dann ist if b then s1 else s2 end → if B[b](store ◦ env) = 1 then H s1 else H s2 if b then N s1 else s2 end → N if b then s1 else s2 end Die natürliche Semantik von deterministischen Programmen ist deterministisch. if b then s1 else N s2 end → N if b then s1 else s2 end Lemma 10.2. Falls σ −[s]→ σ 0 und σ −[s]→ σ 00 ,dann ist σ 0 = σ 00 . While-Anweisung H Nur zugewiesene Variablen werden geändert. Lemma 10.3. Falls σ −[s]→ σ 0 und dom(σ)\AV(s), dann ist σ 0 (x) = σ(x). while b do s end → if B[b](store ◦ env) = 1 then H s else N while b do s end x ∈ while b do N s end → H while b do s end Lokale Variablendeklaration 10.5 Koinzidenz H Lemma 10.4. Falls σ und τ auf den Variablen von e übereinstimmen, dann ist A[e]σ = A[e]τ . Lemma 10.5. Falls σ und τ auf den Variablen von b übereinstimmen, dann ist B[b]σ = B[b]τ . var x := e in s end →Hs let α = new(Adr) in store(α) := A[e](store ◦ env) env(x) := α var x := e in N s end →Nvar x := e in s end Lemma 10.6. Sei V eine Menge von Variablen, wel- Prozeduraufruf che die freien Variablen von s umfasst. Falls σ und H p(~e; ~z) → procedure p(~ x; ~ y ) beginHs end τ auf V übereinstimmen und σ −[s]→ σ 0 ,dann gibt es let ~v = A[~e](store ◦ env) and α ~ = new(Adr) in ein τ 0 so, dass τ −[s]→ τ 0 und τ und τ 0 auf V frames := push(frames, hpos, envi) übereinstimmen. store(~ α) := ~v env := {~ x 7→ α ~, ~ y 7→ env(~z)} 10.6 Semantische Äquivalenz von Programmen Rückkehr von einem Prozeduraufruf Definition 10.1. Zwei Anweisungen s1 und s2 sind procedure p(~x; ~y ) beginN s end → let holdpos, oldenvi = top(frames) in semantsich äquivalent (geschrieben: s1 ≡ s2 ), falls frames := pop(frames) für alle Zustände σ und σ 0 gilt: σ −[s1 ]→ σ 0 ⇔ pos := oldpos env := oldenv mode := up σ −[s2 ]→ σ 0 11 ASM Semantik von Programmen 11.2 Natürliche Semantik ⇒ ASM-Semantik Idee: Ein Cursor wandert in kleinen Schritten durch Theorem 11.1. Falls in der natürlichen Semantik den abstrakten Syntaxbaum des Programms und σ −[s]→ σ 0 herleitbar ist und A ein ASM-Zustand ist führt dabei Aktionen aus. mit 9 • A(pos) =Hs 12.2 Instruktionen der VM (ASM-Semantik) • σ ⊆ A(store) ◦ A(env) V M = case code(pc) of const(i) → dann erreicht die ASM-Semantik nach endlich vielen opd:=opd· [i] Schritten einen Zustand B so, dass pc:=pc+1 • B(pos) =Ns load(n) → opd:=opd· [mem(loc(n))] • σ 0 ⊆ B(store) ◦ B(env) pc:=pc+1 loada(n) → • A(env) ⊆ B(env) opd:=opd· [loc(n))] pc:=pc+1 • A(frames) = B(frames) loadi→ let rest· [α] = opd in und für alle Adressen α ∈ dom(A(store))\ran(A(env)) opd:= rest· [mem(α))] gilt pc:=pc+1 store(n) → B(store)(α) = A(store)(α) und α 6∈ ran(B(env)) let rest· [v] = opd in mem(loc(n)):=v opd:= rest 12 Compilation auf virtuelle pc:=pc+1 storei→ Maschine let rest· [α, v] = opd in mem(α):=v 12.1 Instruktionen der VM (informale opd:= rest Beschreibung) pc:=pc+1 prim(op) → const(i) Lege die Konstante i auf den let rest· [v1 , v2 ] = opd in Operandenstack. opd:= rest· [v1 op v2 ] load(n) Lade den Wert der nten lokapc:=pc+1 len Variable auf den Operandenstack. goto(i) → pc := i loada(n) Lade die Adresse der nten locond(op, i) → kalen Variable auf den Opelet rest· [v] = opd in randenstack. opd:= rest loadi Nimm die Adresse zuoberst if op(v) then pc := i auf dem Operandenstack und else pc:=pc+1 lade den Wert, der an der invoke(p, n, k) → Adresse gespeichert ist, auf let rest· [v1 , . . . , vn ] = opd in den Operandenstack. stack:= push(stack,hpc,loci) store(n) Speichere den obersten Wert pc:= f irst(p) des Operandenstacks als Wert opd:= [] der nten lokalen Variablen. loc:= {1 7→ α1 , . . . , n + k 7→ αn+k } storei Nimm den obersten Wert des Operandenstacks und die forall i ∈ [1, . . . , n] do mem(αi ) := vi darunterliegende Adresse und where α1 , . . . , αn+k = new(Adr, n + k) speichere den Wert an der return→ Adresse. let hpc0 , loc0 i = top( stack) in prim(op) Nimm die obersten zwei Werstack:= pop(stack) te des Operandenstacks und pc:= pc0 + 1 ersetze sie durch das Resultat loc:= loc0 der Operation op. opd:= [] goto(i) Springe zur iten Instruktion. cond(op, i) invoke(p, n, k) return Teste mittels op den obersten Wert des Operandenstacks. Falls der Test wahr ist, springe zu i. Rufe die Prozedur p mit n Argumenten und k lokalen Variablen auf. Kehre zurück zum Prozeduraufruf. 12.3 Compilation der arithmetischen Ausdrücke Funktion: CA : Aexp → List(Instr) Gegeben sei die Anzahl von den lokalen Variablen 10 und eine Zuordnung von Nummern zu lokalen CB1 (b1 or b2 , lab) = CB1 (b1 , lab) Variablen und formalen Parametern einer Prozedur. CB1 (b2 , lab) Falls x ∈ Var, dann ist x̄ ∈ VarNr. CB0 (b1 and b2 , lab) = CB0 (b1 , lab) CB0 (b2 , lab) Für lokale Variablen und Werteparameter x CB0 (b1 or b2 , lab) = CB1 (b1 , end) CB0 (b2 , lab) end : CA(x) = load(x̄) Für Variablenparameter x CA(x) = load(x̄) loadi 12.5 Compilation der Anweisungen Für Konstanten i Für lokale Variablen und Werteparameter x CA(i) = const(i) CS(x := e) = CA(e) store(x̄) Funktion CS : Stm → List(Instr) Für binäre Operatoren op Für Variablenparameter x CA(e1 op e2 ) = CA(e1 ) CA(e2 ) prim(op) CS(x := e) = load(x̄) CA(e) storei 12.4 Compilation der Boolschen Ausdrücke Leere Anweisung Funktionen CBi : Bexp × Pc → List(Instr) CS (skip) = [] CB1 (b, lab): Falls b wahr ist, springe zu lab, sonst ans Ende von b. CB0 (b, lab): Falls b falsch ist, springe zu lab, sonst ans Ende von b. Sequentielle Komposition CS(s1 ; s2 ) = CS(s1 ) CS(s2 ) CB1 (e1 op e2 , lab) = CA(e1 ) CA(e2 ) prim(op) cond(ne,lab) If-Anweisung CS(if b then s1 CB0 (b, else) CS(s1 ) goto(end) else : CS(s2 ) end : CB1 (not b, lab) = CB0 (b, lab) CB0 (e1 op e2 , lab) = CA(e1 ) CA(e2 ) prim(op) cond(eq,lab) else s2 Lokale Variablendeklaration CS(var x := e CA(e) store(x̄) CS(s) CB0 (not b, lab) = CB1 (b, lab) in s end)= While-Anweisung CS(while b do s end)= goto(test) whl : CS(s) test : CB1 (b, whl) CB1 (b1 and b2 , lab) = CB0 (b1 , end) CB1 (b2 , lab) end : 11 end)= 13.3 Natürliche Semantik ⇒ denationelle Semantik Prozeduraufruf (p mit k lokalen Variablen) CS(p(e1 , . . . , em ; z1 , . . . , zn )) = CA(e1 ) .. . Theorem 13.1. Falls in der natürlichen Semantik σ −[s]→ σ 0 herleitbar ist, dann gibt es ein n ∈ N, so dass in der denationellen Semantik gilt σR[s n]σ 0 . CA(em ) CV(z1 ) .. . 13.4 Denationelle Semantik ⇒ natürliche Semantik CV(zn ) invoke(p, m + n, k) Theorem 13.2. Falls in der denationellen Semantik σR[s n]σ 0 gilt, dann ist in der natürlichen Semantik σ −[s]→ σ 0 herleitbar. Für lokale Variablen und Werteparameter z CV(z) = loada(z̄) 13.5 Wiederholung: Relationen Definition 13.2. Eine Relation R auf einer Menge A ist eine Menge von Paaren R ⊆ A × A. Für Variablenparameter z CV(z) = load(z̄) Definition 13.3. Eine Relation R heisst funktional, falls für alle x, y, z gilt: Falls hx, yi ∈ R und hx, zi ∈ R, dann ist y = z. Prozedurdeklaration Komposition und Identität CS(procedure p(~x; ~y ) CS(s) return begin s end)= Zuerst S, dann R. Definition 13.4. R ◦ S S und hy, zi ∈ R} {hx, zi|∃yhx, yi ∈ 13 Denationelle Semantik Definition 13.5 (Identität auf A). IdA {hx, xi|x ∈ A} = 13.1 Beschränkte Rekursion Die Komposition ist assoziativ und IdA ist ein Neutralelement. Neue Prozedurnamen p n für n ∈ N. = Lemma 13.2. Seien R1 , R2 , R3 und R Relationen auf A. Dann gilt: Mit p n wird die Prozedur p beschränkt auf die maximale Rekursionstiefe n bezeichnet. • R1 ◦ (R2 ◦ R3 ) = (R1 ◦ R2 ) ◦ R3 • IdA ◦ R = R = R ◦ IdA • Falls die Ausführung des Prozeduraufrufs p n(~e; ~z) zu einer Rekursionstiefe grösser als n Die Komposition von Relationen ist monoton führt, terminiert der Aufruf nicht. bezüglich der Mengeninklusion. • Die Prozedur p (n + 1) ruft in ihrem Rumpf Lemma 13.3. Falls R1 ⊆ R2 und S1 ⊆ S2 , dann ist nur Prozeduren q n auf. R1 ◦ S1 ⊆ R 2 ◦ S2 Die Komposition von Relationen ist distributiv über der Vereinigung von Relationen. • Die Ausführung von p 0(~e; ~z) terminiert nie. Definition 13.1. Für eine Anweisung s bezeichnet Lemma 13.4. Sei (R ) i i∈I eine Familie von Relatioman mit s n die Anweisung, die man aus s erhält, nen. Dann gilt: indem man alle Prozeduraufrufe p(~e; ~z) durch be- [ [ [ [ schränkte Prozeduraufrufe p n(~e; ~z) ersetzt. ( Ri )◦S = (Ri ◦S), S ◦( Ri ) = (S ◦Ri ) i∈I 13.2 Monotonie i∈I i∈I i∈I Reflexivität und Transitivität Was mit kleiner Rekursionstiefe berechnet werden Definition 13.6. Eine Relation R heisst reflexiv auf kann, bekommt man auch mit grösserer RekursionsA, falls hx, xi ∈ R für alle x ∈ A. tiefe. Definition 13.7. Eine Relation R heisst transitiv Lemma 13.1 (Monotonie). Falls m ≤ n und auf A, falls für alle x, y, z ∈ A gilt: Falls hx, yi ∈ R σR[s m]σ 0 , dann gilt auch σR[s n]σ 0 und hy, zi ∈ R, dann ist hx, zi ∈ R. 12 13.7 Die While-Anweisung als kleinster Fixpunkt • R ist reflexiv auf A genau dann, wenn IdA ⊆ R. Lemma 13.5. Sei R eine Relation. Dann gilt: • R ist transitiv auf A genau dann, wenn R ◦ R ⊆ Definition 13.10. Sei Γ ein Operator von P(M ) nach P(M ). R. • X ist ein Fixpunkt von Γ, falls Γ(X) = X. Potenzen und reflexiv, transitiver Abschluss Die Potenzen Rn und der reflexiv, transitive Abschluss R∗ . • X ist der kleinste Fixpunkt von Γ, falls X ein Fixpunkt von Γ ist und für jeden weiteren Fixpunkt Y von Γ gilt, dass X ⊆ Y . Definition 13.8. Sei R eine Relation auf A. Dann definieren wir: Satz 13.1 (Fixpunktsatz). Für die While[ Anweisung gilt: 0 n+1 n ∗ n R = IdA , R =R◦R , R = R n∈N Lemma 13.6. Für alle m, n ∈ N ist R Rm+n m R[while b do s end] = kleinster Fixpunkt von Γb,s ◦ Rn = wobei der Operator Γb,s von P(State × State) nach P(State × State) definiert ist durch ∗ R ist die kleinste reflexiv, transitive Relation die R umfasst. Γb,s (X) = (X ◦ R[s] ◦ R[b]) ∪ R[not b] Lemma 13.7. Sei R eine Relation auf A. Dann gilt: • R∗ ist reflexiv und transitiv auf A und R ⊆ R∗ . 14 Hoare-Logik • Falls S eine reflexive und transitive Relation auf A ist mit R ⊆ S, dann ist R∗ ⊆ S. Axiomatische, syntaktische Lemma 13.8. Sei R eine Relation auf A. Dann gilt mit Hoare-Tripel: R∗ = IdA ∪ (R∗ ◦ R) Programmverifikation {ϕ}s{ψ} Dabei sind ϕ und ψ logische Formeln: Zusicherungen. Funktionale Relationen Die Komposition von funktionalen Relationen ist Bedeutung: Falls vor der Ausführung der Anweisung funktional. s die Formel ϕ gilt und s terminiert, dann gilt ψ nach der Ausführung von s. Lemma 13.9. • Falls R und S funktional sind, dann ist R ◦ S funktional. 14.1 Das Beweissystem von Hoare n • Falls R funktional ist, dann ist R funktional. {ϕ}skip{ϕ} Bemerkung. Wenn R funktional ist, folgt nicht dass R∗ funktional ist. Axiom (skip) {ϕ xe }x := e{ϕ} 13.6 Eigenschaften der denationellen Semantik Axiom (:=) {ϕ}s1 {ψ} {ψ}s2 {χ} {ϕ}s1 ; s2 {χ} Definition 13.9. Für Boolsche Ausdrücke b sei Regel (;) {ϕ ∧ b}s1 {ψ} {ϕ ∧ ¬b}s2 {ψ} {ϕ}if b then s1 else s2 end{ψ} R[b] = {hσ, σi|σ ∈ State, B[b]σ = 1} Theorem 13.3. Für die denotationelle Semantik von Programmen gilt: {ϕ ∧ b}s{ϕ} {ϕ}while b do s end{ϕ ∧ ¬b} R[skip] = IdState R[s1 ; s2 ] = R[s2 ] ◦ R[s1 ] R[if b then s1 else s2 end] = (R[s1 ]◦R[b])∪(R[s2 ]◦R[not b]) R[while b do s end] = R[not b] ◦ (R[s] ◦ R[b])∗ ϕ → ϕ0 {ϕ0 }s{ψ 0 } ψ 0 → ψ {ϕ}s{ψ} Regel (if) Regel (while) Abschwächung Definition 14.1. Wir schreiben ` {ϕ}s{ψ}, falls {ϕ}s{ψ} in dem erweiterten Beweissystem herleitbar ist. Lemma 13.10. R[if b then s end] = (R[s] ◦ R[b]) ∪ R[not b] 13 Definition 14.3. Wir schreiben `tot {ϕ}s{ψ}, falls {ϕ}s{ψ} in dem Beweissystem mit neuer Regel für die While-Schlaufe herleitbar ist. Ableitbare und zulässige Axiome und Regeln {ϕ ∧ b}s{ψ} ϕ ∧ ¬b → ψ {ϕ}if b then s end{ψ} Definition 14.4. Ein Tripel {ϕ}s{ψ} ist wahr im Sinne der totalen Korrektheit (geschrieben: tot {ϕ}s{ψ}), falls für alle Belegungen σ mit FV(s) ⊆ dom(σ) gilt: {ϕ}s{true} {false}s{ϕ} {ϕ}s{ϕ} Falls T [ϕ]σ = 1, dann gibt es ein σ 0 ∈ State mit hσ, σ 0 i ∈ R[s] und T [ψ]σ 0 = 1. falls FV(ϕ) ∩ AV(s) = ∅ {ϕ1 }s{ψ} {ϕ2 }s{ψ} {ϕ1 ∨ ϕ2 }s{ψ} tot {ϕ}s{ψ} bedeutet, dass in jedem Anfanszustand, in dem die Vorbedingung ϕ gilt, die Anweisung s terminiert mit einem Endzustand, in dem die Nachbedingung ψ gilt. {ϕ}s{ψ1 } {ϕ}s{ψ2 } {ϕ}s{ψ1 ∧ ψ2 } {ϕ}s{ψ} {∃xϕ}s{ψ} falls x 6∈ FV(s) ∪ FV(ψ) 14.4 Korrektheit der totalen Hoare-Logik {ϕ}s{ψ} {ϕ}s{∀xψ} falls x 6∈ FV(s) ∪ FV(ϕ) Theorem 14.2. Falls `tot {ϕ}s{ψ}, dann tot {ϕ}s{ψ}. 14.5 Erweiterung der Hoare-Logik: Arrays 14.2 Korrektheit der partiellen Hoare-Logik Axiom für die Array-Zuweisung Definition 14.2. Ein Tripel {ϕ}s{ψ} ist wahr im Sinne der partiellen Korrektheit (geschrieben {ϕ}s{ψ}), falls für alle Belegungen σ, σ 0 gilt: e2 }a[e1 ] := e2 {ϕ} {ϕ a[e 1] e2 Problem: Wie definiert man die Substitution ϕ a[e ? 1] Falls T [ϕ]σ = 1 und hσ, σ 0 i ∈ R[s], dann ist T [ψ]σ 0 = 1. Einfacher Fall: Falls das Tripel {ϕ}s{ψ} herleitbar ist im Beweissystem von Hoare, dann ist {ϕ}s{ψ} wahr. {x < a[j]}a[j + 1] := a[j]{x < a[j + 1]} | {z } | {z } ϕ a[j] Theorem 14.1. Falls ` {ϕ}s{ψ}, dann {ϕ}s{ψ}. ϕ a[j+1] Komplizierter Fall: 14.3 Totale Korrektheit von Programmen (Terminierung) {x < (k = j + 1?a[j] : a[k])}a[j + 1] := a[j]{x < a[k]} | {z } | {z } ϕ a[j] ϕ a[j+1] Die Regel (while) wird ersetzt durch die folgende Regel (whiletot ): Neue Ausdrücke: b ? e1 : e2 [if b then e1 else e2 ] {ϕ ∧ b}s{ϕ} {ϕ ∧ b ∧ t = z}s{t < z} ϕ→0≤t {ϕ}while b do s end{ϕ ∧ ¬b} 15 Dynamische Logik 15.1 Überblick Dabei muss gelten z 6∈ FV(ϕ)∪FV(t)∪FV(b)∪FV(s). Die Variable z wird benutzt um sich den Wert von t vor Ausführung von s zu merken und ihn mit dem Wert von t nach s zu vergleichen. Der Term t ist ein Mass, das bei jedem Durchlauf echt abnimmt und so die Terminierung der WhileSchlaufe garantiert. Die Prämisse ϕ → 0 ≤ t stellt sicher, dass das Mass nicht negativ wird. Der Term t ist eine obere Schranke für die Anzahl Durchläufe der While-Schlaufe. Modale Operatoren [s] (Box s) und hxi (Diamond s) Bedeutung der Operatoren [s]ϕ Falls s terminiert, dann gilt ϕ nach der Ausführung von s. hsiϕ Die Anweisung s terminiert und ϕ gilt nach der Ausführung. [s]ϕ ↔ ¬hsi¬ϕ 14 16 Bytecode Verification Verifikationsalgorithmus (top-level Loop) (Byte)Code Verifikation: Das Programm wird, wenn es geladen wird, statisch analysiert. Nur Programme, die zur Laufzeit keine Checks der defensiven VM verletzen würden, werden vom Verifikator akzeptiert. Solche Programme können dann auf der normalen VM ausgeführt werden. Verify= while Changed 6= ∅ do choose i ∈ Changed do if Check(i, locVi , opdVi ) = True then Changed := Changed\{i}; for all hj, locT, opdT i ∈ Succ(i, locVi , opdVi ) do Propagate(j, locT, opdT ) else throw ’’Check violated’’ 16.1 Verifikationsalgorithmus (Idee) Das Propagieren der Typen Propagate(j, locT, opdT ) = if j 6∈ V isited then if ValidCodeIndex(j) then locVj := locT ; opdVj := opdT ; Visited := Visited ∪ {j}; Changed := Changed ∪ {j} else throw ’’Invalid code index’’ elseif locT v locVj and opdT v opdVj then skip elseif length(opdT ) = length(opdVj ) then locVj := locVj t locT ; opdVj := locVj t opdT ; Changed := Changed ∪ {j} else throw ’’Propagation not possible’’ Die Prozeduren werden einzeln verifiziert. Der Verifikator führt die Instruktionen symbolisch aus und rechnet nur mit Typen, nicht mit Werten. Definition 16.1. Vars(p) = V alueP arams(p) ∪ V arP arams(p) ∪ Locals(p) Definition 16.2. ValidCodeIndex(j) ⇔ 0 ≤ j ≤ maxCode(p) Annahme: Alle Variablenparameter sind Outputparameter, d.h. der Verifikator muss sicherstellen, dass Die Struktur der Verify-Typen bei einer Return-Instruktion die Variablenparameter Definition 16.3. Der Typ undef ist der grösste Typ: sicher einen Wert zugewiesen haben. σ v τ ⇔ σ = τ ∨ τ = undef 16.2 Verifikation (statischer Teil) Die kleinste untere Schranke σ t τ von zwei Typen ist Die folgenden statischen Constraints müssen erfüllt int oder adr(n), falls beide int bzw. adr(n), sonst sein: undef. const(i) load(n) loada(n) store(n) prim(op) goto(i) cond(op, i) invoke(q, n, k) i∈Z n ∈ Vars(p) n ∈ Vars(p)\V arP arams(p) n ∈ Vars(p) op ∈ AddOp ∪ MulOp ∪ RelOp ValidCodeIndex(i) op ∈ {eq,ne}, ValidCodeIndex(i) n = |V alueP arams(q)∪ V arP arams(q)|, k = |Locals(q)| Vergleich der Typen von Instruktionen Definition 16.4. Punktweiser Vergleich der Typen der Variablen: locS v locT ⇔ dom(locS) = dom(locT )∧ ∀i ∈ dom(locS)(locS(i)) v locT (i)) Definition 16.5. Punktweiser Vergleich der Typen der Operanden: 16.3 Verifikation (dynamischer Teil) opdS v opdT ⇔ length(opdS) = length(opdT )∧ Anfangszustand der Verifikation von p ∀i ∈ [1 . . . length(opdS)](opdS(i) v opdT (i)) • Visited = Changed = {0} Die dynamischen Checks für die Instruktionen • opdV0 = [] Check(j, locT, opdT ) ⇔ case code(j) of load(n) → locT (n) 6= undef loadi → length(opdT ) ≥ 1 ∧ IsAddr(top(opdT, 1)) store(n) → length(opdT ) ≥ 1 storei → length(opdT ) ≥ 2 ∧ IsAddr(top(drop(opdT, 1))) prim(op) → length(opdT ) ≥ 2 ∧ last(opdT, 2) = [int,int] Cond(op, i) → length(opdT ) ≥ 1 ∧ top(opdT, 2) = int invoke(q, n, k) → length(opdT ) ≥ n∧ ∀i ∈ V alueP arams(q)(opdT (i) = int)∧ ∀i ∈ V alueP arams(q)IsAddr(opdT (i)) return → ∀i ∈ V arP arams(p)(locT (adr(i)) 6= undef) otherwise → True • locV0 = init(p) Die Typen der Variablen zu Beginn von p init(p) = ∪ ∪ ∪ {i 7→ int|i ∈ V alueP arams(p)} {i 7→ adr(i)|i ∈ V arP arams(p)} {adr(i) 7→ undef|i ∈ V arP arams(p)} {i 7→ undef|i ∈ Locals(p)} 15 Die Berechnung der Nachfolger Succ(pc, locT, opdT ) = case code(pc) of const(i) → {hpc + 1, locT, opdT · [int]i} load(n) → {hpc + 1, locT, opdT · [locT (n)]i} loada(n) → {hpc + 1, locT, opdT · [adr(n)]i} loadi → {hpc + 1, locT, drop(opdT, 1)· [locT (top(opdT ))]i} store(n) → {hpc + 1, locT [n 7→ top(opdT )], drop(opdT, 1)i} storei → {hpc + 1, locT [α 7→ top(opdT )], drop(opdT, 2)i} where α = top(drop(opdT, 1)) prim(op) → {hpc + 1, locT, drop(opdT, 2)· [int]i} goto(i) → {hi, locT, opdT i} Cond(op, i) → {hpc + 1, locT, opdT i, hi, locT, opdT i} invoke(q, n, k) → {hpc + 1, locT ⊕ out(p, opdT ), []i} return → ∅ Durch einen Prozeduraufruf mit den Argumenten opdT werden den Variablenargumenten von opdT (= die Adressen auf opdT ) Werte zugewiesen. out(p, opdT ) = {i 7→ int|adr(i) ∈ opdT, i ∈ V alP arams(p) ∪ Locals(p)}∪ {adr(i) 7→ int|adr(i) ∈ opdT, i ∈ V arP arams(p)} Das Überschreiben von Bindungen in endlichen Funktionen: f ⊕ g = {(x 7→ y) ∈ f |x 6∈ dom(g)} ∪ g 16.4 Die defensive VM Bei der defensiven VM tragen die Werte ihren Typ mit sich herum. Die defensive VM benutzt dieselben Checks wie der Verifikator. Theorem 16.1. Falls in einem Programm jede Prozedur eine Typisierung ihres Code-Arrays hat, dann verletzt das Programm keine Checks auf der defensiven VM. Konklusion: Falls ein Programm vom Verifikator akzeptiert wird, dann ist es sicher auf der normalen VM. 16