Praktische Informatik 3: Funktionale Programmierung Vorlesung 6 vom 22.11.2016: Funktionen Höherer Ordnung II und
Effizienzaspekte
Christoph Lüth Universität Bremen Wintersemester 2016/17
16:02:27 2017-01-17 1 [34]
Fahrplan
I Teil I: Funktionale Programmierung im Kleinen
IEinführung
IFunktionen und Datentypen
IAlgebraische Datentypen
ITypvariablen und Polymorphie
IFunktionen höherer Ordnung I
I Funktionen höherer Ordnung II und Effizenzaspekte I Teil II: Funktionale Programmierung im Großen I Teil III: Funktionale Programmierung im richtigen Leben
PI3 WS 16/17 2 [34]
Heute
I Mehr übermapundfold
Imapundfoldsind nicht nur für Listen
I Effizient funktional programmieren
PI3 WS 16/17 3 [34]
map als strukturerhaltende Abbildung
I Der Datentyp()istterminal: es gibt für jeden Datentypαgenau eine total Abbildungα→ ()
I Struktur(Shape) eines DatentypsTαistT ()
IGegeben durch kanonische Funktionshape :: Tα→T (), dieαdurch ()ersetzt
IFür Listen:[ ( ) ]∼=Nat
mapist die kanonischestrukturerhaltende Abbildung I Fürmapgelten folgende Aussagen:
map id=id map f◦map g=map ( f◦g) shape◦map f=shape
PI3 WS 16/17 4 [34]
map und f i l t e r als Berechnungsmuster
Imap, f i l t e r,foldals Berechnungsmuster:
1.Anwenden einer Funktion aufjedesElement der Liste 2.möglicherweiseFilternbestimmter Elemente 3.Kombinationder Ergebnisse zu EndergebnisE I Gut parallelisierbar, skaliert daher als
Berechnungsmuster für große Datenmengen (big data)
x1 x2 x3 x4
E I Besondere Notation: Listenkomprehension
[ f x | x← as , g x ]≡ map f ( f i l t e r g as )
I Beispiel:
d i g i t s s t r = [ ord x−ord ’0 ’| x← str , i s D i g i t x ]
I Auch mit mehreren Generatoren:
idx = [ a : show i| a← [ ’ a ’ . . ’ z ’ ] , i← [ 0 . . 9 ] ]
PI3 WS 16/17 5 [34]
f o l d r ist kanonisch
f o l d rist diekanonische strukturell rekursiveFunktion.
I Alle strukturell rekursiven Funktionen sind als Instanz vonf o l d r darstellbar
I Insbesondere auchmapundf i l t e r(siehe Übungsblatt) I Es gilt:f o l d r ( : ) [ ]=id
I Jeder algebraischer Datentyp hat einf o l d r
PI3 WS 16/17 6 [34]
f o l d für andere Datentypen
foldist universell
Jeder algebraische DatentypThat genau einf o l d r.
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 I Anmerkung: TypklasseFoldableschränkt Signatur vonf o l d rein 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
f o l d für bekannte Datentypen
I Bool: Fallunterscheidung:
dataBool = False | True foldBool :: β→β→Bool→β foldBool a1 a2 False = a1 foldBool a1 a2 True = a2 IMaybeα: Auswertung
dataMaybeα= Nothing | Justα foldMaybe :: β→ (α→β)→Maybeα→β foldMaybe b f Nothing = b
foldMaybe b f ( Just a) = f a
IAlsmaybevordefiniert
f o l d 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)
PI3 WS 16/17 9 [34]
f o l d für binäre Bäume
I Binäre Bäume:
dataTreeα= Mt | Nodeα (Treeα) (Treeα)
ILabelnurin den Knoten
I Instanz vonfold:
foldT :: (α→β→β→β)→β→ Treeα→β foldT f e Mt = e
foldT f e (Node a l r ) = f a ( foldT f e l ) ( foldT f e r )
IInstanz vonmap, kein (offensichtliches) Filter
PI3 WS 16/17 10 [34]
Funktionen mit foldT und mapT
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 ) [ ]
PI3 WS 16/17 11 [34]
Kanonische Eigenschaften von foldT und mapT
I Auch hier gilt:
foldT Node Mt=id mapT id=id mapT f◦mapT g=mapT ( f◦g) shape (mapT f xs )=shape xs
I Mitshape :: Treeα→Tree ()
PI3 WS 16/17 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 )
PI3 WS 16/17 13 [34]
Suche im Labyrinth
I Tiefensuche viafoldT dfts ’ :: Labα→ [ Pathα]
dfts ’ = foldT add where 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
PI3 WS 16/17 14 [34]
Effizienzaspekte
PI3 WS 16/17 15 [34]
Effizienzaspekte
I ZurVerbesserungder Effizienz:
IAnalyse derAuswertungsstrategie
I. . . und desSpeichermanagement
I Der ewige Konflikt:Geschwindigkeitvs.Platz I Effizenzverbesserungen durch
IEndrekursion: Iteration in funktionalen Sprachen
IStriktheit:Speicherlecksvermeiden (bei verzögerter Auswertung) I Vorteil: Effizienzmuss nichtim Vordergrund stehen
PI3 WS 16/17 16 [34]
Endrekursion
Definition (Endrekursion) Eine Funktion istendrekursiv, wenn
(i) es genaueinenrekursiven Aufruf gibt,
(ii) dernichtinnerhalb einesgeschachtelten Ausdruckssteht.
I D.h. darübernur Fallunterscheidungen:caseoderi f I Entsprichtgotooderwhilein imperativen Sprachen.
I Wird inSprungoderSchleifeübersetzt.
I Brauchtkeinen Platzauf dem Stack.
PI3 WS 16/17 17 [34]
Einfaches Beispiel
I In Haskell:
even x =i f x>1 theneven (x−2) else x == 0 I Übersetzt nach C:
int even (int x)
{ i f (x>1) return (even (x−2)) else return x == 0; } I Äquivalente Formulierung:
int x ; int even ()
{ i f (x>1) { x−= 2; return even ( ) ; } return x == 0; }
I Iterative Variante mit Schleife:
intx ; int even () { while (x>1) { x−= 2; }
return x == 0; }
PI3 WS 16/17 18 [34]
Beispiel: Fakultät
I Fakultätnichtendrekursiv:
fac1 :: Integer→ Integer
fac1 n =i f n == 0 then1 elsen∗ fac1 (n−1) I Fakultät endrekursiv:
fac2 :: Integer→ Integer fac2 n = fac ’ n 1where
fac ’ :: Integer→ Integer→ Integer fac ’ n acc =i f n == 0then acc
else fac ’ (n−1) (n∗acc) I fac1verbraucht Stack,fac2nicht.
I Istnichtmerklich schneller?!
PI3 WS 16/17 19 [34]
Beispiel: Listen umdrehen
I Liste umdrehen,nichtendrekursiv:
rev ’ :: [ a ]→ [ a ] rev ’ [ ] = [ ]
rev ’ (x : xs ) = rev ’ xs ++ [ x ]
IHängt auch nochhintenan —O(n2)!
I Liste umdrehen,endrekursivundO(n):
rev :: [ a ]→ [ a ]
rev xs = rev0 xs [ ] where rev0 [ ] ys = ys
rev0 (x : xs ) ys = rev0 xs (x : ys ) I Beispiel:last (rev [1..10000]) I Schneller— warum?
PI3 WS 16/17 20 [34]
Verzögerte Auswertung und Speicherlecks
I Garbage collectiongibtunbenutztenSpeicher wieder frei.
I Unbenutzt: Bezeichner nicht mehr imerreichbar
I Verzögerte Auswertungeffizient, weil nur beiBedarfausgewertet wird
I Aber Achtung:Speicherlecks!
I Eine Funktion hat einSpeicherleck, wenn Speicherunnötiglange im Zugriff bleibt.
I “Echte” Speicherlecks wie in C/C++nicht möglich.
I Beispiel:fac2
I Zwischenergebnisse werdennicht auswertet.
I Insbesondere ärgerlich beinicht-terminierenden Funktionen.
PI3 WS 16/17 21 [34]
Striktheit
I Strikte Argumenteerlauben AuswertungvorAufruf
IDadurchkonstanterPlatz beiEndrekursion.
I ErzwungeneStriktheit:seq :: α→β→β
⊥‘seq‘ b = ⊥ a ‘seq‘ b = b
I seqvordefiniert (nichtinHaskell definierbar)
I ($!) :: (a→b)→a→bstrikte Funktionsanwendung f $ ! x = x ‘ seq ‘ f x
Ighc machtStriktheitsanalyse I Fakultät in konstantem Platzaufwand
fac3 :: Integer→ Integer fac3 n = fac ’ n 1where
fac ’ n acc = seq acc $ i f n == 0then acc else fac ’ (n−1) (n∗acc)
PI3 WS 16/17 22 [34]
Speicherprofil: fac1 50000, nicht optimiert
fac 1 50000 +RTS -hc 108,438 bytes x seconds Tue Nov 22 15:24 2016
seconds 0.0 0.0 0.0 0.1 0.1 0.1 0.1 0.1 0.2 0.2 0.2 0.2 0.2
bytes
0k 200k 400k 600k
(52)PINNED (101)fac1/main/Main.CAF
Speicherprofil: fac1 50000, optimiert
fac 1 50000 +RTS -hc 64,778 bytes x seconds Tue Nov 22 15:24 2016
seconds
0.0 0.0 0.0 0.1 0.1 0.1 0.1 0.1 0.2 0.2 0.2 0.2 0.2
bytes
0k 50k 100k 150k 200k 250k 300k 350k
(52)PINNED (102)fac1/main
Speicherprofil: fac2 50000, nicht optimiert
fac 2 50000 +RTS -hc 126,365 bytes x seconds Tue Nov 22 15:24 2016
seconds
0.0 0.1 0.1 0.2 0.2 0.2
bytes
0k 200k 400k 600k
(52)PINNED (101)fac2/main/Main.CAF
PI3 WS 16/17 25 [34]
Speicherprofil: fac2 50000, optimiert
fac 2 50000 +RTS -hc 25,634 bytes x seconds Tue Nov 22 15:24 2016
seconds
0.0 0.1 0.1 0.2 0.2 0.2
bytes
0k 20k 40k 60k 80k 100k
(85)GHC.Conc.Signal.CAF (93)GHC.IO.Handle.FD.CAF (84)GHC.IO.Encoding.CAF (52)PINNED (102)fac2/main
PI3 WS 16/17 26 [34]
Speicherprofil: fac3 50000, nicht optimiert
fac 3 50000 +RTS -hc 26,525 bytes x seconds Tue Nov 22 15:24 2016
seconds
0.0 0.1 0.1 0.2 0.2 0.2 0.3
bytes
0k 20k 40k 60k 80k 100k
(89)GHC.Conc.Signal.CAF (86)GHC.IO.Handle.FD.CAF (87)GHC.IO.Encoding.CAF (52)PINNED (101)fac3/main/Main.CAF
PI3 WS 16/17 27 [34]
Speicherprofil: fac3 50000, optimiert
fac 3 50000 +RTS -hc 26,855 bytes x seconds Tue Nov 22 15:24 2016
seconds
0.0 0.1 0.1 0.2 0.2 0.2 0.3
bytes
0k 20k 40k 60k 80k 100k
(85)GHC.Conc.Signal.CAF (93)GHC.IO.Handle.FD.CAF (84)GHC.IO.Encoding.CAF (52)PINNED (102)fac3/main
PI3 WS 16/17 28 [34]
Fazit Speicherprofile
I Endrekursionnurbeistrikten Funktionenschneller I Optimierung desghc
I MeistausreichendfürStriktheitsanalyse
I Abernichtfür Endrekursion I Deshalb:
I ManuelleÜberführung in Endrekursionsinnvoll
I Compiler-Optimierungfür Striktheit nutzen
PI3 WS 16/17 29 [34]
Überführung in Endrekursion
I Voraussetzung 1: Funktion istlinear rekursiv
I Gegeben Funktion f0:S→T
f0x = ifB x thenH x
else(f0(K x))⊗ (E x)
IMitK:S→S,⊗:T→T→T,E:S→T,H:S→T.
I Voraussetzung 2:⊗assoziativ,e:T neutrales Element
I Dann istendrekursiveForm:
f:S→T
f x = g x ewhere
g x y = ifB x then(H x)⊗y elseg(K x) ((E x)⊗y)
PI3 WS 16/17 30 [34]
Beispiel
I Länge einer Liste (nicht-endrekursiv) length ’ :: [α]→ Int
length ’ xs =i f n u l l xs then0 else 1+ length ’ ( t a i l xs )
I Zuordnung der Variablen:
K(x) 7→ tailx E(x) 7→ 1 x⊗y 7→ x+y
B(x) 7→ nullx H(x) 7→ 0
e 7→ 0 I Es gilt:x⊗e=x+ 0 =x (0 neutrales Element)
PI3 WS 16/17 31 [34]
Beispiel
I DamitendrekursiveVariante:
length :: [α]→ Int length xs = len xs 0where
len xs y = i f n u l l xs then y−−was: y+ 0 else len ( t a i l xs ) (1+ y)
I AllgemeinesMuster:
IMonoid (⊗,e):⊗assoziativ,eneutrales Element.
IZusätzlicher ParameterakkumuliertResultat.
PI3 WS 16/17 32 [34]
Weiteres Beispiel: f o l d r vs. f o l d l
I f o l d ristnicht endrekursiv:
f o l d r :: (α→β→β) →β → [α] →β f o l d r f z [ ] = z
f o l d r f z (x : xs ) = f x ( f o l d r f z xs ) I f o l d listendrekursiv:
f o l d l :: (α→β→α) →α→ [β] →α f o l d l f z [ ] = z
f o l d l f z (x : xs ) = f o l d l f ( f z x) xs I f o l d l ’iststriktundendrekursiv:
f o l d l ’ :: (α→β→α)→α→ [β]→α f o l d l ’ f a [ ] = a
f o l d l ’ f a (x : xs ) =let a0= f a x in a0 ‘ seq ‘ f o l d l ’ f a0 xs I Für Monoid (⊗,e) gilt:f o l d r⊗e l=f o l d l ( f l i p ⊗) e l
PI3 WS 16/17 33 [34]
Zusammenfassung
Imapundfoldsind kanonische Funktionen höherer Ordnung, und für alle Datentypen definierbar
Imap,f i l t e r,foldsind ein nützliches, skalierbares und allgemeines Berechnungsmuster
I Effizient funktional programmieren:
IEndrekursion:whilefür Haskell
IMitStriktheitundEndrekursionSpeicherlecksvermeiden.
IFür StriktheitCompileroptimierungnutzen
I Nächste Woche: Funktionale Programmierung im Großen — Abstrakte Datentypen
PI3 WS 16/17 34 [34]