Funktionale Programmierung Das Funktionale Quiz Worin besteht der Unterschied zwischen Lazy-Evaluation und gleichungsbasiertem Auswerten mittels Vereinfachen /Ersetzen? 7.6.2005 Lazy-Evaluation ist eine Strategie, die genau festlegt wo als nächstes vereinfacht wird Das Funktionale Quiz Warum ist beim gleichungsbasierten Auswerten vorgegeben, dass das Pattern-Matching von oben nach unten eine Gleichung aussucht? Weil mehrere passen könnten, die rechten Seiten aber unterschiedliche Werte liefern Themen heute • Vorteile von Listen unendlicher Länge • I/O in Haskell 1-6 Vorteile unendlicher Listen: Abstraktion Programme können abstrakter werden, weil keine Listenlänge festgelegt werden muss • Bei sieve musste nicht festgelegt werden, wie viele Primzahlen benötigt werden • Ebenso randomSequence • Gleiches Argument wie bei Virtual Memory: Abschätzung des Bedarfs ist möglich, aber mühsam Laufende Summen Vorteile unendlicher Listen: Modularisierung Modularisierung durch Trennung von Erzeugung und Transformation • Bei der Zufallszahlenfolge wird erst die Liste erzeugt, dann skaliert • Teile können einzeln ausgewechselt werden • Prozessnetzwerke: Unendliche Listen sind Verknüpfungen zwischen Komponenten Prozessnetzwerk für listSums listSums :: [Int] -> [Int] listSums iList = out where out = 0 : zipWith (+) iList out zipWith :: (a -> b -> c) -> [a] -> [b] -> [c] zipWith f (x:xs) (y:ys) = f x y : zipWith f xs ys zipWith f _ _ = [] zipWith (+) [2,4 ..] [3,5 ..] => [5,9,13,...] listSums [1 ..] = out = 0 : zipWith (+) [1 ..] out = 0 : zipWith (+) [1 ..] (0:...) = 0 : 1+0: zipWith (+) [2 ..] (1+0:...) = 0 : 1: 2+1 : zipWith (+) [3 ..] (2+1:...) ... 7-10 scanl' Beweise für unendliche Listen Allgemein: Liste aus anderer Liste mit • Startwert und • Funktion von Listenelement und vorherigem Funktionswert scanl1' :: (a -> b -> b) -> b -> [a] -> [b] scanl1' f st iList = out where out = st : zipWith f iList out listSums = scanl1' (+) 0 • Bis jetzt: Beweise für endliche Listen mit definierten Elementen • Müssen aber berücksichtigen, dass Listen aus ⊥ aufgebaut sind oder ⊥ als Elemente enthalten undef = undef list1 = 2:3:undef list2 = 1:undef:[4,8] Funktionswerte einer primitiv rekursiven Funktion über Zahlen: scanl1' f s [1 ..] wobei s der Wert für Null und f die Vorschrift für die Rekursion ist Begriffsdefinitionen Beweise für endliche oder partielle Listen • Definierter Wert: Wert der nicht ⊥ ist • Endliche Liste: Haben eine definierte, endliche Länge • Definierte Listen: Endliche Liste aus definierten Werten • Partielle Liste: Aus ⊥ aufgebaut, kann ⊥ enthalten Um P(xs) für endliche oder partielle Liste xs zu beweisen: • Induktionsanfang: Beweise P( []) und P( ⊥) • Induktionsschritt: Beweise P( x:xs) unter der Annahme, dass P( xs) gilt und berücksichtige x = ⊥ • Unendliche Liste: ∀ n: take n xs ≠take (n+1) xs 11-14 Beispielbeweis Beweise für Listen unendlicher Länge Z.Z.: sum (doubleAll xs) = 2 * sum xs IA: xs = [] ok. IA: xs = ⊥: sum (doubleAll ⊥) = sum ⊥ = ⊥ = 2 * ⊥ = 2 * sum⊥ Idee: Die Folge partieller Listen ⊥, a0:⊥, a0:a1:⊥, a0:a1:s2⊥ nähert die unendliche Liste [a0,a1,a2,...] an Damit Gleichheit unendlicher Listen: IS: xs = x:_ 2 unendliche Listen xs und ys sind gleich g.d.w. ∀ n: xs!!n = ys!!n x ≠ ⊥: okay x = ⊥, linke Seite: sum (2*⊥:_) = ⊥ + _ = ⊥ x = ⊥, rechte Seite: 2 * (⊥ + _ = ⊥ Beispielbeweis Beweis Induktion über n facMap = map fac [0 ..] facs = 1:zipWith (*) [1 ..] facs Z.Z.: facMap !! n = facs !! n Hilfslemmas: • L1: (map f xs) !! n = f (xs !! n) • L2: (zipWith g xs ys) !! n = g (xs !! n) (ys !! n) IA, linke Seite: facMap!!0 = (map fac [0 ..]) !! 0 = fac ([0 ..]!!0) = fac 0 = 1 IA, rechte Seite: facs !! 0 = (1:zipWith ...)!! 0 = 1 IS, linke Seite: facMap!!n = (map fac [0 ..])!!n = fac ([0 ..]!!n) = fac n = n*fac(n-1) = n*(facMap!!(n-1) IS, rechte Seite: facs!!n = 1:zipWith (*) [1 ..] facs)!!n = zipWith (*) [1 ..] facs)!!(n-1) = (*) ([1 ..]!!(n-1)) (facs!!(n-1)) = ([1 ..]!!(n-1)) * (facs!!(n-1)) = n * (facs!!(n-1)) = n*(facMap!!(n-1)) 15-18 Zusammenfassung: Lazy-Evaluation Programmieren mit Aktionen Aktion: Interaktion mit dem Rest der Welt • Auswertungsstrategie für Haskell • I/O in Haskell • Ermöglicht neue Programmiertechniken • Monaden • Ermöglicht unendlich große Datenstrukturen • Zustand, Nicht-Determinismus • Roboter-Steuerung Schwierigkeiten mit I/O in Haskell Angenommen, es gäbe inputInt :: Int, liest Zahl ein Betrachte: inputDiff = inputInt - inputInt Lösung in Haskell • "Monadischer Ansatz" • Struktur von Programmen mit I/O einschränken • Reihenfolge in der - die Argumente auswertet würde den Wert von inputDiff verändern • Beobachtung dazu: I/O passiert immer hintereinander • Beweise scheitern: x - x ≠ 0! • Abstrakter Typ IO a enspricht den I/O-Aktionen die Werte vom Typ a liefern Grund: Ausdruck steht nicht mehr für genau einen Wert Pflanzt sich fort: funny n = n + inputDiff Referentielle Transparenz (Funktionswert hängt nur vom Parameter ab) geht also verloren • IO stellt Sequenzierung sicher • Nur der Interpreter kann Werte vom Typ IO a "laufen lassen" • Programme mit I/O sind daher immer vom Typ IO a 19-22 Intuition Einschub: Der Typ () Aktionen geben manchmal keinen sinnvollen Wert zurück • IO ist "die Welt", die durch das Programm gereicht wird • IO speichert, was mit der Welt geschieht • IO stellt eine Programmiersprache oberhalb von Haskell dar, ohne das funktionale Modell zu gefährden Dann: • Eingebauter Typ () • Einziger Wert: () • Entspricht void in C,++, Java I/O-Primitiva Hello World! Eingabe: • getLine :: IO String liefert Eingabe als String • getChar :: IO Char liefert Zeichen aus der Eingabe • IO.isEOF :: IO Bool Test auf Dateiende Ausgabe: • putStr,putStrLn :: String -> IO () gibt String aus • print :: Show a => a -> IO () gibt externe Repräsentation aus helloWorld :: IO () helloWorld = putStrLn "Hello World!" • Interpreter startet IO: Main> helloWorld Hello World! Main> • Eintrittspunkt für Haskell-Programme: main :: IO a Beliebiger Wert als I/O-Aktion: • return :: a -> IO a macht kein I/O, liefert aber I/O-Aktion mit Wert vom Typ a 23-26 Die do-Notation Syntax der do-Notation exp stmts stmt Sinn: • Sequenzierung von I/O-Programmen • Bindung der Werte von I/O-Aktionen reverseLine :: IO () reverseLine = do line <- getLine putStrLn (reverse line) -> -> -> | | | do { stmts } stmt1 ... stmtn exp [;] exp; pat <- exp; let decls; ; • exp müssen vom Typ IO a sein • exp; wirft Ergebnis weg • pat <- exp; bindet Ergebnis in den restlichen Statements • let decls; bindet Variablen in den restlichen Statements Beispiele: do-Notation putStrLn :: String -> IO () putStrLn str = do putStr str putStr "\n" while-Schleifen • Test hängt von I/O ab (sonst ändert er sich ja nicht) • Also Typ: IO Bool putNtimes :: Int -> String -> IO () putNtimes n str = if n <= 1 then putStrLn str else do putStrLn str putNtimes (n - 1) str getInt :: IO Int getInt = do line <- getLine let int = read line :: Int return int • Typ des Rumpfs: IO () while :: IO Bool -> IO () -> IO () while test action = do res <- test if res then do action while test action else return () 27-30 Beispiel mit while Kopiere Eingabe auf Ausgabe copyInputToOutput :: IO () copyInputToOutput = while (do eof <- isEOF return (not eof)) (do line <- getLine putStrLn line) 31