Funktionale Programmierung 31.5.2005 1 Das Funktionale Quiz Nenne eine Gemeinsamkeit zwischen Typklassen und OO-Klassen 2-3 Das Funktionale Quiz Nenne einen Unterschied zwischen Typklassen und OO-Klassen 4-5 Das Funktionale Quiz Ist es möglich, ein Haskell-Programm mit Modulen in ein Haskell-Programm ohne Module umzuformen? Ja, mittels Umbenennung 6-7 Themen heute • Lazy Evaluation • Parserkombinatoren • Listen unendlicher Länge 8 Lazy Programming Lazy Evaluation: Auswertungsstrategie für Haskell Darauf aufbauend: Techniken zur Programmierung 9 Auswertungsstrategie • Bis jetzt: Wert eines Ausdrucks durch Vereinfachen und Ersetzen mittels Gleichungen • Reihenfolge bis jetzt nicht festgelegt • Für Implementierung aber notwendig (Strategie) • Wünschenswert: Finde Normalform, wenn sie existiert 10 Beispiel trues = True:trues take 0 l = [] take n (x:xs) = x:(take (n-1) xs) take 2 trues ==> [True,True] Reduktionsreihenfolge die Normalform findet: take 2 trues = take 2 True:trues = True:take (2-1) trues = True:take 1 True:trues = True:True:take (1-1) trues = True:True:take 0 trues = True:True:[] 11 Lazy Evaluation Zugrundeliegende Idee: • Werte Argumente nicht aus • Beim Pattern-Matching Parameter nur so weit auswerten, bis Muster passt/nicht passt Findet Normalform, wenn sie existiert 12 Gefahr • Ausdrücke werden eventuell mehrfach ausgewertet double x = x + x double (9 - 3) = (9 - 3) + (9 - 3) = 6 + (9 -3) = 6 + 6 = 12 • Für das Ergebnis egal, in der Praxis untragbar • Lösung: Beim Ersetzen der Parameter anstatt der Argumente nur Zeiger auf Argumente einsetzen. 13 Repräsentation als Graph • Mit Zeigern werden Ausdrücke nicht mehr als Baum (Terme) repräsentiert, sondern als Graph • Zeiger werden automatisch dereferenziert add3 x = x + 3 add3 5 = ⋅ + 3 = 8 5 • Wenn Zeiger vereinfacht werden muss, Ziel vereinfachen: add3 (7 - 6) = ⋅ + 3 (7 - 6) = ⋅ + 3 = 4 1 14 Keine doppelte Auswertung • Somit wird jedes Argument nur einmal ausgewertet: double (9 - 3) = ⋅ + ⋅ (9 - 3) = ⋅ + ⋅ = 12 6 15 Beschreibung von Lazy-Evaluation Nach Art des Ausdrucks: 1. Funktionsanwendung 2. Operatoren und sonstige Syntax 3. Geschachtelte Funktionsanwendung 16 Funktionsanwendung Betrachte allgemeinste Funktionsdefinition: f p1 ... pK | g1 = e1 ... | otherwise = eO where v1 a1,1 ... = r1 ... f q1 ... qK ... ... -- oder v1 = r1 Auswertung von Funktionsanwendung f a1 ... aK 17 Drei Aspekte der Funktionsanwendung Schritte zum Funktionswert: 1.1. Gleichung auswählen über Pattern-Matching 1.2. Zweig auswählen mittels Guards 1.3. Lokale Definitionen berücksichtigen 18 1.1 Pattern-Matching Ziel: Gleichung auswählen Vorgehen: • Argumente a1 ... aK jeweils so weit auswerten, bis klar ist, ob sie auf p1 ... pK passen • Falls ja, erste Gleichung auswerten • Andernfalls mit zweiter Gleichung fortfahren 19 Beispiel: Pattern-Matching f :: [Int] -> [Int] -> Int f [] ys = 0 f (x:xs) [] = 0 f (x:xs) (y:ys) = x + y Auswertung von f [1 .. 3] [5 .. 9]: f (1:[1+1 .. 3]) [5 .. 9] -- 1. Gl. passt nicht f (1:[1+1 .. 3]) (5:[5+1 .. 9]) -- 2. Gl. passt nicht -- 3. Gl. passt 20 1.2. Guards Ziel: Zweig einer Gleichung auswählen Vorgehen: • Parameter durch Zeiger auf Argumente ersetzen • Guards von oben nach unten auswerten, bis erster True ergibt • Rechte Seite (mit lokalen Definitionen) auswerten 21 Beispiel: Guards max | | | max a b c a >= b b >= c True = (51-2) && a >= c = a = b c (5*7) 23 | ⋅>=⋅&&⋅>=⋅=⋅ | ⋅>=⋅=⋅ | True = ⋅ 51-2 5*7 23 | ⋅>=⋅&&⋅>=⋅=⋅ | ⋅>=⋅=⋅ | True = ⋅ 49 5*7 23 | ⋅>=⋅&&⋅>=⋅=⋅ | ⋅>=⋅=⋅ | True = ⋅ 49 35 23 22 Beispiel Guards | True&&⋅>=⋅=⋅ | ⋅>=⋅=⋅ | True = ⋅ 49 35 23 | True && True =⋅ | ⋅>=⋅=⋅ | True = ⋅ 49 35 23 | True =⋅ | ⋅>=⋅=⋅ | True = ⋅ 49 35 23 ⋅ 49 23 1.3. Lokale Definitionen Ziel: Ausdruck mit lokalen Definitionen auswerten Vorgehen: • Ausdruck auswerten • Rechte Seite einer Definition auswerten, wenn Variable benötigt wird • Lokale Definitionen bleiben also erhalten - Graph mittels Namen 24 Beispiel: Lokale Definitionen double x = xx where xx = x + x double 5 Auswertung: xx where {xx = ⋅ + ⋅} 5 xx where {xx = 10} 10 25 2. Operatoren und sonstige Syntax • Argumente der Operatoren auswerten je nach Operator: in Haskell definierbar ( &&...) oder strikt (z.B. arithmetische Operatoren) oder so lazy wie möglich ( ==, :, ...) • Lambda-Ausdrücke wie Funktionen mit Namen • case wie Pattern-Matching oben 26 3. Geschachtelte Funktionsaufrufe Vorgehen: • Von außen nach innen f e1 (g e2) wertet f 1 (g e2) aus • Von rechts nach links f e1 + g e2 wertet erst f e1 dann g e2 aus 27 Parser-Kombinatoren mittels Lazy-Evaluation Kombinator: Geschlossener Ausdruck Bezug zur Lazy-Evaluation: • Grammatiken enthalten oft Alternativen und Sequenzen. • Mit Lazy-Evaluation stoppt der Parser, sobald feststeht, dass eine Alternative erfüllt ist oder das erste Element einer Folge nicht passt. • "Backtracking" 28 Parser type Parse a b = [a] -> [(b, [a])] Dabei: • [a]: Eingabe • (b,[a]): Ergebnis und restliche Eingabe • [(b,[a])]: Liste verschiedener Möglichkeiten, leer wenn Parser gescheitert ist 29 Einfache Parser • Scheitert immer none :: Parser a b none inp = [] • Liest nichts, liefert Argument succeed :: b -> Parser a b succeed val inp = [(val,inp)] • Liest genau t token :: Eq a => a -> Parse a a token t (x:xs) | t==x = [(t,xs)] | otherwise = [] token t [] = [] 30 Einfache Parser • Liest Zeichen, das Präsikat erfüllt spot :: (a -> Bool) -> Parse a a spot p (x:xs) | p x = [(x,xs)] | otherwise = [] spot p [] = [] • Alternative alt :: Parse a b -> Parse a b -> Parse a b alt p1 p2 inp = p1 inp ++ p2 inp • Sequenz zweier Parser (>*>) :: Parse a b -> Parse a c -> Parse a (b,c) (>*>) p1 p2 inp = [((y,z),rem2) | (y,rem1) <- p1 inp , (z,rem2) <- p2 rem1 ] 31 Einfache Parser • Wandelt Ergebnis um build :: Parse a b -> (b -> c) -> Parse a c build p f inp = [ (f x,rem) | (x,rem) <- p inp ] • Sequenz einer Liste list :: Parse a b -> Parse a [b] list p = (succeed []) `alt` ((p >*> list p) `build` convert) where convert = uncurry (:) 32 Einstiegspunkt in den Parser Parser anwenden, erste Möglichkeit zurückgeben topLevel :: Parse a b -> [a] -> b topLevel p inp = case results of [] -> error "parse unsuccessful" _ -> head results where results = [ found | (found,[]) <- p inp ] 33 Listen unendlicher Länge Lazy Evaluation macht Beschreibung unendlicher Datenstrukturen möglich Zwei Aspekte: • Erzeugen ones :: [Int] ones = 1:ones • Konsumieren addFirstTwo :: [Int] -> Int addFirstTwo (x:y:xs) = x + y addFirstTwo ones = 2 34 Erweiterung der Listennotation Schlusselement weglassen ergibt unendlich lange Liste • Liste aller Zahlen ab 3: [3 ..] • Liste aller ungeraden Zahlen ab 3: [3,5 ..] Pythagoreische Tripel pythagTriples = [ (x,y,z) | z <- [2 ..], y <- [2 .. z-1], x <- [2 .. y-1], x*x + y*y == z*z] 35 Liste aller Primzahlen Idee: Streiche aus der Liste aller Zahlen die Vielfachen der bereits bekannten Primzahlen heraus primes = sieve [2 ..] sieve (x:xs) = x : sieve [ y | y <- xs, y `mod` x > 0] Die ersten 6 Primzahlen: take 6 primes ==> [2,3,5,7,11,13] 36 Folge von Pseudo-Zufallszahlen Lineare Kongruenzmethode: Lineare Funktion anwegendet auf Vorgänger liefert neuen Wert nextRand :: Int -> Int nextRand n = (multiplier*n + increment) `mod` modulus randomSequenze :: Int -> [Int] randomSequenze = iterate nextRand iterate :: (a -> a) -> a -> [a] iterate f x = x : iterate f (f x) 37 Folge von Pseudo-Zufallszahlen seed = 17489 multiplier = 25173 increment = 13849 modulus = 65536 randomSequenze seed = [17489,59134,9327,52468,... Auf bestimmten Bereich skalieren: scaleSequence :: Int -> Int -> [Int] -> [Int] scaleSequence s t = map scale where scale n = n 'div' denom + s range = t-s+1 denom = modulus 'div' range scaleSequence 40 50 (randomSequenze seed) = [42,49,41, 38