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