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