Was bisher geschah I Deklarative vs. imperative Programmierung I Deklarative Programmierung: FP, LP, FLP, CLP Haskell: I Algebraische Datentypen I Pattern Matching I Polymorphie I Rekursive Datentypen (Peano-Zahlen, Listen, binäre Bäume) I Rekursive Funktionen I strukturelle Induktion I Funktionen höherer Ordnung prominente Beispiele: filter, map, fold (über Peano-Zahlen, Listen, binäre Bäume) 59 Wiederholung Typen Beispiele: I last :: [a] -> a Typ von [ 3, 5 .. 10 ] ist [Int] angewendete Instanz der Funktion last :: [Int] -> Int , der Typ von last [ 3, 5 .. 10 ] ist also Int I take :: Int -> [a] -> [a] Typ von take 1 ? Typ von take 1 [ "foo", "bar" ] ? 60 Polymorphie nicht polymorphe Typen: tatsächlicher Argumenttyp muss mit deklariertem Argumenttyp übereinstimmen: Wenn f :: A -> B und e :: A, dann ( f e ) :: B. polymorphe Typen: Typ von f :: A -> B und Typ von x :: A’ können Typvariablen enthalten. A und A’ müssen unfizierbar (eine gemeinsame Instanz besitzen) aber nicht notwendig gleich sein. σ = mgu(A, A0 ) (allgemeinster Unifikator) allgemeinster Typ von ( f x ) ist dann σ(B) Typ von x wird dadurch spezialisiert auf σ(A0 ) 61 Eingeschänkte Polymorphie reverse [1,2,3,4] = [4,3,2,1] reverse "foobar" = "raboof" reverse :: [a] -> [a] reverse ist polymorph sort [5,1,4,3] = [1,3,4,5] sort "foobar" = "abfoor" sort :: [a] -> [a] -- ?? sort [sin,cos,log] = ?? sort ist eingeschränkt polymorph 62 Motivation sort enthält: let ( low, high ) = partition ( < ) xs in ... Für alle Typen a, die für die es eine Vergleichs-Funktion compare gibt, hat sort den Typ [a] -> [a]. sort :: Ord a => [a] -> [a] Ord ist eine Typklasse, definiert durch class Ord a where compare :: a -> a -> Ordering data Ordering = LT | EQ | GT 63 Instanzen Typen können Instanzen von Typklassen sein. (analog in OO: Klassen implementieren Interfaces) Für vordefinierte Typen sind auch die meisten sinnvollen Instanzen vordefiniert instance Ord Int ; instance Ord Char ; ... weitere Instanzen kann man selbst deklarieren: data Student = Student { vorname :: String , nachname :: String , matrikel :: Int } instance Ord Student where compare s t = compare (matrikel s) (matrikel t) 64 Typen und Typklassen In Haskell sind unabhängig: 1. Deklaration einer Typklasse (= Deklaration von abstrakten Methoden) class C where { m :: ... } 2. Deklaration eines Typs (= Sammlung von Konstruktoren und konkreten Methoden) data T = ... 3. Instanz-Deklaration (= Implementierung der abstrakten Methoden) instance C T where { m = ... } In Java sind 2 und 3 nur gemeinsam möglich class T implements C { ... } 65 Typen mit Gleichheit class Eq a where (==) :: a -> a -> Bool (/=) :: a -> a -> Bool Beispiele: I (’a’ == ’b’) = False I (True /= False) = True I ("ab" /= "ac") = True I ([1,2] == [1,2,3]) = False I (\ x -> 2 * x) == (\ x -> x + x) = ? 66 Typen mit totaler Ordnung Instanzen der Typklasse Eq mit data Ordering = LT | EQ | GT class Eq a => Ord a where compare :: a -> a -> Ordering (<) :: a -> a -> Bool (<=) :: a -> a -> Bool (>) :: a -> a -> Bool (>=) :: a -> a -> Bool min :: a -> a -> a max :: a -> a -> a Beispiele: I (’a’ < ’b’) = True I (False < True) = True I ("ab" < "ac") = True (lexikographisch) I ([1,2] > [1,2,3]) = False 67 Klassen-Hierarchien Typklassen können in Beziehung stehen. Ord ist „abgeleitet“ von Eq: class Eq a where (==) :: a -> a -> Bool class Eq a => Ord a where (<) :: a -> a -> Bool Ord ist Typklasse mit Typconstraint (Eq) also muß man erst die Eq-Instanz deklarieren, dann die Ord-Instanz. Jedes Ord-Wörterbuch hat ein Eq-Wörterbuch. 68 Instanzen data Bool = False | True instance Ord False == True == _ == Bool where False = True True = True _ = False instance Eq Bool where False < True = True _ < _ = False x <= y = ( x < y ) || ( x == y ) x > y = y < x x >= y = y <= x 69 Typen mit Operation zum (zeilenweisen) Anzeigen class Show a where show :: a -> String Beispiele: I show 123 = "123" I show True = "True" I show [1,2] = "[1,2]" I show (1,’a’,True) = "show (1,’a’,True)" Instanzen Bool, Char, Int, Integer, Float, Listen und Tupel von Instanzen 70 Typklasse Show Die Interpreter Ghci / Hugs geben bei Eingabe exp (normalerweise) show exp aus. Man sollte (u. a. deswegen) für jeden selbst deklarierten Datentyp eine Show-Instanz schreiben. . . . oder schreiben lassen: deriving Show 71 Typen mit Operation zum Lesen class Read a where read :: String -> a Beispiele: I ( read "3" :: Int ) = 3 I ( read "3" :: Float ) = 3.0 I ( read "False" :: Bool ) = False I ( read "’a’" :: Char ) = ’a’ I ( read "[1,2,3]" :: [Int] ) = [1,2,3] Instanzen Bool, Char, Int, Integer, Float, Listen und Tupel von Instanzen 72 Numerische Typen class (Eq a, Show a) => Num a where (+) :: a -> a -> a (-) :: a -> a -> a (*) :: a -> a -> a negate :: a -> a abs :: a -> a signum :: a -> a Beispiele: I signum (-3) = -1 I signum (-3.3) = -1.0 Instanzen Int, Integer, Float 73 Numerische Typen mit Division Ganzzahl-Division: class Num a => Integral a where div :: a -> a -> a mod :: a -> a -> a Instanzen Int, Integer Beispiel: 3 ‘div‘ 2 = 1 Division: class Num a => Fractional a where (/) :: a -> a -> a recip :: a -> a -> a Instanz Float Beispiel: 3 / 2 = 0.6 74 Generische Instanzen class Eq a where (==) :: a -> a -> Bool Vergleichen von Listen (elementweise) wenn a in Eq, dann [a] in Eq: instance Eq a => Eq [a] where [] == [] = True (x : xs) == (y : ys) = (x == y) && ( xs == ys ) _ == _ = False 75 Abgeleitete Instanzen Deklaration eigener Typen als Instanzen von Standardklassen durch automatische Erzeugung der benötigten Methoden: Beispiele: data Bool = False | True deriving (Eq, Ord, Show, Read) data Shape = Circle Float | Rect Float Float deriving (Eq, Ord, Show, Read) Beispiel: (Circle 3 < Rect 1 2) == True data (Eq a) => Maybe a = Nothing | Just a deriving (Eq, Ord, Show, Read) Beispiel: (Just ’a’ == Just ’b’) == False 76 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) 77 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 ) 78 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 79 Termination inf :: Int inf = 1 + inf Auswertung von fst (0, inf) terminiert unter outermost-Strategie, aber nicht unter innermost-Strategie Fakt Für jeden Ausdruck, für den die Auswertung unter irgendeiner Strategie terminiert, terminert auch die Auswertung unter outermost-Strategie. 80 Sharing von Teilausdrücken 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 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 81 Lazyness jeder Wert wird erst bei Bedarf ausgewertet. Listen sind Streams, der Tail wird erst bei Bedarf ausgewertet. Wann die Auswertung stattfindet, lässt nicht beobachten. Die Auswertung hat keine Nebenwirkungen. unendliche Datenstruktur: naturals :: [ Integer ] naturals = from 0 where from x = x : from (x+1) Liste aller Quadratzahlen? Primzahlen? 82 Unendliche Datenstrukturen inf :: Int inf = 1 + inf fst(0, inf) einsen :: [Int] einsen = 1 : einsen head einsen take 3 einsen nats :: [Int] nats = 0 : map (+1) nats takeWhile (<= 5) nats 83 Motivation: Datenströme Folge von Daten: I I I erzeugen (producer) transformieren verarbeiten (consumer) aus softwaretechnischen Gründen diese drei Aspekte im Programmtext trennen, aus Effizienzgründen in der Ausführung verschränken (bedarfsgesteuerter Transformation/Erzeugung) 84 Rekursive Stream-Definitionen 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 ) ) 85 Primzahlen Sieb des Eratosthenes alle_ab :: Int -> [ Int ] alle_ab n = n : alle_ab ( n+1 ) primzahlen :: [ Int ] primzahlen = sieb $ alle_ab 2 sieb :: [ Int ] -> [ Int ] sieb (x : xs) = x : ... take 100 primzahlen takeWhile (< 100) primzahlen 86 Strictness Berechnung eines Funktions-Argumentes vor Anwendung der Funktion mitunter auch in Haskell sinnvoll (z.B. Zeit- und Platzbeschränkung der Berechnung) Syntax: f $! x berechnet denselben Wert wie fx, abe in anderer Auswertungsreihenfolge: f $! x wird erst dann ein Redex (und reduziert zu fx), sobald nach (lazy) Auswertung x kein undefinierter Wert mehr ist. in Haskell: I Konstruktoren (Cons, . . . ) sind nicht strikt, I Destruktoren (head, tail, . . . ) sind strikt. für Funktionen mit mehreren Argumenten: Striktheit in jedem Argument einzeln. 87 Strikte Auswertung Bei bekannter Striktheit kann der Compiler effizienteren Code erzeugen (frühe Argumentauswertung) z.B. Listen-Summe mit Akkumulator: sum_n :: Int -> [Int] -> Int sum_n n [] = n sum_n n ( x : xs ) = sum_n (n + x) xs sum_n 0 [ 1,2,3] sum_n 0 [ 1 .. 100000] strikte Auswertung eines Teiltermes erzwingen: sum_n n ( x : xs ) = (sum_n $!(n + x)) xs strikte Version von foldl sfoldl :: (a -> b -> a) -> a -> [b] -> a sfoldl f x [] = x sfoldl f x (y : ys) = ((sfoldl f) $! (f x y)) ys 88