Aufgabe
|
Kombinator für das Hintereinanderausführen von Berechnungen
mit zusätzlichen Effekten.
|
Eine Lösung
|
Monaden
|
Definition
|
class Applicative m => Monad m where
return :: a -> m a
(>>=) :: m a -> (a -> m b) -> m b
(>>) :: m a -> m b -> m b
f >> g = f >>= ( \ _ -> g )
(>=>) :: (a -> m b) -> (b -> m c) -> (a -> m c)
f >=> g = \ x -> f x >>= g
|
| |
|
Die Variable m steht für einen Typkonstruktor
mit einem Typ-Parameter, nicht für einen einfachen Typ.
|
Kandidaten
|
|
Maybe
|
1. Beispiel
|
|
instance Monad Maybe where
return x = Just x
Nothing >>= f = Nothing
(Just x) >>= f = f x
|
| |
Reformulierung
|
der Beispielfunktion f
|
|
f :: a -> Maybe d
f x
= f1 x >>= \ x1 ->
f2 x1 >>= \ x2 ->
f3 x2
f x = f1 x >>= f2 >>= f3
f = f1 >=> f2 >=> f3
|
| |
|
Die Verzweigung über Nothing und Just wird
einmal in der Monade beschrieben und bei jeder Benutzung von >>= wiederverwendet.
|
|
In der Definition von f werden nur noch Operatoren aus der Monad-Klasse
verwendet, keine speziellen Eigenschaften von Maybe.
|
|
Die Funktion könnte also auch für andere Monaden verwendet werden, wenn f1,
f2 und f3 entsprechend verallgemeinert werden.
|
Beweis
|
der Äquivalenz der Definitionen von f
|
Explizite Definition
|
f x
= case f1 x of
Nothing -> Nothing
Just x1 ->
case f2 x1 of
Nothing -> Nothing
Just x2 -> f3 x2
|
1. Schritt
|
f x
= case f1 x of
Nothing -> Nothing
Just x1 ->
f2 x1 >>= \ x2 ->
f3 x2
|
2. Schritt
|
f x
= f1 x >>= \ x1 ->
f2 x1 >>= \ x2 ->
f3 x2
f x = f1 x >>= f2 >>= f3
|
|
Komposition der Funktionen f1, f2 und f3 mit Hilfe von >>= erreicht.
|
| |
StateT
|
2. Beispiel:
Zustandsmonade
|
Definition
|
instance Monad StateT where
return x = ST $ \ s -> (x, s)
f >>= g = ST $ \ s ->
let (x1, s1) = (unST f ) s in
let f2 = g x1 in
(unST f2) s1
f >>= g = ST $ \ s ->
let (x1, s1) = unST f s in
unST (g x1) s1
|
Reformulierung
|
der Beispielfunktion f
|
|
f x
= f1 x >>= \ x1 ->
f2 x1 >>= \ x2 ->
f3 x2
f x = f1 x >>= f2 >>= f3
f = f1 >=> f2 >=> f3
|
|
Das Durchfädeln des Zustands wird einmal in der Monade beschrieben und
bei jeder Benutzung von >>= wiederverwendet.
|
|
In der Definition von f werden nur noch Operatoren aus der Monad-Klasse
verwendet, keine speziellen Eigenschaften von StateT.
|
|
Die Funktion könnte also auch für andere Monaden verwendet werden, wenn f1,
f2 und f3 entsprechend verallgemeinert werden.
|
Beweis
|
der Äquivalenz der Definitionen von f
|
Explizite Definition
|
f x
= ST $ \ s ->
let (x1, s1) = unST (f1 x ) s in
let (x2, s2) = unST (f2 x1) s1 in
unST (f3 x2) s2
|
1. Schritt
|
Globale Variable aus 2. let zu Parametern machen.
|
|
f x
= ST $ \ s ->
let (x1, s1) = unST (f1 x ) s in
unST (g x1) s1
where
g x1'
= ST $ \ s1' ->
let (x2, s2) = unST (f2 x1') s1' in
unST (f3 x2) s2
|
2. Schritt
|
g mit >>= formulieren.
|
|
f x
= ST $ \ s ->
let (x1, s1) = unST (f1 x ) s in
unST (g x1) s1
where
g x1'
= f2 x1' >>= \ x2 ->
f3 x2
|
3. Schritt
|
f mit >>= und g formulieren.
|
|
f x
= f1 x >>= \ x1 ->
g x1
where
g x1'
= f2 x1' >>= \ x2 ->
f3 x2
|
4. Schritt
|
g einsetzen.
|
|
f x
= f1 x >>= \ x1 ->
f2 x1 >>= \ x2 ->
f3 x2
f x = f1 x >>= f2 >>= f3
|
|
Komposition der Funktionen f1, f2 und f3 mit Hilfe von >>= erreicht.
|
| |
?
|
Wie startet man eine zustandsbehaftete Berechnung?
|
|
runState :: StateT a -> State -> (a, State)
runState f initialState
= let (res, finalState) = (unST f) initialState
in
(res, finalState)
runState = unST
evalState :: StateT a -> State -> a
evalState = fst . runState
execState :: StateT a -> State -> State
execState = snd . runState
|
?
|
Wie startet man eine Berechnung in der Maybe-Monade?
|
|
runMaybe :: Maybe a -> Maybe a
runMaybe = id
|
Ein-/Ausgabe
|
3. Beispiel:
Mit World und IO.
|
|
IO besitzt die gleiche Struktur wie die Zustandsmonade,
also gilt die Lösung für das Beispiel 2 hier auch.
|
Einschränkung
|
Der Datetyp World wird vollständig vom Haskell-Laufzeitsystem übernommen
und ist nicht nach außen sichtbar.
|
Einschränkung
|
Nur vordefinierte IO-Operationen manipulieren diesen Zustand.
|
?
|
Wie startet man eine IO Berechnung?
|
|
runIO :: IO () -> World -> ((), World)
runIO main currentWorld
|
|
data World = ...
newtype IO a = IO (World -> (a, World))
putChar :: Char -> IO ()
getChar :: IO Char
main :: IO ()
main
= getChar >>= \ c ->
putChar c
main
= getChar >>= putChar
|
|
Was hat die IO-Monade mit Kuchen backen zu tun?
|