Was bisher geschah I Algebraische Datentypen I strukturelle Induktion I Rekursionsschemata I Funktionen höherer Ordnung (z.B. map, fold, filter I Monaden (Maybe, List, IO) I Parser-Monade (Parsec) 117 Auswertungsreihenfolge inc :: Int -> Int inc n = n + 1 2 Möglichkeiten, den Wert von inc(3 * 5) zu berechnen Es wird bei beiden Möglichkeiten derselbe Wert berechnet. (Haskell ist nebenwirkungsfrei.) sq :: Int -> Int sq x = x * x sq (3 + 1) 118 Redex Reduktion Termersetzung durch Funktionsanwendung Redex reduzierbarer Teilterm Redexe von mult( 1 + 2, 2 + 3 ) mult :: Int -> Int -> Int mult = \x \y -> x * y mult ( 1 + 2 )( 2 + 3 ) fst :: (a, b) -> a fst ( x, _ ) = x fst ( 1 + 2, 3 + 5 ) 119 Auswertungs-Strategien innermost Reduktion von Redexen, die keinen Redex enthalten (Parameterübergabe by value) outermost Reduktion von Redexen, die in keinem Redex enthalten sind (Parameterübergabe by name) (jeweils so weit links wie möglich zuerst) Teilterme in λ-Ausdrücken werden nicht reduziert. mult :: Int -> Int -> Int mult x = \y -> x * y mult ( 1 + 2 )( 2 + 3 ) (\ x -> 1 + 2) 1 120 Lazy Evaluation in Haskell jeder Funktionsaufruf ist lazy: I kehrt sofort zurück I Resultat ist thunk I thunk wird erst bei Bedarf ausgewertet I Bedarf entsteht durch Pattern Matching I Sharing von Teilausdrücken sq :: Int -> Int sq x = x * x sq (3 + 1) data N = Z | S N nichtnull :: N -> Bool nichtnull n = case n of Z -> False ; S _ -> True x = S (error "42") nichtnull x Lazy Evaluation (Bedarfsauswertung) = Outermost-Reduktionsstrategie mit Sharing 121 Termination inf :: Int inf = 1 + inf Auswertung von fst (0, inf) terminiert unter outermost-Strategie, aber nicht unter innermost-Strategie Fakt 1 Für jeden Ausdruck, für den die Auswertung unter irgendeiner Strategie terminiert, terminert auch die Auswertung unter outermost-Strategie. 122 Unendliche Datenstrukturen einsen :: [Int] einsen = 1 : einsen head einsen take take take take :: Int -> [a] -> [a] 0 _ = [] (n + 1) [] = ... (n + 1) (x : xs) = ... take 3 einsen nats :: [Int] nats = 0 : map (+1) nats takeWhile (<= 5) nats 123 Motivation: Datenströme Folge von Daten: I erzeugen (producer) I transformieren I verarbeiten (consumer) aus softwaretechnischen Gründen diese drei Aspekte im Programmtext trennen, aus Effizienzgründen in der Ausführung verschränken (bedarfsgesteuerter Transformation/Erzeugung) 124 Beispiele nats = 0 : map (+1) nats fibonacci = 0 : 1 : zipWith (+) fibonacci ( tail fibonacci ) take 10 fibonacci take 1 $ dropWhile (< 200) fibonacci Welchen Wert hat bin ? bin = False : True : concat ( map ( \ x -> [ x, not x ] ) ( tail bin ) ) 125 Thue-Morse-Wort t := limn→∞ τ n (0) für τ : 0 7→ 01, 1 7→ 10 t = 0110100110010110 . . . t ist kubikfrei Folge der Abstände zwischen den Nullen v := 210201210120 . . . ist auch Fixpunkt eines Morphismus v ist quadratfrei 126 Beispiel Primzahlen Sieb des Eratosthenes primes :: [ Int ] primes = sieve [2 ..] sieve :: [ Int ] -> [ Int ] sieve (x : xs) = x : sieve [y | y <- xs, mod y x /= 0] take 100 primes takeWhile (< 100) primes 127 Strikte Auswertung Symbol ⊥ für undefinierte Werte zu jedem Typ T betrachte T⊥ = {⊥} ∪ T Funktion f heißt strikt, wenn f (⊥) = ⊥. (Funktionswert immer undefiniert, wenn ein Argumentwert undefiniert ist.) in Haskell: I Konstruktoren (Cons,. . . ) sind nicht strikt, I Destruktoren (head, tail,. . . ) sind strikt. für Funktionen mit mehreren Argumenten: betrachte Striktheit in jedem Argument einzeln. Falls Striktheit bekannt ist, kann der Compiler effizienteren Code erzeugen (frühe Argumentauswertung) 128 Strikte Auswertung in Haskell seq :: a -> b -> b undefined ‘seq‘ y = undefined x ‘seq‘ y = y ($!) :: (a -> b) -> a -> b f $! x = x ‘seq‘ f x 129 Beispiele square x = x * x square ( 1 + 2 ) square $! ( 1 + 2 ) Striktheit in einzelnen Argumenten (f $! x) y (f x) $! y (f $! x) $! y sum_n :: Integer -> [Integer] -> Integer sum_n n [] = n sum_n n ( x : xs ) = sum_n (n + x) xs sum_n 0 [ 1,2,3] sum_n 0 [ 1 .. 100000000] strikte Auswertung eines Teiltermes erzwingen: sum_n n ( x : xs ) = (sum_n $!(n + x)) xs 130 Funktionale und logische Programmierung I funktionale Programmierung: LISP (John McCarthy, 1957) benutzerdefinierte Funktionen, definiert durch Gleichungen (Ersetzungsregeln) Rechnen = Normalform bestimmen Methode: Termersetzung I logische Programmierung: Prolog (Alain Colmerauer, 1972) benutzerdefinierte Relationen (Prädikate), definiert durch Schlussregeln (Implikationen). Rechnen = Schlussfolgerung (Widerspruch) ableiten Methode: prädikatenlogische Resolution 131 Wiederholung: Prolog-Syntax Regel (Klausel) a :- a1, ..., an. Bedeutung in Prädikatenlogik (der ersten Stufe) (∀X1 · · · ∀Xn ((a1 ∧ · · · ∧ an ) → a) wobei X1 , . . . Xn alle in a, a1 , . . . , an vorkommenden Variablen sind. Rumpf a1 ∧ . . . ∧ an , Kopf a Fakt Atom a. (positives Literal, Regel ohne Rumpf) Bedeutung in Prädikatenlogik (der ersten Stufe): ∀X1 · · · ∀Xn a, wobei X1 , . . . Xn alle in a vorkommenden Variablen sind. Zielklausel (Query, Anfrage) ?- a1, ..., an. Bedeutung in Prädikatenlogik (der ersten Stufe): (∀X1 · · · ∀Xn (a1 ∧ · · · ∧ an ) wobei X1 , . . . Xn alle in a1 , . . . , an vorkommenden Variablen sind. Variablennamen beginnen mit Großbuchstaben, Funktions- und Relationssymbole mit Kleinbuchstaben 132 Wiederholung: Prolog-Programme Programm P (Wissensbasis): endliche Menge von Fakten und Regeln, repräsentiert eine prädikatenlogische Formelmenge Φ, repräsentiert eine prädikatenlogische Formel V ϕ = ψ∈Φ ψ Beispiel: Programm P liest(max,krimi). liest(bob,zeitung). liest(tina,arztroman). mag(tina,X) :- liest(X,krimi). repräsentiert die Formelmenge Φ = {l(m, k ), l(b, z), l(t, a), ∀x(l(x, k ) → m(t, x))} 133 Wiederholung: Prolog-Anfragen Zielklausel Atom repräsentiert eine prädikatenlogische Formel ψ Beispiel: ?- mag (tina,X). repräsentiert die Formel ψ = mag(ina, X ) 134 Wiederholung: Prolog-Auswertung Ausgewertet werden Paare (Φ, ψ) aus I Programm Φ I Zielklausel ψ (prädikatenlogische Darstellung von Programm und Anfrage) Antwort: Substitution θ mit Φ |= θ(ψ) (Prolog-Ausgabe: Grundinstanzen θ(ψ) der Zielklausel ψ) 135 Prolog: Bestimmung der Antworten I durch Lösung des Problemes Für welche Substitutionen θ gilt Φ |= θ(ψ)? I durch Lösung des äquivalenten Problemes: Für welche Substitutionen θ ist die Formelmenge Φ ∪ ¬θ(ψ) unerfüllbar? I durch Lösung des äquivalenten Problemes: Für welche Substitutionen θ gilt Φ ∪ ¬θ(ψ) |= f ? I durch Bestimmung der Substitutionen θ, für die f aus Φ ∪ ¬θ(ψ) syntaktisch herleitbar ist. I durch prädikatenlogische Resolution (mit festgelegter Auswertungsreihenfolge, SLD-Resolution) 136 Beispiel für Prolog-Auswertung Programm P: p(a,b). p(b,c). p(c,d). e(X,Y) :- p(X,Y). e(X,Y) :- p(X,Z), e(Z,Y). Zielklausel e(X , d) Prädikatenlogische Bedeutung: I Programm P: p(a, b), p(b, c), p(c, d), ∀X ∀Y (p(X , Y ) → e(X , Y )), Φ= ∀X ∀Y ∀Z (p(X , Z ) ∧ e(Z , Y ) → e(X , Y )) I Zielklausel ψ = e(X , d) I negierte Zielklausel ¬ψ = ¬e(X , d) I Problem ¬e(X , d), p(a, b), p(b, c), p(c, d), ∀X ∀Y (p(X , Y ) → e(X , Y )), {¬ψ} ∪ Φ = ∀X ∀Y ∀Z (p(X , Z ) ∧ e(Z , Y ) → e(X , Y )) 137 Wiederholung Unifikation Substitution: partielle Funktion θ : X → Term(Σ, X ), wobei keine Variable x, für welche θ(x) definiert ist, für ein y ∈ X in f (y ) vorkommt, Notation: [x 7→ t1 , y 7→ t2 , . . .] Unifikator der Terme (Atome, Literale) s und t: Substitution θ mit sθ = tθ mgu(s, t) allgemeinster Unifikator der Terme (Atome, Literale) s und t: Unifikator θ für s und t, so dass zu jedem Unifikator σ für s und t eine Substitution ρ für s und t existiert, so dass gilt: σ(s) = ρ(θ(s)) 138 Wiederholung Unifikationsalgorithmus zur Bestimmung des mgu (sofern dieser existiert) Eingabe: Terme (Atome, Literale) s, t Ausgabe: (unifizierbar, mgu(s, t)), falls mgu(s, t) existiert, sonst nicht unifizierbar Algorithmus (rekursiv) : unifiziere(s, t) BF1 falls s, t Konstanten: falls s = t: (unifizierbar, mgu(s, t) = []), sonst nicht unifizierbar BF2 falls s Variable, t Konstante: [s 7→ t] in θ einfügen und auf alle rechten Seiten in θ anwenden BF3 falls s Variable, t = f (t1 , . . . , tn ): falls s nicht in t vorkommt: mgu(s, t) = [s 7→ t], sonst nicht unifizierbar RF falls s = g(s1 , . . . , sm ), t = f (t1 , . . . , tn ): falls m 6= n oder f 6= g: nicht unifizierbar sonst θ = unifiziere(s1 , t1 ), . . . , unifiziere(sn , tn ) 139 Beispiel für Prolog-Auswertung Programm: append(nil,Y,Y). append(cons(X,Y),Z,cons(X,W)) :append(Y,Z,W). Anfragen ????- append append append append (cons(a,nil),cons(b,nil),Z). (X,Y,nil). (X,Y,cons(a,nil)). (X,X,cons(a,cons(a,nil))). 140 Prolog-Interpreter in Haskell I Datentypen für I I I I I I I I I Funktions- und Prädikatsymbole Identifier (String) Variablen Identifier (String) Terme Tree Atome (Identifier, [Tree]) Regeln (Atom, [Atom]) Fakten (Atom, []) Anfragen (Regeln mit leerem Kopf) Programme [Regel] Substitutionen Map s Term 141 Suche in Haskell Modellierung von Suche/Nichtdeterminismus in Haskell: Liste von Resultaten, vgl. permutationen :: [a] -> [[a]] permutationen [] = return [] permutationen (x:xs) = do ys <- perms xs (pre, post) <zip (inits xs) (tails xs) return $ pre ++ x : post Phil Wadler: How to replace failure by a list of successes—a method for exception handling, backtracking, and pattern matching in lazy functional languages. 1985. http://homepages.inf.ed.ac.uk/wadler/ 142 Prolog-Interpreter query :: [Clause] -> [Atom] -> [Substitution] query cs [] = return M.empty query cs (a : as) = do u1 <- single cs a u2 <- query cs $ map ( apply u1 ) as return $ u1 ‘times‘ u2 single :: [Clause] -> Atom -> [Substitution] single cs a = do c <- cs let c’ = rename c -- VORSICHT u1 <- maybeToList $ unify a $ head c’ u2 <- query cs $ map ( apply u1 ) $ body c’ return $ u1 ‘times‘ u2 143 Global eindeutige Namen bei jeder Benutzung jeder Klausel müssen deren Variablen umbenannt werden (= durch „frische“ Namen ersetzt). Globalen Zähler hinzufügen = Zustands-Monaden-Transformator anwenden. single :: [Clause] -> Atom -> [Substitution] single cs a = do c <- cs import Control.Monad.State single :: [Clause] -> Atom -> StateT Int [] Substitution single cs a = do c <- lift cs 144 Ideales und Reales Prolog wie hier definiert (ideal): I Semantik ist deklarativ I Reihenfolge der Regeln im Programm und Atome in Regel-Rumpf beeinflusst Effizienz, aber nicht Korrektheit reales Prolog: I cut (!) zum Abschneiden der Suche I I green cut: beeinflusst Effizienz red cut: ändert Semantik merke: cut ≈ goto, grün/ rot schwer zu unterscheiden I Regeln mit Nebenwirkungen (u. a. für Ein/Ausgabe) für beides: keine einfache denotationale Semantik 145 Erweiterungen I eingebaute Operationen (Maschinenzahlen) I effiziente Kompilation (für Warren Abstract Machine) I Modi: Deklaration von In/Out und Determinismus (Mercury) I Funktionen/Prädikate höherer Ordnung: Lambda-Prolog (Dale Miller) http: //www.lix.polytechnique.fr/~dale/lProlog/ I statisches Typsystem: Mercury (Fergus Henderson) http://www.mercury.csse.unimelb.edu.au/ 146