Eine Folie pro Seite

Werbung
Funktionale Programmierung
7.6.2005
1
Das Funktionale Quiz
Worin besteht der Unterschied zwischen Lazy-Evaluation und
gleichungsbasiertem Auswerten mittels Vereinfachen /Ersetzen?
Lazy-Evaluation ist eine Strategie, die genau festlegt wo als nächstes
vereinfacht wird
2-3
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
4-5
Themen heute
• Vorteile von Listen unendlicher Länge
• I/O in Haskell
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
7
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
8
Laufende Summen
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:...)
...
9
Prozessnetzwerk für listSums
10
scanl'
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
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
11
Beweise für unendliche Listen
• 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]
12
Begriffsdefinitionen
• 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
• Unendliche Liste: ∀ n: take n xs ≠take (n+1) xs
13
Beweise für endliche oder partielle Listen
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 = ⊥
14
Beispielbeweis
Z.Z.: sum (doubleAll xs) = 2 * sum xs
IA: xs = [] ok.
IA: xs = ⊥: sum (doubleAll ⊥) = sum ⊥ = ⊥ = 2 * ⊥ =
2 * sum⊥
IS: xs = x:_
x ≠ ⊥: okay
x = ⊥, linke Seite: sum (2*⊥:_) = ⊥ + _ = ⊥
x = ⊥, rechte Seite: 2 * (⊥ + _ = ⊥
15
Beweise für Listen unendlicher Länge
Idee: Die Folge partieller Listen ⊥, a0:⊥, a0:a1:⊥, a0:a1:s2⊥
nähert die unendliche Liste [a0,a1,a2,...] an
Damit Gleichheit unendlicher Listen:
2 unendliche Listen xs und ys sind gleich g.d.w. ∀
n: xs!!n = ys!!n
16
Beispielbeweis
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)
17
Beweis
Induktion über 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))
18
Zusammenfassung: Lazy-Evaluation
• Auswertungsstrategie für Haskell
• Ermöglicht neue Programmiertechniken
• Ermöglicht unendlich große Datenstrukturen
19
Programmieren mit Aktionen
Aktion: Interaktion mit dem Rest der Welt
• I/O in Haskell
• Monaden
• Zustand, Nicht-Determinismus
• Roboter-Steuerung
20
Schwierigkeiten mit I/O in Haskell
Angenommen, es gäbe inputInt :: Int, liest Zahl ein
Betrachte: inputDiff = inputInt - inputInt
• Reihenfolge in der - die Argumente auswertet würde den Wert von
inputDiff verändern
• Beweise scheitern: x - x ≠ 0!
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
21
Lösung in Haskell
• "Monadischer Ansatz"
• Struktur von Programmen mit I/O einschränken
• Beobachtung dazu: I/O passiert immer hintereinander
• Abstrakter Typ IO a enspricht den I/O-Aktionen die Werte vom
Typ a liefern
• 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
22
Intuition
• 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
23
Einschub: Der Typ ()
Aktionen geben manchmal keinen sinnvollen Wert zurück
Dann:
• Eingebauter Typ ()
• Einziger Wert: ()
• Entspricht void in C,++, Java
24
I/O-Primitiva
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
Beliebiger Wert als I/O-Aktion:
• return :: a -> IO a macht kein I/O, liefert aber I/O-Aktion mit
Wert vom Typ a
25
Hello World!
helloWorld :: IO ()
helloWorld = putStrLn "Hello World!"
• Interpreter startet IO:
Main> helloWorld
Hello World!
Main>
• Eintrittspunkt für Haskell-Programme: main :: IO a
26
Die do-Notation
Sinn:
• Sequenzierung von I/O-Programmen
• Bindung der Werte von I/O-Aktionen
reverseLine :: IO ()
reverseLine =
do line <- getLine
putStrLn (reverse line)
27
Syntax der do-Notation
exp
stmts
stmt
->
->
->
|
|
|
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
28
Beispiele: do-Notation
putStrLn :: String -> IO ()
putStrLn str = do putStr str
putStr "\n"
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
29
while-Schleifen
• Test hängt von I/O ab (sonst ändert er sich ja nicht)
• Also Typ: IO Bool
• 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 ()
30
Beispiel mit while
Kopiere Eingabe auf Ausgabe
copyInputToOutput :: IO ()
copyInputToOutput =
while (do eof <- isEOF
return (not eof))
(do line <- getLine
putStrLn line)
31
Herunterladen