Was bisher geschah I deklarative Programmierung I I I funktional: Programm: Menge von Termgleichungen, Term Auswertung: Pattern matsching, Termumformungen logisch: Programm: Menge von Regeln (Horn-Formeln), Formel (Query) Auswertung: Unifikation, Resolution funktionale Programmierung (Haskell): I I I nebenwirkungsfrei lazy evaluation (ermöglicht unendliche Datentypen) kompakte Darstellung 15 Haskell-Programme Semantik: Funktion von Eingabe auf Ausgabe I keine Variablen, also keine Programmzustände (kein Aufruf-Kontext) I Wert jeder Funktion(sanwendung) hängt ausschließlich von den Werten der Argumente ab Syntax: I Ausdrücke: Terme z.B. 2 + x * 7 oder double 2 I Funktionsdefinition: Gleichung zwischen zwei Ausdrücken z.B. inc x = x + 1 Programm: I I I Liste von Funktionsdefinitionen Ausdruck 16 Ausdrücke Ausdruck = Term (Baumstruktur) Jeder Ausdruck hat I einen Typ und I einen Wert Berechnung des Wertes durch schrittweise Reduktion (Termersetzung) 17 Beispiele Ausdruck 7 hat I den Typ Int I den Wert 7 Ausdruck 3 * 7 + 2 hat I den Typ Int I den Wert ? Reduktion: rekursive Berechnung des Wertes 18 Funktionsdeklarationen double :: Int -> Int double x = x + x (Typdeklaration) (Funktionsdefinition) Ausdruck double 3 hat I den Typ Int I den Wert 6 Ausdruck double (double 3) hat I den Typ Int I den Wert ? Ausdruck double hat I den Typ Int -> Int I den Wert x 7→ x + x (mathematische Notation) λx.(x + x) (λ-Kalkül) 19 Typinferenz Inferenzregel: f :: A → B e :: A f e :: B (man bemerke die Analogie zum Modus Ponens) Beispiel: True :: Bool False :: Bool neg :: Bool -> Bool neg b = case b of True -> False False -> True Typ von neg True, neg (neg True) sum [1,2,3] 20 Auswertung Normalform: nicht-reduzierbarer Ausdruck Auswertung: schrittweise Reduktion, bis Normalform erreicht Auswertungsstrategien: innermost-Reduktion (strikt) outermost-Reduktion (lazy) Beispiel: double (double 3) Besonders in Haskell: I Termination I Auswertungsreihenfolge egal (Konfluenz) 21 Definition von Funktionen I Fallunterscheidung I Rekursion Beispiel: fac n = if n < 1 then 1 else n * (fac (n-1)) zum Vergleich: Ablaufsteuerung in imperativen Sprachen I Nacheinanderausführung I Verzweigung (Fallunterscheidung) I Wiederholung (Iteration) 22 Funktionen als Daten f :: Int -> Int f x = 2 * x + 3 äquivalent: Lambda-Ausdruck f = λx.(2x + 3) Funktionsanwendung (Reduktion): f = λx.A fB = A[x 7→ B] falls x nicht (frei) in B vorkommt Lambda-Kalkül: Alonzo Church 1936, Henk Barendregt 1984, ... Beispiel: A = 2x + 3, B = 1 f = λx.A = λx.(2x + 3) fB = (λx.A)B = λx.(2x + 3)1 = 2·1+3=5 23 Rekursion und Pattern Matching fac :: Int -> Int fac 1 = 1 fac n = n * (fac (n-1)) Wert von fac 4 ? Wert von fac (-4) ? Verbesserungsvorschlag ? alternative Darstellung: fac :: Int -> Int fac n = if (n > 1) then n * (fac (n-1)) else 1 noch viel mehr: http://www.willamette.edu/~fruehr/haskell/ evolution.html 24 Haskell-Datentypen einfache Datentypen, z.B. Int ganze Zahlen (feste Länge) Integer ganze Zahlen (beliebige Länge) Bool Wahrheitswerte Char ASCII-Symbole Konstruktion zusammengesetzter Datentypen: I Produkt, z.B. Tupel I Summe (Fallunterscheidung) z.B. Aufzählungstypen True, False I Rekursion, z.B. Listen, Bäume I Potenz, Funktionen 25 Algebraische Datentypen data Foo = Foo { bar :: Int, baz :: String } deriving Show Bezeichnungen: I data Foo ist Typname I Foo { .. } ist Konstruktor I bar, baz sind Komponenten x :: Foo x = Foo { bar = 3, baz = "hal" } Mathematisch: Produkt Foo = Int × String 26 Datentyp mit mehreren Konstruktoren Beispiel (selbst definiert) data T = A { foo :: Int } | B { bar :: String } deriving Show Beispiel (in Prelude vordefiniert) data Bool = False | True data Ordering = LT | EQ | GT Mathematisch: (disjunkte) Summe Bool = { False } ∪ { True } 27 Fallunterscheidung, Pattern Matching data T = A { foo :: Int } | B { bar :: String } Fallunterscheidung: f :: T -> Int f x = case x of A {} -> foo x B {} -> length $ bar x Pattern Matching (Bezeichner f,b werden lokal gebunden): f :: T -> Int f x = case x of A { foo = f } -> f B { bar = b } -> length b 28 Rekursive Datentypen: Peano-Zahlen data Nat = Zero | S Nat Addition add :: Nat -> Nat -> Nat add x Zero = x add x ( S y ) = S ( add x y ) Beispiel: add (S (S (S Zero ))) (S (S Zero)) = S (S (S (S (S Zero)))) Ausführung der Berechnungsschritte (Tafel) Nat ist mit dieser Addition assoziativ, kommutativ, add Zero x = x Nachweis durch strukturelle Induktion (Tafel) Definition weiterer Operationen: Multiplikation, Potenz 29 Datentyp Liste (polymorph) eigentlich: data List a = Nil {} | Cons { head :: a, tail :: List a } aber aus historischen Gründen data [a] = [] | a : [a] Pattern matching dafür: len :: [a] -> len xs = case [] -> (x : xss) Int xs of 0 -> ... Summe der Elemente einer Liste? 30 Strukturelle Induktion über Listen app :: [a] -> [a] -> [a] app xs ys = case xs of [] -> ys (x : xss) -> x : (app xss ys) Strukturelle Induktion zum Nachweis von Eigenschaften wie z.B. I app xs [] = xs I app ist assoziativ, d.h append xs (app ys zs) = app (app xs ys) zs 31 Mehr Beispiele Länge der Eingabeliste len :: [a] -> len xs = case [] (x : xss) Int xs of -> 0 -> 1 + len xss jedes Element der Eingabeliste verdoppeln doubles doubles [] ( y :: [Int] -> [Int] xs = case xs of -> [] : ys ) -> ( y + y ) : (doubles ys) is_monoton :: [Int] -> Bool is_monoton xs = case xs of [] -> True [ _ ] -> True (x : y : ys) -> x <= y && is_monoton (y : ys) 32 Datentyp Binärbaum (polymorph) data Tree a = Leaf {} | Branch { left :: Tree a, key :: a, right :: Tree a } Pattern Matching: f :: Tree a -> .. f t = case t of Leaf {} -> .. Branch { left = l, key = k, right = r } -> .. 33 Rekursion über binäre Bäume Anzahl der inneren Knoten count :: Tree Int -> Int count t = case t of Leaf {} -> 0 Branch {} -> count (left t) + 1 + count (right t) Anzahl der Blätter: leaves :: Tree a -> Int leaves t = case t of Leaf {} -> ... Branch {} -> ... 34 Mehr Beispiele jeden Schlüssel verdoppeln doubles :: Tree Int -> Tree Int doubles t = case t of Leaf {} -> Leaf {} Branch {} -> ... inorder :: Tree a -> [a] inorder t = case t of Leaf {} -> [] Branch {} -> ... vollständiger binärer Baum der Höhe h: full :: Int -> Tree Int full h = if h > 0 then Branch { left = full (h-1), key = h, right = full (h-1) } else Leaf {} 35 Binäre Suchbäume Suchbaum-Eigenschaft: Ein binärer Baum t :: Tree Int ist genau dann ein Suchbaum, wenn seine Knoten in Inorder-Durchquerung aufsteigend geordnet sind. search_tree t = is_monoton (inorder t) Einfügen eines Schlüssels in einen binären Suchbaum: insert :: Int -> Tree Int -> Tree Int insert x t = case t of Leaf {} -> Branch { left = Leaf {}, key = t, right = Leaf {} } Branch {} -> ... 36 Sortieren durch Einfügen in binäre Suchbäume Einfügen mehrerer Schlüssel in binären Suchbaum: inserts :: [Int] -> Tree Int -> Tree Int inserts xs t = case xs of [] -> t ( x : xss ) -> ... Sortieren durch Einfügen in binären Suchbaum: sort :: [Int] -> [Int] sort xs = inorder ( inserts xs Leaf ) 37 Strukturelle Induktion über Bäume zum Nachweis von Eigenschaften wie z.B. I Einfügen eines Knotens erhält die Suchbaum-Eigenschaft. 38