Funktionales Programmieren
Teil 3
Carl Philipp Reh
Universit¨at Siegen
22. Mai 2020
Functor
Die Funktion map :: ( a - > b ) - > [ a ] - > [ b ]
bekommt eine Funktion f :: a - > b und wendet diese auf alle Elemente einer Liste an. Zum Beispiel gilt
map s h o w [1 ,2] = [ " 1 " , " 2 " ]
map (\ x - > mod x 2 == 0) [1 ,2 ,3]
= [ False , True , F a l s e ]
Dies funktioniert nicht bloß auf Listen, sondern auf vielen verschiedenen Datenstrukturen, die Elemente
”enthalten“. Hierf¨ur hat Haskell eine eigene Typklasse:
c l a s s F u n c t o r f w h e r e
f m a p :: ( a - > b ) - > f a - > f b
Die Implementierung f¨ur Listen ist:
i n s t a n c e F u n c t o r [] w h e r e f m a p = map
Functor
c l a s s F u n c t o r f w h e r e
f m a p :: ( a - > b ) - > f a - > f b
Da hier f a und f b gebildet werden, muss f :: * - > * gelten, was zum Beispiel passend ist f¨ur [ ] : : * - > *.
Ein Functor ist also ein Typkonstruktor mit Kind * - > *, d.h.
eine Abbildung von Typen auf Typen, und eine Funktion, die Funktionen vom Typ a - > b auf Funktionen vom Typ f a - > f b abbildet.
Functor
c l a s s F u n c t o r f w h e r e
f m a p :: ( a - > b ) - > f a - > f b Beispielsweise sieht die Implementierung f¨ur M a y b e folgendermaßen aus:
i n s t a n c e F u n c t o r M a y b e w h e r e f m a p f m = c a s e m of
N o t h i n g - > N o t h i n g J u s t x - > J u s t ( f x )
Zum Beispiel gilt
f m a p (\ x - > x + 1) N o t h i n g = N o t h i n g f m a p (\ x - > x + 1) ( J u s t 3) = J u s t 4
Functor
Anwendungsbeispiel: Wir wollen ausrechnen, ob ein Benutzer vollj¨ahrig ist. Dieser gibt sein Alter als String ein, den wir erst umwandeln m¨ussen mit der Funktion
s t r i n g T o I n t :: S t r i n g - > M a y b e Int
Diese gibt N o t h i n g zur¨uck, wenn der String kein g¨ultiger Int ist. Zum Testen, ob jemand mindestens 18 ist, definieren wir i s A d u l t :: Int - > B o o l
i s A d u l t x = x >= 18
Dann gilt zum Beispiel
f m a p i s A d u l t ( s t r i n g T o I n t " abc " ) = N o t h i n g f m a p i s A d u l t ( s t r i n g T o I n t " 10 " ) = J u s t F a l s e f m a p i s A d u l t ( s t r i n g T o I n t " 20 " ) = J u s t T r u e
Applicative
A p p l i c a t i v e ist eine Erweiterung von F u n c t o r, die es erlaubt, ¨uber mehr als einen Wert auf einmal zu
”mappen“. Als Beispiel wollen wir ausgeben, ob ein Benutzer ¨alter ist als der andere. Dazu definieren wir
i s O l d e r T h a n :: Int - > Int - > B o o l
Mit f m a p auf M a y b e erhalten wir f m a p i s O l d e r T h a n
:: M a y b e Int - > M a y b e ( Int - > B o o l )
Um damit weiter zu arbeiten, brauchen wir eine Funktion M a y b e ( Int - > B o o l ) - > M a y b e Int
- > M a y b e B o o l die uns von A p p l i c a t i v e bereitgestellt wird:
( <* >) :: f ( a - > b ) - > f a - > f b
Applicative
Die volle Definition von A p p l i c a t i v e ist
c l a s s F u n c t o r f = > A p p l i c a t i v e f w h e r e p u r e :: a - > f a
( <* >) :: f ( a - > b ) - > f a - > f b Die Funktion p u r e
”bettet einen Wert ein“.
Die Implementierung f¨ur M a y b e sieht folgendermaßen aus i n s t a n c e A p p l i c a t i v e M a y b e w h e r e
p u r e = J u s t
f <* > m = c a s e f of N o t h i n g - > N o t h i n g J u s t f ’ - > c a s e m of
N o t h i n g - > N o t h i n g
J u s t m ’ - > J u s t ( f ’ m ’)
Es gilt also f¨ur g :: a - > b und x :: a, dass J u s t g <* > J u s t x = J u s t ( g x )
Applicative
Wir k¨onnen in unserem Beispiel dann schreiben:
c o m p a r e A g e s ::
S t r i n g - > S t r i n g - > M a y b e B o o l c o m p a r e A g e s x y =
( f m a p i s O l d e r T h a n ) ( s t r i n g T o I n t x )
<* > ( s t r i n g T o I n t y )
Um diesen Code besser zu verstehen, ist es wichtig, sich die Typen, die auftreten, anzuschauen: Zun¨achst haben wir, dass
f m a p i s O l d e r T h a n ::
M a y b e Int - > M a y b e ( Int - > B o o l ) Dieses wenden wir dann auf
s t r i n g T o I n t x :: M a y b e Int an und erhalten M a y b e ( Int - > B o o l ). Als N¨achstes ¨ubergeben wir dies zusammen mit s t r i n g T o I n t y :: M a y b e Int an <* >
und erhalten M a y b e B o o l.
Applicative
A p p l i c a t i v e erlaubt es, statt <* > die Funktion l i f t A 2 zu implementieren:
l i f t A 2 :: ( a - > b - > c ) - > f a - > f b - > f c
Als Beispiel betrachten wir wieder M a y b e: i n s t a n c e A p p l i c a t i v e M a y b e w h e r e
l i f t A 2 f m m ’ = c a s e m of N o t h i n g - > N o t h i n g
J u s t x - > c a s e m ’ of N o t h i n g - > N o t h i n g
J u s t x ’ - > J u s t ( f x x ’)
Wir k¨onnen dann auch schreiben c o m p a r e A g e s x y =
l i f t A 2 i s O l d e r T h a n
( s t r i n g T o I n t x ) ( s t r i n g T o I n t y )
Applicative
Als Anwendungsbeispiel von A p p l i c a t i v e betrachten wir die Funktion s e q u e n c e A auf Listen:
s e q u e n c e A :: A p p l i c a t i v e f = > [ f a ] - > f [ a ]
Mit M a y b e ergibt sich
s e q u e n c e A :: [ M a y b e a ] - > M a y b e [ a ]
Hierbei gilt, dass s e q u e n c e A l = N o t h i n g genau dann, wenn mindestens ein Element inl N o t h i n g ist.
Beispielsweise gilt
s e q u e n c e A [ Nothing , J u s t 1] = N o t h i n g s e q u e n c e A [ J u s t 1 , J u s t 2] = J u s t [1 ,2]
Wichtig: Die Implementierung von s e q u e n c e A auf Listen funktioniert mit jedem A p p l i c a t i v e.
Monad
M o n a d ist eine Erweiterung von A p p l i c a t i v e, die es erlaubt,
”sequentielle Abh¨angigkeit“ auszudr¨ucken.
Als Beispiel wollen wir die Fehlerbehandlung beim Einlesen eines Alters erweitern. Wir wollen Int durch Nat ersetzen, sodass das Alter nicht mehr negativ sein kann. Zum Konvertieren von Int zu Nat verwenden wir die Funktion
i n t T o N a t :: Int - > M a y b e Nat
Die Funktion s t r i n g T o I n t liefert uns M a y b e Int. Dieses wollen wir nur an i n t T o N a t ¨ubergeben, wenn es nicht N o t h i n g ist. Wir brauchen also eine Funktion
M a y b e Int - > ( Int - > M a y b e Nat ) - > M a y b e Nat Diese wird uns von M o n a d bereitgestellt:
( > >=) :: f a - > ( a - > f b ) - > f b
Monad
Die allgemeine Definition von M o n a d ist
c l a s s A p p l i c a t i v e f = > M o n a d f w h e r e ( > >=) :: f a - > ( a - > f b ) - > f b Zum Beispiel sieht die Implementierung von M a y b e so aus:
i n s t a n c e M o n a d M a y b e w h e r e m > >= f = c a s e m of
N o t h i n g - > N o t h i n g J u s t x - > f x
Die Funktion fkann nur einen Wert produzieren, wenn m schon nicht N o t h i n g ist. Diese
”sequentielle Abh¨angigkeit“ kann man nicht mit A p p l i c a t i v e alleine ausdr¨ucken. Zum Beispiel gilt:
s t r i n g T o I n t " x " > >= i n t T o N a t = N o t h i n g s t r i n g T o I n t " -5 " > >= i n t T o N a t = N o t h i n g s t r i n g T o I n t " 2 " > >= i n t T o N a t
= J u s t ( S u c c ( S u c c Z e r o ) )
Monad
Als Anwendungsbeispiel von M o n a d betrachten wir die Funktion j o i n :: M o n a d f = > f ( f a ) - > f a
Diese
”entfernt“ eine Schicht eines Typkonstruktors. Zum Beispiel erhalten wir f¨ur M a y b e und [], dass
j o i n ( J u s t ( J u s t 10) ) = J u s t 10 j o i n ( J u s t N o t h i n g ) = N o t h i n g j o i n [ [ ] ] = []
j o i n [[1 ,2] ,[] ,[3 ,4] ,[5]] = [1 ,2 ,3 ,4 ,5]
Auch hier gilt nat¨urlich, dass j o i n mit jeder Monade funktioniert.