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
IEinführung
IFunktionen und Datentypen
IRekursive Datentypen
ITypvariablen und Polymorphie
IFunktionen höherer Ordnung I
IFunktionen höherer Ordnung II
ITypinferenz
I Teil II: Funktionale Programmierung im Großen I Teil III: Funktionale Programmierung im richtigen Leben
2 [34]
Heute
I Die Geheimnisse vonmapundfoldrgelüftet.
I mapund foldrsind nicht nur für Listen.
I Funktionen höherer Ordnung als Entwurfsmuster
3 [34]
foldr ist kanonisch
I mapund filter sind durch foldrdarstellbar:
map :: (α→β)→ [α]→ [β] map f=f o l d r ( ( : ) . f ) [ ]
f i l t e r :: (α→ Bool)→ [α]→ [α]
f i l t e r p=f o l d r (λa as→ i f p a then a : as else as ) [ ]
foldrist diekanonische einfach rekursiveFunktion.
I Alle einfach rekursiven Funktionen sind als Instanz vonfoldr darstellbar.
foldr (:) [ ]=id
4 [34]
map als strukturerhalten Abbildung
mapist die kanonischestrukturerhaltende Abbildung.
I Struktur(Shape) eines Datentyps Tαist T().
I Für jeden Datentyp kann man kanonische Funktionshape:: Tα→T () angeben
I Für Listen: [()]∼=Nat.
I Fürmapgelten 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
IQuicksort:baumartigeRekursion 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 überInt take :: Int→ [ a ]→ [ a ] take n _ | n≤0= [ ]
take _ [ ] = [ ]
take n (x : xs ) = x : take (n−1) xs
IVersion mitfoldr divergiert für nicht-endliche Listen
6 [34]
fold für andere Datentypen
foldist universell
Jeder algebraische Datentyp T hat genau einfoldr. I Kanonische Signatur fürT:
I Pro KonstruktorCein FunktionsargumentfC I Freie TypvariableβfürT
I Kanonische Definition:
I Pro KonstruktorCeine Gleichung
I Gleichung wendet FunktionsparameterfCauf Argumente an
data IL=Cons Int IL | Err String | Mt
f ol dIL :: ( Int→β→β)→ ( String→β)→β→ IL→β f ol dIL f e a (Cons i i l )=f i ( f old IL f e a i l ) f ol dIL f e a ( Err s t r ) =e s t r
f ol dIL f e a Mt =a
fold für bekannte Datentypen
I Bool: Fallunterscheidung:
dataBool=True | False foldBool :: β→β→Bool→β foldBool a1 a2 True =a1 foldBool a1 a2 False=a2 I Maybe a: Auswertung
dataMaybeα=Nothing | Justα foldMaybe :: β→ (α→β)→Maybeα→β foldMaybe b f Nothing =b
foldMaybe b f ( Just a)=f a
IAlsmaybevordefiniert
fold für bekannte Datentypen
I Tupel: dieuncurry-Funktion
foldPair :: (α→β→γ)→ (α, β)→γ foldPair f (a , b)=f a b
I Natürliche Zahlen: Iterator dataNat=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:
dataTreeα=Mt | Nodeα (Treeα) (Treeα)
ILabelnurin den Knoten I 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 :: (α→β→β→β)→β→ Treeα→β foldT f e Mt=e
foldT f e (Node a l r )= f a ( foldT f e l ) ( foldT f e r )
IKein (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 Mitshape :: Treeα→Tree ()
12 [34]
Das Labyrinth
I Das Labyrinth als variadischer Baum:
dataVTreeα=Nodeα [ VTreeα]
typeLabα=VTreeα
I Auch hierfürfoldTundmapT:
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 viafoldT dfts ’ :: Labα→ [ Pathα]
dfts ’ =foldT addwhere add a [ ]=[ [ a ] ]
add a ps=concatMap (map (a : ) ) ps I Problem:
I foldTterminiertnichtfürzyklischeStrukturen
IAuch nicht, wennaddprüft obaschon enthalten ist
IPfade werden vomEndekonstruiert
14 [34]
Alternativen: Breitensuche
I Alternative 1:Tiefensuchedirekt rekursiv, mitTerminationsprädikat dfts :: Eqα⇒ (Labα→ Bool)→Labα→ [ Pathα]
I Alternative 2: Breitensuche fürpotentiell unendlicheListeallerPfade 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 kannkonsumiertwerden
15 [34]
Zusammenfassung map und fold
I mapundfoldsindkanonischeFunktionen höherer Ordnung I Für jeden Datentyp definierbar
I foldl nur für Listen (linearerDatentyp)
I foldkann beizyklischenArgumenten nicht terminieren
IProblem: Termination vonfoldnurlokalentscheidbar
IIm Labyrinth braucht man denKontextum zu entscheiden ob ein Knoten ein Blatt ist
16 [34]
Funktionen Höherer Ordnung als Entwurfsmethodik
I Kombinationvon Basisoperationen zu komplexen Operationen I KombinatorenalsMusterzur Problemlösung:
I EinfacheBasisoperationen
I WenigeKombinationsoperationen
I Alle anderen Operationenabgeleitet I Kompositionalität:
I Gesamtproblem läßt sichzerlegen
I Gesamtlösung durchZusammensetzender Einzellösungen
17 [34]
Kombinatoren im engeren Sinne
Definition (Kombinator)
EinKombinatorist 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,IsindKombinatoren
I Fun fact #1: kann alle berechenbaren Funktionen ausdrücken I Fun fact #2:SundKsind genug:I=S K K
18 [34]
Beispiel: Parser
I Parserbilden Eingabe auf Parsierungen ab
I Mehrere Parsierungenmöglich
I Backtrackingmöglich I Kombinatoransatz:
I BasisparsererkennenTerminalsymbole
I Parserkombinatorenzur Konstruktion:
I Sequenzierung(erstA, dannB)
I Alternierung(entwederAoderB)
I AbgeleiteteKombinatoren (z.B.ListenA∗,nicht-leereListenA+)
19 [34]
Modellierung in Haskell
WelcherTypfür Parser?
typeParseα β=[α]→[(β, [α])]
I Parametrisiert überEingabetyp(Token)αundErgebnisβ I Parser übersetztTokeninErgebnis(abstrakte Syntax) I MussRest der Eingabemodellieren
I Mussmehrdeutige Ergebnissemodellieren
I Beispiel:"4*5+3"→[ (4,"*4+3"), (4*5,"+3"), (4*5+3,"")]
20 [34]
Basisparser
I Erkenntnichts:
none :: Parseα β none=const [ ] I Erkenntalles:
suceed :: β→ Parseα β suceed b inp=[ ( b , inp ) ] I Erkennteinzelne 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 nichtnone,suceeddurchspot? Typ!
21 [34]
Basiskombinatoren: alt , >∗>
I Alternierung:
IErste Alternative wirdbevorzugt 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:
IRest des ersten Parsers alsEingabefü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 mapfür Parser (Rückgabeweiterverarbeiten):
i n f i x 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. Sequenzierungrechts/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
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-leereListen: A+::=AA∗ some :: Parseα β→ Parseα [β]
some p=p>∗>l i s t p ‘ use2 ‘ ( : )
I NB. Präzedenzen:>∗>(5) voruse(4) voralt(3)
Verkapselung
I Hauptfunktion:
I Eingabe mußvollständigparsiert werden
I AufMehrdeutigkeitprüfen
parse :: Parseα β→ [α]→ Either Stringβ parse p i=
case f i l t e r ( n u l l . snd) $ p i of [ ] → Left "Input␣does␣not␣parse "
[ ( e , _) ] → Right e
_ → Left "Input␣ i s ␣ambiguous"
I Schnittstelle:
I Nach außen nur TypParsesichtbar, plusOperationendarauf
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 Grammatikabstrakte Syntax data Expr =Plus Expr Expr
| Times Expr Expr
| Var String
I Hier UnterscheidungTerm,Factor,Numberunnötig.
27 [34]
Parsierung Arithmetischer Ausdrücke
I Token:Char I Parsierung vonFactor
pFactor :: Parse Char Expr
pFactor=some ( spot isAlpha ) ‘ use ‘ Var
‘ alt ‘ token ’ ( ’ ∗>pExpr>∗ token ’ ) ’ I Parsierung vonTerm
pTerm :: Parse Char Expr pTerm=
pFactor>∗ token ’∗’>∗>pFactor ‘ use2 ‘ Times
‘ alt ‘ pFactor I Parsierung vonExpr
pExpr :: Parse Char Expr
pExpr=pTerm>∗ token ’+’>∗>pTerm ‘ use2 ‘ Plus
‘ alt ‘ pTerm
28 [34]
Die Hauptfunktion
I Lexing:Leerzeichenaus der Eingabeentfernen parseExpr :: String→ Expr
parseExpr i=
case parse pExpr ( f i l t e r (not . isSpace ) i ) of Right e→ e
Left e r r → er ro r e r r
29 [34]
Ein kleiner Fehler
I Mangel:a+b+cführt zuSyntaxfehler— Fehler in derGrammatik I Behebung:Änderungder Grammatik
Expr ::= Term+Expr|Term Term ::= Factor*Term|Factor Factor ::= Variable|(Expr) Variable ::= Char+
Char ::= a| · · · |z|A| · · · |Z
I Abstrakte Syntaxbleibt
30 [34]
Änderung des Parsers
I Entsprechende Änderung des Parsers inpTerm pTerm :: Parse Char Expr
pTerm=
pFactor>∗ token ’∗’>∗>pTerm ‘ use2 ‘ Times
‘ alt ‘ pFactor I . . . und inpExpr:
pExpr :: Parse Char Expr
pExpr=pTerm>∗ token ’+’>∗>pExpr ‘ use2 ‘ Plus
‘ alt ‘ pTerm
I pFactorund Hauptfunktion bleiben.
31 [34]
Erweiterung zu einem Taschenrechner
I Zahlen:
Factor ::= Variable|Number|. . . Number ::= Digit+
Digit ::= 0| · · · |9 I Eine einfacheEingabesprache:
Input ::= !Variable=Expr|$Expr I EineAuswertungsfunktion:
type State=[ ( String , Integer ) ] eval :: State→Expr→ Integer run :: State→ String→ ( State , String )
32 [34]
Zusammenfassung Parserkombinatoren
I Systematische Konstruktiondes Parsers aus der Grammatik.
I Kompositional:
I Lokale Änderung der Grammatik führt zu lokaler Änderung im Parser
I Vgl. Parsergeneratoren (yacc/bison, antlr, happy)
I Struktur vonParsezur Benutzung irrelevant
I Vorsicht beiMehrdeutigkeitenin der Grammatik (Performance-Falle)
I Einfache Implementierung(wie oben) skaliertnicht
I Effiziente Implementation mitgleicher Schnittstelleauch fürgroße Eingabengeeignet.
33 [34]
Zusammenfassung
I mapundfoldsind kanonische Funktionen höherer Ordnung I . . . und für alle Datentypen definierbar
I Kombinatoren: Funktionen höherer Ordnung alsEntwurfsmethodik
IEinfacheBasisoperationen
IWenigeabermächtigeKombinationsoperationen
IReiche Bibliothek anabgeleitetenOperationen I 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]