Kapitel 10: Monaden

Werbung
Programmieren in Haskell
Monaden
Programmieren in Haskell
1
Was wir heute machen
• Sequenzierung mit Dollar und Euro
• Die Atommüll-Metapher
• Maybe- und Listen-Monaden
• Return
• Die do-Notation
• Monaden als Berechnungen
• Listenbeschreibungen und Monaden
• State-Monaden
• Die IO-Monade
Programmieren in Haskell
2
Dollar
i (h (g (f x)))
i $ h $ g $ f x
Programmieren in Haskell
3
Dollar
i (h (g (f x)))
i $ h $ g $ f x
infixr 0 $
($) :: (a -> b) -> a -> b
f $ x = f x
Programmieren in Haskell
3
Euro
infixl 0 |>
(|>) :: a -> (a -> b) -> b
x |> f = f x
i $ h $ g $ f x
f x |> g |> h |> i
f x |>
g |>
h |>
i
Programmieren in Haskell
4
Atommüll-Metapher
“Abfall-Prozessoren” sind eine Metapher für Funktionen. Sie nehmen Atommüll,
verarbeiten ihn und spucken Atommüll aus:
Programmieren in Haskell
5
Abfall-Container
Atommüll ist gefährlich, also wird er verpackt:
Programmieren in Haskell
6
bind-Roboter
Ein bind-Roboter entpackt einen Container und steckt den Inhalt (Atommüll) in
einen Prozessor:
Programmieren in Haskell
7
bind (>>=)
container >>= fn =
let a = extractWaste container
in fn a
(>>=) :: m a -> (a -> m b) -> m b
Programmieren in Haskell
8
bind (>>=)
container >>= fn =
let a = extractWaste container
in fn a
(>>=) :: m a -> (a -> m b) -> m b
Mit dem bind-Roboter können wir Prozessoren hintereinanderschalten:
wasteInAContainer >>=
(\a1 -> putInContainer (decompose a1)) >>=
(\a2 -> putInContainer (decay a2)) >>=
(\a3 -> putInContainer (melt a3))
Programmieren in Haskell
8
Bisherige Metaphern
1. Prozessoren (Funktionen)
2. Atommüll (Eingaben)
3. Container (monadische Werte)
4. Der bind-Roboter >>=
5. Fabriken (Monaden)
Programmieren in Haskell
9
Die Maybe-Monade
Zur Erinnerung der Maybe-Datentyp:
data Maybe a = Nothing | Just a
Programmieren in Haskell
10
Die Maybe-Monade
Zur Erinnerung der Maybe-Datentyp:
data Maybe a = Nothing | Just a
Die bind-Funktion:
container >>= fn =
case container of
Nothing -> Nothing
Just a -> fn a
(>>=) :: Maybe a -> (a -> Maybe b) -> Maybe b
Programmieren in Haskell
10
Die Listen-Monade
container >>= fn =
case container of
[] -> []
xs -> concat (map fn xs)
(>>=) :: [a] -> (a -> [b]) -> [b]
Programmieren in Haskell
11
Return
Was machen wir mit Maschinen, die direkt Atommüll ausgeben ohne ihn vorher in
Container zu verpacken?
return :: a -> m a
Das ist unsere Funktion putInContainer von vorhin.
Programmieren in Haskell
12
Return
Was machen wir mit Maschinen, die direkt Atommüll ausgeben ohne ihn vorher in
Container zu verpacken?
return :: a -> m a
Das ist unsere Funktion putInContainer von vorhin.
Die return-Funktion für Maybe:
return a = Just a
Programmieren in Haskell
12
Return
Was machen wir mit Maschinen, die direkt Atommüll ausgeben ohne ihn vorher in
Container zu verpacken?
return :: a -> m a
Das ist unsere Funktion putInContainer von vorhin.
Die return-Funktion für Maybe:
return a = Just a
und für Listen:
return a = [a]
Programmieren in Haskell
12
Die do-Notation
Monadischer Code wie bisher:
wasteInAContainer >>=
\a1 -> foo a1 >>=
\a2 -> bar a2 >>=
\a3 -> baz a3
Programmieren in Haskell
13
Die do-Notation
Monadischer Code wie bisher:
wasteInAContainer >>=
\a1 -> foo a1 >>=
\a2 -> bar a2 >>=
\a3 -> baz a3
Etwas anders geschrieben:
wasteInAContainer >>= \a1 ->
foo a1 >>= \a2 ->
bar a2 >>= \a3 ->
baz a3
Programmieren in Haskell
13
Die do-Notation
Monadischer Code wie bisher:
wasteInAContainer >>=
\a1 -> foo a1 >>=
\a2 -> bar a2 >>=
\a3 -> baz a3
Etwas anders geschrieben:
wasteInAContainer >>= \a1 ->
foo a1 >>= \a2 ->
bar a2 >>= \a3 ->
baz a3
Programmieren in Haskell
Und mit syntaktischem Zucker:
do a1 <- wasteInAContainer
a2 <- foo a1
a3 <- bar a2
baz a3
13
Monaden als Berechnungen
Unserer bisherige Metapher sah Monaden als Container. Jetzt interpretieren wir
Monaden als Berechnungen:
• >>=: verkettet zwei monadische Berechnungen (lässt die erste Berechnung
laufen, füttert das Ergebnis in die zweite Berechung und lässt auch diese laufen)
• return x: die Berechung, die einfach x als Ergebnis hat
Programmieren in Haskell
14
Rechnen mit der Maybe-Monade
Beginnen wir mit einem Telefonbuch:
phonebook :: [(String,String)]
phonebook = [ ("Bob",
"01788
("Fred", "01624
("Alice", "01889
("Jane", "01732
Programmieren in Haskell
665242"),
556442"),
985333"),
187565") ]
15
Rechnen mit der Maybe-Monade
Beginnen wir mit einem Telefonbuch:
phonebook :: [(String,String)]
phonebook = [ ("Bob",
"01788
("Fred", "01624
("Alice", "01889
("Jane", "01732
665242"),
556442"),
985333"),
187565") ]
Darin können wir nach Namen und dazu gespeicherten Telefonnummern suchen:
lookup :: a
-- Schluessel
-> [(a, b)] -- Lookup-Tabelle
-> Maybe b -- Ergebnis des Lookups
Programmieren in Haskell
15
Programmieren in Haskell
16
Beispiel:
Prelude> lookup "Bob" phonebook
Just "01788 665242"
Prelude> lookup "Jane" phonebook
Just "01732 187565"
Prelude> lookup "Zoe" phonebook
Nothing
Programmieren in Haskell
16
Kombination von Lookups
comb :: Maybe a -> (a -> Maybe b) -> Maybe b
comb Nothing _ = Nothing
comb (Just x) f = Just $ f x
Programmieren in Haskell
17
Kombination von Lookups
comb :: Maybe a -> (a -> Maybe b) -> Maybe b
comb Nothing _ = Nothing
comb (Just x) f = Just $ f x
comb ist nichts anderes als der bind-Operator (>>=) für Maybe-Berechnungen. Also:
getTaxOwed :: String
-- Name
-> Maybe Double -- Steuerschuld
getTaxOwed name = lookup name phonebook >>=
(\number -> lookup number governmentalDatabase) >>=
(\registration -> lookup registration taxDatabase)
Programmieren in Haskell
17
Kombination von Lookups
comb :: Maybe a -> (a -> Maybe b) -> Maybe b
comb Nothing _ = Nothing
comb (Just x) f = Just $ f x
comb ist nichts anderes als der bind-Operator (>>=) für Maybe-Berechnungen. Also:
getTaxOwed :: String
-- Name
-> Maybe Double -- Steuerschuld
getTaxOwed name = lookup name phonebook >>=
(\number -> lookup number governmentalDatabase) >>=
(\registration -> lookup registration taxDatabase)
Oder in der do-Notation:
Programmieren in Haskell
17
getTaxOwed name = do
number
<- lookup name phonebook
registration <- lookup number governmentalDatabase
lookup registration taxDatabase
Programmieren in Haskell
18
Die weiteren Tabellen:
governmentalDatabase :: [(String,String)]
governmentalDatabase = [ ("01788 665242",
("01624 556442",
("01889 985333",
("01732 187565",
"B24869237"),
"F36636467"),
"A22121254"),
"J77848449") ]
taxDatabase :: [(String,String)]
taxDatabase = [ ("B24869237", "lots of"),
("F36636467", "not so much"),
("A22121254", "gets something back"),
("J77848449", "criminal") ]
Programmieren in Haskell
19
do number
<- lookup "Bob" phonebook
registration <- lookup number governmentalDatabase
taxOwed
<- lookup registration taxDatabase
return taxOwed
=> Just "lots of"
Programmieren in Haskell
20
do number
<- lookup "Bob" phonebook
registration <- lookup number governmentalDatabase
taxOwed
<- lookup registration taxDatabase
return taxOwed
=> Just "lots of"
do number
<- lookup "Zoe" phonebook
registration <- lookup number governmentalDatabase
taxOwed
<- lookup registration taxDatabase
return taxOwed
Programmieren in Haskell
20
do number
<- lookup "Bob" phonebook
registration <- lookup number governmentalDatabase
taxOwed
<- lookup registration taxDatabase
return taxOwed
=> Just "lots of"
do number
<- lookup "Zoe" phonebook
registration <- lookup number governmentalDatabase
taxOwed
<- lookup registration taxDatabase
return taxOwed
=> Nothing
Programmieren in Haskell
20
Eigenschaften der Maybe-Monade
• Die Maybe-Monade repräsentiert Berechnungen, die versagen können
• Versagen wird propagiert!
Programmieren in Haskell
21
Die Listen-Monade
Berechnungen in der Listen-Monade reprässentieren null oder mehr gültige
Antworten:
instance Monad [] where
return a = [a]
xs >>= f = concat (map f xs)
Programmieren in Haskell
22
Tic-Tac-Toe
Wir wollen alle Brett-Konfigurationen ausrechnen, die sich nach drei Zügen ergeben
können:
getNextConfigs :: Board -> [Board]
getNextConfigs = ... -- details not important
tick :: [Board] -> [Board]
tick bds = concatMap getNextConfigs bds
find3rdConfig :: Board -> [Board]
find3rdConfig bd = tick $ tick $ tick [bd]
Programmieren in Haskell
23
Tic-Tac-Toe
Wir wollen alle Brett-Konfigurationen ausrechnen, die sich nach drei Zügen ergeben
können:
getNextConfigs :: Board -> [Board]
getNextConfigs = ... -- details not important
tick :: [Board] -> [Board]
tick bds = concatMap getNextConfigs bds
find3rdConfig :: Board -> [Board]
find3rdConfig bd = tick $ tick $ tick [bd]
Mit der Listen-Monade:
Programmieren in Haskell
23
find3rdConfig :: Board -> [Board]
find3rdConfig bd0 = do
bd1 <- getNextConfigs bd0
bd2 <- getNextConifgs bd1
bd3 <- getNextConfigs bd2
return bd3
Programmieren in Haskell
24
Listenbeschreibungen und Monaden
pythags = [ (x, y, z) | x <- [1..], y <- [x..], z <- [y..], x^2 + y^2 == z^2 ]
Programmieren in Haskell
25
Listenbeschreibungen und Monaden
pythags = [ (x, y, z) | x <- [1..], y <- [x..], z <- [y..], x^2 + y^2 == z^2 ]
pythags = do
x <- [1..]
y <- [x..]
z <- [y..]
guard (x^2 + y^2 == z^2)
return (x, y, z)
Programmieren in Haskell
25
guard :: Bool -> [()]
guard True = [()]
guard False = []
Programmieren in Haskell
26
guard :: Bool -> [()]
guard True = [()]
guard False = []
pythags =
let ret x y z = [(x, y, z)]
gd x y z = concatMap (\_ -> ret x y z) (guard $ x^2 + y^2 == z^2)
doZ x y
= concatMap (gd x y) [y..]
doY x
= concatMap (doZ x ) [x..]
doX
= concatMap (doY
) [1..]
in doX
Programmieren in Haskell
26
start
|____________________________________________ ...
|
|
|
x 1
2
3
|_______________ ... |_______________ ... |_______________ ...
|
|
|
|
|
|
|
|
|
y 1
2
3
1
2
3
1
2
3
|___...|___...|___... |___...|___...|___...|___...|___...|___...
| | | | | | | | |
| | | | | | | | | | | | | | | | | |
z 1 2 3 1 2 3 1 2 3
1 2 3 1 2 3 1 2 3 1 2 3 1 2 3 1 2 3
Programmieren in Haskell
27
Seiteneffekte mit der State-Monade
Container werden mit einem Ticket gefüttert und geben ihren Inhalt und ein neues
Ticket (eine Quittung) aus:
Programmieren in Haskell
28
bind in der State-Monade
Programmieren in Haskell
29
Input/Output
Die IO-Monade ist eine State-Monade, in der der Zustand die komplette
“Umgebung” ist. Dies erlaubt sequentielle Ein-/Ausgabe:
f = do
putStrLn "What is your name?"
name <- getLine
putStrLn ("Nice to meet you, " ++ name ++ "!")
Programmieren in Haskell
30
Input/Output
Die IO-Monade ist eine State-Monade, in der der Zustand die komplette
“Umgebung” ist. Dies erlaubt sequentielle Ein-/Ausgabe:
f = do
putStrLn "What is your name?"
name <- getLine
putStrLn ("Nice to meet you, " ++ name ++ "!")
Diese Konstruktion legt die Auswertungsreihenfolge fest!
Programmieren in Haskell
30
Copyright notice
http://en.wikibooks.org/wiki/Haskell/Understanding_monads
http://en.wikibooks.org/wiki/Haskell/Advanced_monads
http://en.wikibooks.org/wiki/Haskell/MonadPlus
Programmieren in Haskell
31
Herunterladen