Vier Folien pro Seite

Werbung
Funktionale Programmierung
31.5.2005
Das Funktionale Quiz
Nenne einen Unterschied zwischen Typklassen und OO-Klassen
Das Funktionale Quiz
Nenne eine Gemeinsamkeit zwischen Typklassen und OO-Klassen
Das Funktionale Quiz
Ist es möglich, ein Haskell-Programm mit Modulen in ein
Haskell-Programm ohne Module umzuformen?
Ja, mittels Umbenennung
1-7
Themen heute
Lazy Programming
• Lazy Evaluation
Lazy Evaluation: Auswertungsstrategie für Haskell
• Parserkombinatoren
Darauf aufbauend: Techniken zur Programmierung
• Listen unendlicher Länge
Auswertungsstrategie
• Bis jetzt: Wert eines Ausdrucks durch Vereinfachen und Ersetzen
mittels Gleichungen
Beispiel
trues = True:trues
take 0 l
= []
take n (x:xs) = x:(take (n-1) xs)
take 2 trues
==> [True,True]
• Reihenfolge bis jetzt nicht festgelegt
Reduktionsreihenfolge die Normalform findet:
• Für Implementierung aber notwendig (Strategie)
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:[]
• Wünschenswert: Finde Normalform, wenn sie existiert
8-11
Lazy Evaluation
Gefahr
• Ausdrücke werden eventuell mehrfach ausgewertet
Zugrundeliegende Idee:
double x = x + x
• Werte Argumente nicht aus
• Beim Pattern-Matching Parameter nur so weit auswerten, bis
Muster passt/nicht passt
Findet Normalform, wenn sie existiert
Repräsentation als Graph
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.
Keine doppelte Auswertung
• Mit Zeigern werden Ausdrücke nicht mehr als Baum (Terme)
repräsentiert, sondern als Graph
• Zeiger werden automatisch dereferenziert
add3 x = x + 3
• Somit wird jedes Argument nur einmal ausgewertet:
add3 5 = ⋅ + 3 = 8
double (9 - 3) =
5
⋅ + ⋅
(9 - 3)
= ⋅ + ⋅ = 12
6
• Wenn Zeiger vereinfacht werden muss, Ziel vereinfachen:
add3 (7 - 6) =
⋅ + 3
(7 - 6)
= ⋅ + 3 = 4
1
12-15
Beschreibung von Lazy-Evaluation
Funktionsanwendung
Betrachte allgemeinste Funktionsdefinition:
Nach Art des Ausdrucks:
1. Funktionsanwendung
2. Operatoren und sonstige Syntax
3. Geschachtelte Funktionsanwendung
f p1 ... pK
| g1
= e1
...
| otherwise = eO
where
v1 a1,1 ... = r1
...
f q1 ... qK
...
...
-- oder v1 = r1
Auswertung von Funktionsanwendung f a1 ... aK
Drei Aspekte der Funktionsanwendung
1.1 Pattern-Matching
Ziel: Gleichung auswählen
Schritte zum Funktionswert:
Vorgehen:
1.1. Gleichung auswählen über Pattern-Matching
1.2. Zweig auswählen mittels Guards
1.3. Lokale Definitionen berücksichtigen
• 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
16-19
Beispiel: Pattern-Matching
1.2. Guards
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]:
Beispiel: Guards
a b c
a >= b
b >= c
True =
(51-2)
49
5*7
23
| ⋅>=⋅&&⋅>=⋅=⋅ | ⋅>=⋅=⋅ | True = ⋅
5*7
23
| ⋅>=⋅&&⋅>=⋅=⋅ | ⋅>=⋅=⋅ | True = ⋅
49
• Rechte Seite (mit lokalen Definitionen) auswerten
| True&&⋅>=⋅=⋅ | ⋅>=⋅=⋅ | True = ⋅
| ⋅>=⋅&&⋅>=⋅=⋅ | ⋅>=⋅=⋅ | True = ⋅
49
• Parameter durch Zeiger auf Argumente ersetzen
Beispiel Guards
&& a >= c = a
= b
c
(5*7) 23
51-2
Vorgehen:
• Guards von oben nach unten auswerten, bis erster True ergibt
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
max
|
|
|
max
Ziel: Zweig einer Gleichung auswählen
35
23
35
23
| True && True =⋅ | ⋅>=⋅=⋅ | True = ⋅
49
35
23
| True =⋅ | ⋅>=⋅=⋅ | True = ⋅
49
35
23
⋅
49
20-23
1.3. Lokale Definitionen
Ziel: Ausdruck mit lokalen Definitionen auswerten
Beispiel: Lokale Definitionen
double x = xx
where
xx = x + x
Vorgehen:
double 5
• Ausdruck auswerten
Auswertung:
xx where {xx = ⋅ + ⋅}
• Rechte Seite einer Definition auswerten, wenn Variable benötigt
wird
5
• Lokale Definitionen bleiben also erhalten - Graph mittels Namen
xx where {xx = 10}
10
2. Operatoren und sonstige Syntax
3. Geschachtelte Funktionsaufrufe
• 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
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
• case wie Pattern-Matching oben
24-27
Parser-Kombinatoren mittels Lazy-Evaluation
Parser
Kombinator: Geschlossener Ausdruck
type Parse a b = [a] -> [(b, [a])]
Bezug zur Lazy-Evaluation:
Dabei:
• Grammatiken enthalten oft Alternativen und Sequenzen.
• [a]: Eingabe
• Mit Lazy-Evaluation stoppt der Parser, sobald feststeht, dass eine
Alternative erfüllt ist oder das erste Element einer Folge nicht passt.
• (b,[a]): Ergebnis und restliche Eingabe
• "Backtracking"
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 []
= []
• [(b,[a])]: Liste verschiedener Möglichkeiten, leer wenn Parser
gescheitert ist
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 ]
28-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 (:)
Listen unendlicher Länge
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 ]
Erweiterung der Listennotation
Lazy Evaluation macht Beschreibung unendlicher Datenstrukturen
möglich
Schlusselement weglassen ergibt unendlich lange Liste
Zwei Aspekte:
• Liste aller Zahlen ab 3: [3 ..]
• Erzeugen
ones :: [Int]
ones = 1:ones
• Liste aller ungeraden Zahlen ab 3: [3,5 ..]
• Konsumieren
addFirstTwo :: [Int] -> Int
addFirstTwo (x:y:xs) = x + y
addFirstTwo ones = 2
Pythagoreische Tripel
pythagTriples = [ (x,y,z) | z <- [2 ..],
y <- [2 .. z-1],
x <- [2 .. y-1],
x*x + y*y == z*z]
32-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]
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)
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,48,...]
36-38
Herunterladen