Transcript
Technische Universität München
Monads in Haskell
Seminar: Fortgeschrittene Konzepte der funktionalen Programmierung, Sommersemester 2015 Lukas Fürmetz
Technische Universität München
Agenda
Grundlagen von Monaden
Umsetzung in Haskell
Anwendung von Monaden
Fazit
03.06.2015
2
Technische Universität München
Functor
Ein Functor ist eine Datenstruktur über die gemappt werden kann
In Haskell wird dieses Konzept über eine Typklasse umgesetzt:
class Functor f where fmap :: (a -> b) -> f a -> f b
Dabei müssen zwei Gesetze erfüllt sein: – fmap id = id – fmap (f . g) = fmap f . fmap g (Homomorphismus)
03.06.2015
3
Technische Universität München
Beispiel Functor let xs = [1,2,3,4] fmap (*2) xs = [2,4,6,8]
let x = Just 4 fmap even x = Just True
let dic = fromList [(1, "foo"), (2, "bar"), (3, "baz")] fmap reverse dic = fromList [(1, "oof"), (1, "rab"), (1, "zab")] 03.06.2015
4
Technische Universität München
Applicative
Applicative ist eine Erweiterung von Functor
Operationen auf Containern können mithilfe dieser verbunden werden
Umsetzung erfolgt wiederum über eine Typklasse im Modul Control.Applicative
class Functor f => Applicative f where pure :: a -> f a (<*>) :: f (a -> b) -> f a -> f b
03.06.2015
5
Technische Universität München
Applicative Beispiel instance Applicative Maybe where pure = Just Nothing <*> _ Just f <*> m
= Nothing = fmap f m
Just (/) <*> Just 42 <*> Just 3 -- = Just 14
03.06.2015
6
Technische Universität München
Applicative Laws
Identität:
pure id <*> v = v
Komposition:
pure ( . ) <*> u <*> v <*> w = u <*> (v <*> w)
Homomorphismus:
pure f <*> pure x = pure (f x)
Tausch:
u <*> pure y = pure ($ y) <*> u
03.06.2015
7
Technische Universität München
Monads
Eine Monade ist wiederum eine Erweiterung von Applicative
Mithilfe von Monaden können Operationen auf Containern aneinander gehängt werden, wobei die Monade noch die Werte ändern kann „programmierbares Semikolon“)
03.06.2015
8
Technische Universität München
Umsetzung durch eine Typklasse
class Applicative m => Monad m where (>>=) :: forall a b. m a -> (a -> m b) -> m b return :: a -> m a return = pure
03.06.2015
9
Technische Universität München
Monad
„return“ nimmt einen Wert und tut ihn in den Kontext der Monade:
example :: Maybe Integer example = return 3 -- = Just 3
Mithilfe von „>>=“, auch „bind“ genannt, können Operationen auf Monaden aneinander gehängt werden
Just 1 >>= (\x -> return (x * 8)) >>= (\y -> return (y + 2)) -- = Just 10 03.06.2015
10
Technische Universität München
Monad Laws
„return“ arbeitet ähnlich wie ein „neutrales Element“: –
(return x) >>= f
–
m >>= return
=
=
f x
m
„bind“ ist quasi assoziativ:
m a >>= f) >>= g -- ist äquivalent zu m a >>= (\a -> ((f a) >>= g))
03.06.2015
11
Technische Universität München
Die “do”-Notation
Um den Umgang mit Monaden zu vereinfachen, stellt „syntactic sugar“ für die „bind“-Operation zu Verfügung:
Just 1 >>= (\x -> return (x * 8) >>= (\y -> return (y + 2))) kann umgeschrieben werden zu:
do x <- Just 1 y <- return (x * 8) return (y + 2)
03.06.2015
12
Technische Universität München
Die Maybe-Monade
instance Monad Maybe where (Just x) >>= k = k x Nothing >>= _ = Nothing return
03.06.2015
= Just
13
Technische Universität München
Beispiel zur Maybe-Monade Gegeben: lookup :: Eq a => a -> [(a ,b)] -> Maybe b table1 = [("Foo", 1), ( "Bar", 2), ("Baz", 3)] table2 = [(1, 81476), (2, 81399), (4, 91232)]
Gesucht: getPLZ :: String -> Maybe Integer 03.06.2015
14
Technische Universität München
Beispiel zur Maybe-Monade getPLZ n = case lookup n table1 of Nothing -> Nothing Just id -> lookup id table2 getPLZ n = lookup n table1 >>= (\id -> lookup id table2) getPLZ n = do id <- lookup n table1 lookup id table2 03.06.2015
15
Technische Universität München
Die Maybe-Monade Mithilfe der Maybe-Monade können Operationen, die vielleicht ein Ergebnis zurückgeben, miteinander verbunden werden. Sobald eine dieser Funktionen kein Ergebnis liefert, liefert die gesamte Operation kein Ergebnis zurück.
03.06.2015
16
Technische Universität München
List
Die Maybe-Monade ist nützlich um Funktionen zu verbinden die 0 oder 1 Ergebnis haben. Im Gegensatz dazu ist die List-Monade nützlich um Funktionen zu verbinden, die 0 oder mehr Ergebnisse haben.
instance Monad [] m >>= k return x
03.06.2015
where = foldr ((++) . k) [] m = [x]
17
Technische Universität München
Beispiel zur List-Monade data Substance = Substance1 | Substance2 | Substance3 | Substance4 deriving (Show, Eq) react :: Substance -> Substance -> [Substance] react Substance1 Substance2 = [Substance4, Substance4] react Substance3 Substance4 = [Substance1, Substance2] react _ _ = undefined
03.06.2015
18
Technische Universität München
Beispiel zur List-Monade firstExperiment = react Substance1 secondExperiment = react Substance3 simulateExperiment :: [Substance] simulateExperiment = do secondResult <- firstExperiment Substance2 thirdResult <- secondExperiment secondResult return thirdResult
„simulateExperiment“ ist damit äquivalent zu: [Substance1,Substance2,Substance1,Substance2] 03.06.2015
19
Technische Universität München
List-Comprehension „simulateExperiment“ als List-Comprehension: simulateExperiment = [thirdResult | secondResult <- firstExperiment Substance2, thirdResult <- secondExperiment secondResult] List-Comprehensions sind syntaktischer Zucker für die ListMonade: example1 xs = [ x * x | x <- xs, even x] example2 xs = do x <- xs guard (even x) return (x * x) 03.06.2015
20
Technische Universität München
State
Haskell ist eine rein funktionale Programmiersprache, also kann kein Funktion etwas am globalem Zustand ändern
Für manche Probleme ist jedoch ein veränderbarer Zustand nützlich, z.B. bei Spielen
Haskell stellt dafür die State-Monade zur Verfügung, welche bei dem aktuellen GHC über einen Monad-Transformer implementiert ist
03.06.2015
21
Technische Universität München
State-Monad Beispiel
data Op = Pop | Push Integer | Add type Stack = [Integer] type OpStack = [Op] add :: Stack -> Stack add (x:y:xs) = (x + y) : xs
03.06.2015
22
Technische Universität München
State-Monad Beispiel runStack :: OpStack -> State Stack Integer runStack [] = do xs <- get return (head xs) runStack (x:xs) = do stack <- get case x of Pop -> put (tail stack) Push i -> put (i : stack) Add -> put (add stack) runStack xs 03.06.2015
23
Technische Universität München
State-Monad Beispiel initialStack = [2,5,3,8] evalState (runStack [Add, Pop, Pop, Push 5, Add]) initialStack
≡
13
Die State-Monade kann als Abstraktion eine Funktion, welche ein Zustand übergeben wird, und einen Wert und einen neuen Zustand zurück gibt, interpretiert werden.
03.06.2015
24
Technische Universität München
IO-Monade
Mithilfe dieser Monade können IO-Operationen, wie z.B. der Zugriff auf eine Datei, bewerkstelligt werden.
Haskell hat eigentlich keine Seiteneffekte. Widerspruch? Man kann diese Monade aber als Beschreibung von Aktionen sehen, welche dann von der Runtime-Umgebung ausgeführt wird. Somit kann die Sprache an sich ohne Seiteneffekte auskommen!
Alle Funktionen, welche die IO-Monade verwenden, haben „IO a“ in ihrer Typsignatur, somit kann purer und unpurer Code sauber getrennt werden.
03.06.2015
25
Technische Universität München
IO-Monade Beispiel
Simple Implementierung des Unix-Programms „cat“:
main :: IO () main = do a <- getArgs text <- readFile (head a) print text
Implementierung von „wc –l“:
main :: IO () main = do a <- getArgs text <- readFile (head a) print (length $ lines text) 03.06.2015
26
Technische Universität München
Fazit
Monaden sind ein sehr nützliche Abstraktion, welche viele Anwendungen hat, wie z.B. Fehlerbehandlung.
Durch Monaden kann Zustand und I/O in einer rein funktionalen Sprache, wie Haskell, implementiert werden.
Mithilfe der „do“-Notation wird der Umgang mit Monaden sehr vereinfacht.
03.06.2015
27
Technische Universität München
Vielen Dank noch Fragen?
03.06.2015
Lukas Fürmetz
28