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]