Vorlesung Funktionale Programmierung WS 2008/09 / Folie 711 FP-7.11 Funktional für Tiefensuche in Lösungsbäumen Ziele: • Strom entkoppelt Erzeuger und Verwender der Lösungen Verallgemeinerung der Lösungssuche • Funktional bestimmt die Suchstrategie des Erzeugers in der Vorlesung: • Die Aufgabe wird durch next und pred bestimmt Die drei Abstraktionen werden erläutert: • Erzeuger-Verbraucher: Strom (Teil-)Lösung next • Suchreihenfolge: Funktional erzeugt verfeinerte (Teil-)Lösungen • Anwendungsaufgabe: Parameter 'a -> 'a list © 2004 bei Prof. Dr. Uwe Kastens DFS Tiefensuche: effizient; aber terminiert nicht bei unendlichen Teilbäumen Prädikat pred entscheidet, ob eine Lösung vorliegt: fun depthFirst (next, pred) root = let fun dfs [] = Nil | dfs (x::xs) = if pred x Keller: then Cons (x, fn () =>dfs ((next x) @ xs)) else dfs ((next x) @ xs) in dfs [root] end; Vorlesung Funktionale Programmierung WS 2008/09 / Folie 712 FP-7.12 Funktional für Breitensuche in Lösungsbäumen Ziele: • Strom entkoppelt Erzeuger und Verwender der Lösungen Verallgemeinerung der Lösungssuche • Funktional bestimmt die Suchstrategie des Erzeugers in der Vorlesung: • Die Aufgabe wird durch next und pred bestimmt Die drei Abstraktionen werden erläutert: • Erzeuger-Verbraucher: Strom (Teil-)Lösung next • Suchreihenfolge: Funktional erzeugt verfeinerte (Teil-)Lösungen 'a -> 'a list © 2004 bei Prof. Dr. Uwe Kastens BFS Breitensuche: vollständig; aber speicheraufwendig: fun breadthFirst (next, pred) root = let fun bfs [] = Nil | bfs (x::xs) = if pred x Schlange: then Cons (x, fn () => bfs(xs @ next x)) else bfs (xs @ next x) in bfs [root] end; • Anwendungsaufgabe: Parameter Vorlesung Funktionale Programmierung WS 2008/09 / Folie 801 FP-8.1 8. Lazy Evaluation Ziele: Paradigma lazy: • Eine Berechnung wird erst dann ausgeführt, wenn ihr Ergebnis benötigt wird. Übersicht zum Begriff Lazy in der Vorlesung: • Zusammengesetzte Ergebnisse werden nur so tief wie nötig ausgewertet. Wiederholung zu früheren Folien • Mehrfach benötigte Ergebnisse werden nur einmal berechnet und dann wiederverwendet. Auch als Programmiertechnik in imperativer und objektorientierter Programmierung nützlich! Paradigma eager: • Erst werden alle evtl. benötigten Werte berechnet, dann die Operation darauf ausgeführt. Strikte Auswertung eines Aufrufes: • Wenn ein Parameter bottom liefert (nicht terminiert), dann liefert auch der Aufruf bottom. Parameterübergabe: • eager: call.by-value © 2004 bei Prof. Dr. Uwe Kastens • lazy: call-by- need (entspricht call-by-name plus Wiederverwendung berechneter Werte) Datenstrukturen: • eager: Listen; lazy: Ströme Sprachsemantik: • eager: SML, Lisp; lazy: Haskell, Miranda Vorlesung Funktionale Programmierung WS 2008/09 / Folie 802 FP-8.2 Einführung in Notationen von Haskell Definitionen von Funktionen: add :: Int -> Int -> Int add x y = x + y Ziele: vorangestellte Signatur ist guter Stil, aber nicht obligatorisch sub :: Int -> Int -> Int sub = \x y -> x - y entspricht (secr op+) in SML Lambda-Ausdruck in Haskell Funktionen über Listen: © 2005 bei Prof. Dr. Uwe Kastens lg :: [a] -> Int lg [] = 0 lg (_:xs) = 1 + lg xs in der Vorlesung: An den Beispielen werden die Notationen erläutert. inc1 :: Int -> Int inc1 = add 1 inc2 :: Int -> Int inc2 = (+1) Einfache Haskell-Funktionen lesen können xmap f [] = [] xmap f (x:xs) = (f x) : (xmap f xs) Aufruf z. B.: xmap (+2) [1,2,3] quicksort [] = [] quicksort (x:xs) = quicksort [y | y <- xs, y<x ] ++ [x] ++ quicksort [y | y <- xs, y>=x] Vorlesung Funktionale Programmierung WS 2008/09 / Folie 803 FP-8.3 Lazy-Semantik in Haskell Ziele: Lazy-Semantik anwenden Die Semantik von Haskell ist konsequent lazy, nur elementare Rechenoperationen (+, *, ...) werden strikt ausgewertet. in der Vorlesung: Konsequenzen der Lazy-Semantik werden erläutert. Beispiele: inf = inf ist wohldefiniert; aber die Auswertung würde nicht terminieren. f x y = if x == 0 then True else y Parameterübergabe call-by-need: liefert True f 0 inf terminiert nicht, liefert bottom © 2005 bei Prof. Dr. Uwe Kastens f inf False Vorlesung Funktionale Programmierung WS 2008/09 / Folie 804 FP-8.4 Lazy Listen in Haskell Ziele: Nicht-endlichen Listen verstehen Listen in Haskell haben Lazy-Semantik - wie alle Datentypen. Definition einer nicht-endlichen Liste von 1en: in der Vorlesung: An Beispielen wird erläutert: • Definition nicht-endlicher Listen, ones :: [Int] ones = 1 : ones take 4 ones • Berechnung von nicht-endlichen Listen, liefert [1, 1, 1, 1] Funktionsaufrufe brauchen nicht zu terminieren: numsFrom :: Int -> [Int] numsFrom n = n : numsFrom (n+1) © 2004 bei Prof. Dr. Uwe Kastens take 4 (numsFrom 3)liefert [3, 4, 5, 6] Vorlesung Funktionale Programmierung WS 2008/09 / Folie 805 FP-8.5 Listen als Ströme verwenden Ziele: Listen können unmittelbar wie Ströme verwendet werden: Listen als Ströme verstehen squares :: [Int] squares = map (^2) (numsFrom 0) in der Vorlesung: An Beispielen wird erläutert: liefert [0, 1, 4, 9, 16] take 5 squares • Ströme sind nicht-endliche Listen, • Zusammensetzen von Strömen, Paradigma Konvergenz (vgl. FP-7.7): • spezielle Notationen zur Berechnung von Listen. within :: Float -> [Float] -> Float within eps (x1:(x2:xs)) = if abs(x1-x2)<eps then x2 else within eps (x2:xs) myIterate :: (a->a) -> a -> [a] myIterate f x = x : myIterate f (f x) nextApprox a x = (a / x + x) / 2.0 qroot a = within 1e-8 (myIterate (nextApprox a) 1.0) © 2004 bei Prof. Dr. Uwe Kastens Strom von Fibonacci-Zahlen: fib :: [Int] zip verschränkt zwei Ströme fib = 1 : 1 : [ a+b | (a,b) <- zip fib (tail fib) ] fibs :: [Int] zipWith verknüpft die Elemente zweier Ströme fibs = 1 : 1 : (zipWith (+) fibs (tail fibs)) Vorlesung Funktionale Programmierung WS 2008/09 / Folie 806 FP-8.6 Simulation zyklischer Datenflüsse Ziele: Beispiel für Simulation verstehen requests server client responses in der Vorlesung: Am Beispiel wird erläutert: • suggestive Komposition auch zyklischer Ströme, reqs resps = client csInit resps = server reqs server :: [Int] -> [Int] server (req:reqs) = process req : server reqs client :: Int -> [Int] -> [Int] -- client init (resp:resps) = init : client (next resp) resps -- Fehler: das zweite Pattern wird zu früh ausgewertet © 2004 bei Prof. Dr. Uwe Kastens -- client init resps = init : client (next (head resps)) (tail resps) -- funktioniert: Das zweite Pattern wird erst bei Benutzung ausgewertet client init ~(resp:resps) = init : client (next resp) resps -- Das zweite Pattern wird erst bei Benutzung ausgewertet csInit next resp process req = 0 = resp = req+1 • lazy Bindung von Pattern, • freie Variable mit Vorwärtsreferenzen: next, process