Praktische Informatik 3 (WS 2014/15) - informatik.uni

Werbung
Praktische Informatik 3: Funktionale Programmierung
Vorlesung 6 vom 18.11.2014: Funktionen Höherer Ordnung II
Christoph Lüth
Universität Bremen
Wintersemester 2014/15
Rev. 2776
1 [34]
Fahrplan
I
Teil I: Funktionale Programmierung im Kleinen
I
Einführung
I
Funktionen und Datentypen
I
Rekursive Datentypen
I
Typvariablen und Polymorphie
I
Funktionen höherer Ordnung I
I
Funktionen höherer Ordnung II
I
Typinferenz
I
Teil II: Funktionale Programmierung im Großen
I
Teil III: Funktionale Programmierung im richtigen Leben
2 [34]
Heute
I
Die Geheimnisse von map und foldr gelüftet.
I
map und foldr sind nicht nur für Listen.
I
Funktionen höherer Ordnung als Entwurfsmuster
3 [34]
foldr ist kanonisch
I
map und filter sind durch foldr darstellbar:
map :: (α→ β)→ [α] → [β]
map f = foldr ( ( : ) . f ) [ ]
f i l t e r :: (α→ Bool)→ [α] → [α]
f i l t e r p = foldr (λa as→ i f p a then a : as
else as ) [ ]
4 [34]
foldr ist kanonisch
I
map und filter sind durch foldr darstellbar:
map :: (α→ β)→ [α] → [β]
map f = foldr ( ( : ) . f ) [ ]
f i l t e r :: (α→ Bool)→ [α] → [α]
f i l t e r p = foldr (λa as→ i f p a then a : as
else as ) [ ]
foldr ist die kanonische einfach rekursive Funktion.
I
Alle einfach rekursiven Funktionen sind als Instanz von foldr
darstellbar.
foldr (:) [ ] = id
4 [34]
map als strukturerhalten Abbildung
map ist die kanonische strukturerhaltende Abbildung.
I
Struktur (Shape) eines Datentyps T α ist T ().
I
I
I
Für jeden Datentyp kann man kanonische Funktion shape :: T α → T ()
angeben
Für Listen: [()] ∼
= Nat.
Für map gelten folgende Aussagen:
map id = id
map f ◦ map g = map (f ◦ g)
shape. map f = shape
5 [34]
Grenzen von foldr
I
Andere rekursive Struktur über Listen
I
Quicksort: baumartige Rekursion
qsort :: Ord a⇒ [ a ] → [ a ]
qsort [ ] = [ ]
qsort xs = qsort ( f i l t e r (< head xs ) xs ) +
+
f i l t e r (head xs ==) xs +
+
qsort ( f i l t e r (head xs <) xs )
I
Rekursion nicht über Listenstruktur:
I
take: Rekursion über Int
take
take
take
take
I
:: Int→ [ a ] → [ a ]
n _
| n ≤ 0 = []
_ []
= []
n (x : xs )
= x : take (n−1) xs
Version mit foldr divergiert für nicht-endliche Listen
6 [34]
fold für andere Datentypen
fold ist universell
Jeder algebraische Datentyp T hat genau ein foldr .
I
Kanonische Signatur für T:
I
I
I
Pro Konstruktor C ein Funktionsargument fC
Freie Typvariable β für T
Kanonische Definition:
I
I
Pro Konstruktor C eine Gleichung
Gleichung wendet Funktionsparameter fC auf Argumente an
data IL = Cons Int IL | Err String | Mt
foldIL
foldIL
foldIL
foldIL
:: ( Int→ β→ β)→ ( String→ β)→ β→ IL→ β
f e a (Cons i i l ) = f i ( foldIL f e a i l )
f e a ( Err s t r ) = e s t r
f e a Mt
=a
7 [34]
fold für bekannte Datentypen
I
Bool:
8 [34]
fold für bekannte Datentypen
I
Bool: Fallunterscheidung:
data Bool = True | False
foldBool :: β→ β→ Bool→ β
foldBool a1 a2 True = a1
foldBool a1 a2 False = a2
I
Maybe a:
8 [34]
fold für bekannte Datentypen
I
Bool: Fallunterscheidung:
data Bool = True | False
foldBool :: β→ β→ Bool→ β
foldBool a1 a2 True = a1
foldBool a1 a2 False = a2
I
Maybe a: Auswertung
data Maybe α = Nothing | Just α
foldMaybe :: β→ (α→ β)→ Maybe α→ β
foldMaybe b f Nothing = b
foldMaybe b f ( Just a) = f a
I
Als maybe vordefiniert
8 [34]
fold für bekannte Datentypen
I
Tupel:
9 [34]
fold für bekannte Datentypen
I
Tupel: die uncurry-Funktion
foldPair :: (α→ β→ γ)→ (α, β)→ γ
foldPair f (a , b)= f a b
I
Natürliche Zahlen:
9 [34]
fold für bekannte Datentypen
I
Tupel: die uncurry-Funktion
foldPair :: (α→ β→ γ)→ (α, β)→ γ
foldPair f (a , b)= f a b
I
Natürliche Zahlen: Iterator
data Nat = Zero | Succ Nat
foldNat :: β→ (β→ β)→ Nat→ β
foldNat e f Zero = e
foldNat e f (Succ n) = f ( foldNat e f n)
9 [34]
fold für binäre Bäume
I
Binäre Bäume:
data Tree α = Mt | Node α (Tree α) (Tree α)
I
I
Label nur in den Knoten
Instanzen von Map und Fold:
mapT :: (α→ β)→ Tree α→ Tree β
mapT f Mt = Mt
mapT f (Node a l r )=
Node ( f a) (mapT f l ) (mapT f r )
foldT
foldT
foldT
f a
I
:: (α→ β→ β→ β)→ β→ Tree α→ β
f e Mt = e
f e (Node a l r ) =
(foldT f e l ) (foldT f e r )
Kein (offensichtliches) Filter
10 [34]
Funktionen mit fold und map
I
Höhe des Baumes berechnen:
height :: Tree α→ Int
height = foldT (λ_ l r→ 1+ max l r ) 0
I
Inorder-Traversion der Knoten:
inorder :: Tree α→ [α]
inorder = foldT (λa l r→ l +
+ [ a ]+
+ r) [ ]
11 [34]
Kanonische Eigenschaften von foldT und mapT
I
Auch hier gilt:
foldTree Node Mt = id
mapTree id = id
mapTree f◦mapTree g = mapTree (f ◦ g)
shape (mapTree f xs) = shape xs
I
Mit shape :: Tree α→ Tree ()
12 [34]
Das Labyrinth
I
Das Labyrinth als variadischer Baum:
data VTree α = Node α [VTree α]
type Lab α = VTree α
I
Auch hierfür foldT und mapT:
13 [34]
Das Labyrinth
I
Das Labyrinth als variadischer Baum:
data VTree α = Node α [VTree α]
type Lab α = VTree α
I
Auch hierfür foldT und mapT:
foldT :: (α→ [β] → β)→ VTree α→ β
foldT f (Node a ns) = f a (map (foldT f ) ns)
mapT :: (α→ β)→ VTree α→ VTree β
mapT f (Node a ns) = Node ( f a) (map (mapT f ) ns)
13 [34]
Suche im Labyrinth
I
Tiefensuche via foldT
dfts ’
dfts ’
add
add
:: Lab α→ [ Path α]
= foldT add where
a [ ] = [[a]]
a ps = concatMap (map (a : ) ) ps
14 [34]
Suche im Labyrinth
I
Tiefensuche via foldT
dfts ’
dfts ’
add
add
I
:: Lab α→ [ Path α]
= foldT add where
a [ ] = [[a]]
a ps = concatMap (map (a : ) ) ps
Problem:
I
foldT terminiert nicht für zyklische Strukturen
I
Auch nicht, wenn add prüft ob a schon enthalten ist
I
Pfade werden vom Ende konstruiert
14 [34]
Alternativen: Breitensuche
I
Alternative 1: Tiefensuche direkt rekursiv, mit Terminationsprädikat
dfts :: Eq α⇒ (Lab α→ Bool)→ Lab α→ [ Path α]
I
Alternative 2: Breitensuche für potentiell unendliche Liste aller Pfade
bfts :: Lab α→ [ Path α]
bfts l = bfts0 [ ] [ l ] where
bfts0 p [ ] = [ ]
bfts0 p (Node a cs : ns) =
reverse (a : p) : ( bfts0 p ns +
+ bfts0 (a : p) cs )
I
Gegensatz zur Tiefensuche: Liste kann konsumiert werden
15 [34]
Zusammenfassung map und fold
I
map und fold sind kanonische Funktionen höherer Ordnung
I
Für jeden Datentyp definierbar
I
foldl nur für Listen (linearer Datentyp)
I
fold kann bei zyklischen Argumenten nicht terminieren
I
Problem: Termination von fold nur lokal entscheidbar
I
Im Labyrinth braucht man den Kontext um zu entscheiden ob ein Knoten
ein Blatt ist
16 [34]
Funktionen Höherer Ordnung als Entwurfsmethodik
I
Kombination von Basisoperationen zu komplexen Operationen
I
Kombinatoren als Muster zur Problemlösung:
I
I
Einfache Basisoperationen
I
Wenige Kombinationsoperationen
I
Alle anderen Operationen abgeleitet
Kompositionalität:
I
Gesamtproblem läßt sich zerlegen
I
Gesamtlösung durch Zusammensetzen der Einzellösungen
17 [34]
Kombinatoren im engeren Sinne
Definition (Kombinator)
Ein Kombinator ist ein punktfrei definierte Funktion höherer Ordnung.
I
Herkunft: Kombinatorlogik (Schönfinkel, 1924)
K x y
B x
S x y z B x z (y z)
I x
B x
S, K , I sind Kombinatoren
18 [34]
Kombinatoren im engeren Sinne
Definition (Kombinator)
Ein Kombinator ist ein punktfrei definierte Funktion höherer Ordnung.
I
Herkunft: Kombinatorlogik (Schönfinkel, 1924)
K x y
B x
S x y z B x z (y z)
I x
B x
S, K , I sind Kombinatoren
I
Fun fact #1: kann alle berechenbaren Funktionen ausdrücken
18 [34]
Kombinatoren im engeren Sinne
Definition (Kombinator)
Ein Kombinator ist ein punktfrei definierte Funktion höherer Ordnung.
I
Herkunft: Kombinatorlogik (Schönfinkel, 1924)
K x y
B x
S x y z B x z (y z)
I x
B x
S, K , I sind Kombinatoren
I
Fun fact #1: kann alle berechenbaren Funktionen ausdrücken
I
Fun fact #2: S und K sind genug: I = S K K
18 [34]
Beispiel: Parser
I
I
Parser bilden Eingabe auf Parsierungen ab
I
Mehrere Parsierungen möglich
I
Backtracking möglich
Kombinatoransatz:
I
Basisparser erkennen Terminalsymbole
I
Parserkombinatoren zur Konstruktion:
I
I
Sequenzierung (erst A, dann B)
I
Alternierung (entweder A oder B)
Abgeleitete Kombinatoren (z.B. Listen A∗ , nicht-leere Listen A+ )
19 [34]
Modellierung in Haskell
Welcher Typ für Parser?
type Parse = ?
20 [34]
Modellierung in Haskell
Welcher Typ für Parser?
type Parse α β = ?
I
Parametrisiert über Eingabetyp (Token) α und Ergebnis β
20 [34]
Modellierung in Haskell
Welcher Typ für Parser?
type Parse α β = [α]→ β
I
Parametrisiert über Eingabetyp (Token) α und Ergebnis β
I
Parser übersetzt Token in Ergebnis (abstrakte Syntax)
20 [34]
Modellierung in Haskell
Welcher Typ für Parser?
type Parse α β = [α]→ (β, [α])
I
Parametrisiert über Eingabetyp (Token) α und Ergebnis β
I
Parser übersetzt Token in Ergebnis (abstrakte Syntax)
I
Muss Rest der Eingabe modellieren
20 [34]
Modellierung in Haskell
Welcher Typ für Parser?
type Parse α β = [α]→ [(β, [α])]
I
Parametrisiert über Eingabetyp (Token) α und Ergebnis β
I
Parser übersetzt Token in Ergebnis (abstrakte Syntax)
I
Muss Rest der Eingabe modellieren
I
Muss mehrdeutige Ergebnisse modellieren
20 [34]
Modellierung in Haskell
Welcher Typ für Parser?
type Parse α β = [α]→ [(β, [α])]
I
Parametrisiert über Eingabetyp (Token) α und Ergebnis β
I
Parser übersetzt Token in Ergebnis (abstrakte Syntax)
I
Muss Rest der Eingabe modellieren
I
Muss mehrdeutige Ergebnisse modellieren
I
Beispiel: "4*5+3" → [ (4, "*4+3"),
(4*5, "+3"),
(4*5+3, "")]
20 [34]
Basisparser
I
Erkennt nichts:
none :: Parse α β
none = const [ ]
I
Erkennt alles:
suceed :: β→ Parse α β
suceed b inp = [ ( b, inp ) ]
I
Erkennt einzelne Token:
spot :: (α→ Bool)→ Parse α α
spot p [ ]
= []
spot p (x : xs ) = i f p x then [ ( x , xs ) ] else [ ]
token :: Eq α⇒ α→ Parse α α
token t = spot ( t ==)
I
Warum nicht none, suceed durch spot? Typ!
21 [34]
Basiskombinatoren: alt , >∗>
I
Alternierung:
I
Erste Alternative wird bevorzugt
i n f i x l 3 ‘ alt ‘
a l t :: Parse α β→ Parse α β→ Parse α β
a l t p1 p2 i = p1 i +
+ p2 i
22 [34]
Basiskombinatoren: alt , >∗>
I
Alternierung:
I
Erste Alternative wird bevorzugt
i n f i x l 3 ‘ alt ‘
a l t :: Parse α β→ Parse α β→ Parse α β
a l t p1 p2 i = p1 i +
+ p2 i
I
Sequenzierung:
I
Rest des ersten Parsers als Eingabe für den zweiten
i n f i x l 5 >∗>
(>∗>) :: Parse α β→ Parse α γ→ Parse α (β , γ)
(>∗>) p1 p2 i =
concatMap (λ(b, r )→
map (λ(c , s )→ ((b , c ) , s )) (p2 r )) (p1 i )
22 [34]
Basiskombinatoren: use
I
map für Parser (Rückgabe weiterverarbeiten):
infix 4 ‘ use ‘ , ‘use2 ‘
use :: Parse α β→ (β→ γ)→ Parse α γ
use p f i = map (λ(o , r )→ ( f o , r )) (p i )
use2 :: Parse α (β , γ)→ (β→ γ→ δ )→ Parse α δ
use2 p f = use p ( uncurry f )
I
Damit z.B. Sequenzierung rechts/links:
i n f i x l 5 ∗>, >∗
(∗>) :: Parse α β→ Parse α γ→ Parse α γ
(>∗) :: Parse α β→ Parse α γ→ Parse α β
p1 ∗> p2 = p1 >∗> p2 ‘ use ‘ snd
p1 >∗ p2 = p1 >∗> p2 ‘ use ‘ f s t
23 [34]
Abgeleitete Kombinatoren
I
Listen:
A∗ ::= AA∗ | ε
l i s t :: Parse α β→ Parse α [β]
l i s t p = p >∗> l i s t p ‘ use2 ‘ ( : )
‘ alt ‘ suceed [ ]
I
Nicht-leere Listen:
A+ ::= AA∗
some :: Parse α β→ Parse α [β]
some p = p >∗> l i s t p ‘ use2 ‘ ( : )
I
NB. Präzedenzen: >∗> (5) vor use (4) vor alt (3)
24 [34]
Verkapselung
I
Hauptfunktion:
I
Eingabe muß vollständig parsiert werden
I
Auf Mehrdeutigkeit prüfen
parse :: Parse α β→ [α] → Either String β
parse p i =
case f i l t e r ( null . snd) $ p i of
[]
→ Left "Input␣does␣not␣parse"
[ ( e , _) ] → Right e
_
→ Left "Input␣ i s ␣ambiguous"
I
Schnittstelle:
I
Nach außen nur Typ Parse sichtbar, plus Operationen darauf
25 [34]
Grammatik für Arithmetische Ausdrücke
Expr ::= Term + Term | Term
Term ::= Factor * Factor | Factor
Factor ::= Variable | (Expr)
Variable ::= Char+
Char ::= a | · · · | z | A | · · · | Z
26 [34]
Abstrakte Syntax für Arithmetische Ausdrücke
I
Zur Grammatik abstrakte Syntax
data Expr
I
= Plus Expr Expr
| Times Expr Expr
| Var
String
Hier Unterscheidung Term, Factor, Number unnötig.
27 [34]
Parsierung Arithmetischer Ausdrücke
I
I
Token: Char
Parsierung von Factor
pFactor :: Parse Char Expr
pFactor = some ( spot isAlpha ) ‘ use ‘ Var
‘ alt ‘ token ’( ’ ∗> pExpr >∗ token ’) ’
I
Parsierung von Term
pTerm :: Parse Char Expr
pTerm =
pFactor >∗ token ’∗ ’ >∗> pFactor ‘use2 ‘ Times
‘ alt ‘ pFactor
I
Parsierung von Expr
pExpr :: Parse Char Expr
pExpr = pTerm >∗ token ’+’ >∗> pTerm ‘ use2 ‘ Plus
‘ alt ‘ pTerm
28 [34]
Die Hauptfunktion
I
Lexing: Leerzeichen aus der Eingabe entfernen
parseExpr :: String→ Expr
parseExpr i =
case parse pExpr ( f i l t e r (not . isSpace ) i ) of
Right e → e
Left err → error err
29 [34]
Ein kleiner Fehler
I
Mangel: a+b+c führt zu Syntaxfehler — Fehler in der Grammatik
I
Behebung: Änderung der
Expr
Term
Factor
Grammatik
::= Term + Expr | Term
::= Factor * Term | Factor
::= Variable | (Expr)
Variable ::= Char+
Char ::= a | · · · | z | A | · · · | Z
I
Abstrakte Syntax bleibt
30 [34]
Änderung des Parsers
I
Entsprechende Änderung des Parsers in pTerm
pTerm :: Parse Char Expr
pTerm =
pFactor >∗ token ’∗ ’ >∗> pTerm ‘ use2 ‘ Times
‘ alt ‘ pFactor
I
. . . und in pExpr:
pExpr :: Parse Char Expr
pExpr = pTerm >∗ token ’+’ >∗> pExpr ‘ use2 ‘ Plus
‘ alt ‘ pTerm
I
pFactor und Hauptfunktion bleiben.
31 [34]
Erweiterung zu einem Taschenrechner
I
Zahlen:
Factor ::= Variable | Number | . . .
Number ::= Digit+
Digit ::= 0 | · · · | 9
I
Eine einfache Eingabesprache:
Input ::= ! Variable = Expr | $ Expr
I
Eine Auswertungsfunktion:
type State= [ ( String , Integer ) ]
eval :: State→ Expr→ Integer
run :: State→ String→ (State , String )
32 [34]
Zusammenfassung Parserkombinatoren
I
Systematische Konstruktion des Parsers aus der Grammatik.
I
Kompositional:
I
I
Lokale Änderung der Grammatik führt zu lokaler Änderung im Parser
I
Vgl. Parsergeneratoren (yacc/bison, antlr, happy)
Struktur von Parse zur Benutzung irrelevant
I
Vorsicht bei Mehrdeutigkeiten in der Grammatik (Performance-Falle)
I
Einfache Implementierung (wie oben) skaliert nicht
I
Effiziente Implementation mit gleicher Schnittstelle auch für große
Eingaben geeignet.
33 [34]
Zusammenfassung
I
map und fold sind kanonische Funktionen höherer Ordnung
I
. . . und für alle Datentypen definierbar
I
Kombinatoren: Funktionen höherer Ordnung als Entwurfsmethodik
I
I
Einfache Basisoperationen
I
Wenige aber mächtige Kombinationsoperationen
I
Reiche Bibliothek an abgeleiteten Operationen
Nächste Woche: wie prüft man den Typ von
(>∗>) p1 p2 i =
concatMap (λ(b, r )→
map (λ(c , s )→ ((b , c ) , s )) (p2 r )) (p1 i )
→ Typinferenz!
34 [34]
Herunterladen