Funktionale Programmierung 3.5.2005 Das Funktionale Quiz Warum kann man keine Funktion schreiben, die die erste Komponente eines beliebgen Tupels von Integern zurückgibt? Weil Tupel unterschiedlicher Länge unterschiedliche Typen haben Das Funktionale Quiz Das Funktionale Quiz Was ist das Problem mit folgender Definition für eine while-Schleife: while :: Bool -> Int -> Int while test-expr body-expr = if test-expr then body-expr; while test-expr body-expr else 0 • ; ist kein Sequenzoperator, sondern beendet eine Definition • test-expr hat stets den selben Wert (in der FP hat ein Ausdruck genau einen Wert Was ist der Wert von f 42 mystery = False || mystery f x | mystery = x | otherwise = 0 ⊥ 1-7 Themen heute Universelle Polymorphie • Idee: Abstrahiere über Typen • Beispiel: • Polymorphie • Lokale Definitionen fst :: (Int,Bool) -> Int fst (x,y) = x • Funktionen über Listen • Beweise mit struktureller Induktion fst :: (String,Float) -> String fst (x,y) = x • Wiederverwendung für verschiedene Typen möglich Universelle Polymorphie Allgemeinster Typ • Haskell inferiert immer den allgemeinsten Typ • Es gilt: Je mehr Typvariablen desto allgemeiner • Allgemein: fst :: (a,b) -> a , ∀ a ∈ Typen • Beispiel: • a und b sind Typvariablen (a,b) -> a ist allgemeiner als • fst :: (Int,Bool) -> Int ist eine Instanz des generischen Typs fst :: (a,b) -> a (a,a) -> a • Aber: (a,b) -> c ist kein Typ für fst Problem: Falsch abstrahiert 8-11 Unifikation Typinferenz in groben Zügen • Von innen nach außen Versuche allgemeinsten Typ zweier Typen zu bestimmen • Typ der Literale und Primitiva bekannt • Äußerster Typ unterschiedlich: Fehlschlag • Variabeln bekommen zunächst Typ-Variablen • Basistypen gleich: Ok • Unifizieren bei • Typ und Typvariable: Instanziiere Typvariable Fallunterscheidungen: Alle Fälle • Gleiche zusammengesetzte Typen: Unifiziere Elemente Funktionsaufrufen: Parameter(-Pattern) und Argumente Funktionsaufrufen: Rückgabetyp und Aufruf Die polymorphen Typen einiger Funktionen Polymorphe Typsynonyme • curry :: ( (a,b) -> c) -> a -> b -> c • Auch Typsynonyme können Typvariablen enthalten • (.) :: (a -> b) -> (b -> c) -> a -> c • Syntax: type name tvar ... = Typ • three :: a -> Int • Beispiel: type Pair x y = (x,y) • error :: String -> a 12-15 Listenfunktionen (De-)Serialisierung Sind zumeist polymorph - Abstraktion über den Typ der Listenelemente (:) :: a -> [a] -> [a] vorne anhängen (++) :: [a] -> [a] -> [a] zusammenhängen (!!) :: [a] -> Int -> a Indexzugriff concat :: [[a]] -> [a] plätten length :: [a] -> Int Länge take :: Int -> [a] -> [a] ersten n Elemente drop :: Int -> [a] -> [a] ohne ersten n Elemente reverse :: [a] -> [a] umdrehen • show konvertiert einen Wert in einen String • read konvertiert einen String in den Wert, den er repräsentiert show (True) => "True" read "True" => True zip :: [a] -> [b] -> [(a,b)] 2 Listen zu Liste aus Paaren Lokale Definitionen • Bis jetzt: Definitionen nur global • Jetzt: lokale Definitionen mit where Beispiel: Syntax von Deklarationen Decl-> var pat ...{| guard} = expr [ where Decls] Decls-> { Decl1 ; ... ; DeclN } Layout-Regel ersetzt ; und {}: sumSquares :: Int -> Int -> Int sumSquares n m = sqN + sqM where sqN = n * n sqM = m * m • Erstes Zeichen nach where ersetzt { • Erstes Zeichen auf gleicher Höhe ersetzt ; • Erstes Zeichen davor ersetzt } 16-19 Vorteile lokaler Definitionen • Zwischenergebnisse mehrfach verwenden • Lesbarer • Pattern-Matching für Zwischenergebnisse • Lokale Definitionen im gleichen Scope Sichtbarkeitsbereich der lokalen Definitionen Alternativ: let Syntax: let Decls in expr f x = let y = x * x z = x * x * x in (y,z) Vorsicht! Die Bindungen sind sichtbar: • in allen rechten Seiten der Definitionen • im Rumpf Auswertung: Rumpf vereinfachen, dabei lokale Definitionen einsetzen dupAbs x = x + x where x = abs x Terminiert nicht 20-23 Pattern-Matching Konstruktoren für Listen Bis jetzt bei Funktionsaufrufen verwendet Mögliche Pattern: • Literale (:) : a -> [a] -> [a] ist Konstruktor für Liste: • Variablen [] ist eine Liste • Wildcard _: matcht auf alles, bindet nichts x:xs ist eine Liste • Tupel von Pattern • Konstruktor angewendet auf Pattern Konstruktor: Baut Datentyp eindeutig auf Pattern für Listen Pattern-Matching-Ausdruck (:) und [...] sind Pattern für Listen head :: [a] -> a head (x:_) = x tail :: [a] -> [a] tail (_:xs) = xs sumThree :: [Int] -> Int sumThree [x,y,z] = x + y +z Bis jetzt: Pattern-Matching nur bei Funktionsdefinition Als Ausdruck: case exp of { alts } alts -> alt1 ; ... ; altn (n>=1) alt -> pat -> exp [where decls] | pat gdpat [where decls] Haskell-Standard verwendet case im Kern 24-27 Primitive Rekursion über Listen Primitive Rekursion über Listen • Beispiele/Testfälle: • Listen sind induktiv definiert Ein Test für [] • Eine Funktion die Listen konsumiert, ist p.r. wenn sie beschrieben werden kann durch: Den Funktionswert für [] Eine Vorschrift, wie aus dem Funktionswert für xs, der Funktionswert für x:xs berechnet wird Mindestens ein Test für nicht-leere Listen • Schablone: f [] = ... f (x:xs) = ... x ... (f xs) ... x ... • Annehmen, dass f xs Funktionswert bereits berechnet Insertion-Sort Allgemeine Rekursion über Listen iSort :: [Int] -> [Int] iSort [] = [] iSort (x:xs) = ins x (iSort xs) Um f (x:xs) zu berechnen, nimm an, dass f ys Funktion berechnet, ins ins ins | | Schablone: f [] = ... f (x:xs) = ... (f ys) ... :: Int -> x [] x (y:ys) x <= y otherwise [Int] -> [Int] = [x] = x:y:ys = y : ins x ys 28-31 Quicksort Testfälle für [] [] hat den Typ [a] qSort :: [Int] -> [Int] qSort [] = [] qSort (x:xs) = qSort [y | y <-xs, y <= x] ++ [x] ++ qSort [y <- xs, y>x] Im Testfall f [] == [] kann die Überladung von == somit nicht aufgelöst werden. Auswege: • Nur die leere Liste hat Länge 0: length (f []) == 0 • Typsignaturen für Ausdrücke: f ([] :: [Int]) == ([] :: [Int]) Beweise mittels struktureller Induktion Beispielbeweis len [] = 0 len (x:xs) = 1 + len xs z.Z.: len (xs++ys) = len xs + len ys Prinzip der SI über Listen: Um eine Eigenschaft P(xs) für alle endlichen Listen xs zu beweisen: • Beweise Basisfall [] Beweis: Sei ys beliebig aber fest, • Beweise P(x:xs) mit der Annahme P(xs) Da sich alle Listen xs aus [] und (:) konstruieren lassen, läßt sich so für alle Listen xs ein Beweis für P(xs) konstruieren len ([]++ys) Def len Def len = len ys = 0 + len ys = len xs + len ys Assoz : Def len IA = len (x:xs++ys) = 1 + len (xs++ys) = Def len 1+len xs + len ys = len (x:xs) + len ys len ((x:xs)++ys) 32-35