ALP I Monaden in Haskell

Werbung
ALP I
Monaden in Haskell
Teil I
WS 2009/2010
Prof. Dr. Margarita Esponda
Prof. Dr. Margarita Esponda
1
Schönheit der funktionalen Programmiersprachen
Schöne Eigenschaften von Programmen in rein funktionalen
Programmiersprachen sind:
• Semantische Korrespondenz zwischen ProgrammFunktionen und mathematischen Funktionen
• Funktionale Sprachen sind deklarativ
Church-Rosser-Eigenschaft
• Funktionen Höherer Ordnung
• Algebraische Datentypen
• Parametrischer Polymorphismus
• Abstrakte Datentypen
Prof. Dr. Margarita Esponda
2
Vorteile von funktionalen Programmiersprachen
Vorteile von Programmen in rein funktionalen
Programmiersprachen sind:
• Programme sind sehr kompakt
• Mathematische Analyse von Programmeigenschaften
ist viel leichter als in imperativen
Programmiersprachen.
• Programmverifikation ist machbar
• Saubere Schnittstellen
• Programme haben keine Seiteneffekte
Prof. Dr. Margarita Esponda
3
Nachteile von funktionalen Programmiersprachen
Nachteile: Rein funktionale Programmiersprachen:
• kein "up-date in place" wie in imperativen
Programmiersprachen => Programme sind
langsamer und brauchen mehr Speicher.
• keine Ein-/Ausgabe während der Ausführung des
Programm (nicht interaktiv)
• keine Fehlerbehandlung
• keine Nebenläufigkeit
• keine Zustände oder Programm-Tracing möglich
• keine Schnittstellen zu anderen
Programmiersprachen
Prof. Dr. Margarita Esponda
4
Wo liegt das Hauptproblem?
Wie können Programme in funktionalen
Programmiersprachen mit der Welt interagieren, die die
Welt zwischendurch verändert, ohne die Schönheit des
funktionalen Programmierens zu zerstören?
Hauptproblem
Wie können funktionale Programmiersprachen mit
Seiteneffekten am besten umgehen?
Prof. Dr. Margarita Esponda
5
1. Lösung
Wir können einfach Ein-/Ausgabe-Funktionen einführen.
solution = readNum() + readNum()*2
Beispiele:
Das Ergebnis hängt von der Auswertungsreihenfolge der
(+)-Operation ab.
list = [putChar 'a' + putChar 'b' ]
Die putChar-Funktion wird in Abhängigkeit der
Verwendung der Liste (list) ausgeführt oder nicht.
Wenn nur die Länge der Liste berechnet wird, müssen
die Elemente der Liste nicht ausgewertet werden.
Prof. Dr. Margarita Esponda
6
Lazy-Evaluation
Auswertung nach Bedarf
Lazy-Evaluation ist inkompatibel mit Seiteneffekten
Seiteneffekte
Lazy-Evaluation
Funktionale
Programmierung
Prof. Dr. Margarita Esponda
7
Wir können nicht auf Seiteneffekte verzichten
Moderne Software ist interaktiv und verlangt anspruchvolle
Benutzerschnittstellen.
Das impliziert:
• Fehlerbehandlung
• Nebenläufigkeit
• Verfolgung von Zuständen (für Fehlersuche)
• Veränderungen am Ort (up-date in place), um
effiziente Programme zu implementieren
• Ein-/Ausgabe
Prof. Dr. Margarita Esponda
8
Monaden
Lösung:
• Anfang der 90er Jahre
• aus der Kategorientheorie
• Monaden sind abstrakte Datentypen
• Idee: Seitenefekte ja, aber unter strenger
Kontrolle
• Seiteneffekte können nur in bestimmten Teilen
eines Programms entstehen. Der Rest bleibt rein
funktional.
Prof. Dr. Margarita Esponda
9
Vor Monaden gab es andere Lösungen
• Streams
• Continuations
• World-Passing
Prof. Dr. Margarita Esponda
10
Motivation
Nehmen wir an, wir haben eine Funktion, die boolsche
Ausdrücke auswertet.
data BExp = T | F | And BExp BExp | Or BExp BExp | Not BExp
deriving Show
eval :: BExp -> Bool
eval T = True
eval F = False
eval (And e1 e2) = (eval e1) && (eval e2)
eval (Or e1 e2) = (eval e1) || (eval e2)
eval (Not e) = not (eval e)
Prof. Dr. Margarita Esponda
11
Motivation
Wir möchten bei einer Auswertung die Reihenfolge der
Operationen protokollieren.
eval :: BExp -> Trace Bool
eval T = (True, trace T True)
Funktionen müssen komplett
neu geschrieben werden !
Sehr umständlich !
eval F = (False, trace F False)
eval e@(And e1 e2) =
let (re1, te1) = (eval e1) in
let (re2, te2) = (eval e2) in
let r = re1 && re2 in (r, te1++te2++(trace e r))
eval e@(Or e1 e2) = let (re1, te1) = (eval e1) in
let (re2, te2) = (eval e2) in
let r = re1 || re2 in (r, te1++te2++(trace e r))
eval (Not e) =
let (re, te) = (eval e) in
let r = (not re) in (r, te++(trace (Not e) r))
trace bexp b = "eval (" ++ show bexp ++ ") =" ++ show b ++ "\n"
Prof. Dr. Margarita Esponda
12
Motivation mit Fehlerbehandlung
tail
:: [a] -> [a]
tail
( _ : xs ) = xs
tail
[]
= error ("… empty list")
divide :: Int -> Int -> Int
divide n m | (m /= 0)
| otherwise
Prof. Dr. Margarita Esponda
=
n 'div' m
= error ("… not posible!")
13
Motivation mit Fehlerbehandlung
data Maybe a
=
Nothing | Just a
deriving (Eq, Ord, Read, Show)
errorDivide :: Int -> Int -> Maybe Int
errorDivide n m | m == 0
= Nothing
| otherwise
errorTail
:: [a] -> Maybe [a]
errorTail
( _ : xs ) = Just xs
errorTail
[]
Prof. Dr. Margarita Esponda
= Just (n `div` m)
= Nothing
14
Motivation mit Fehlerbehandlung
Was passiert, wenn wir unsere Funktionen innerhalb
anderer Funktionen verwenden?
f ( errorDivide x y)
Der Datentyp muss zusammenpassen!
errorDivide ::
f
Int
->
Int
:: Maybe Int ->
-> Maybe Int
b
Wir möchten den Fehler weiterleiten!
Prof. Dr. Margarita Esponda
15
Motivation mit Fehlerbehandlung
Maybe-Monade
mapMaybe-Funktion
Maybe b
Maybe a
g
a
b
mapMaybe g
Prof. Dr. Margarita Esponda
16
Motivation mit Fehlerbehandlung
Maybe-Monade
mapMaybe ::
(a->b) -> Maybe a -> Maybe b
mapMaybe g Nothing
mapMaybe g
Prof. Dr. Margarita Esponda
= Nothing
(Just x) = Just ( g x )
17
Motivation mit Fehlerbehandlung
Maybe-Monade
maybe-Funktion
b
Maybe a
f
a
n
maybe n f
Prof. Dr. Margarita Esponda
18
Motivation mit Fehlerbehandlung
Maybe-Monade
maybe ::
b -> (a->b) -> Maybe a -> b
maybe n f Nothing
maybe n f
= n
(Just x) = f x
Anwendung:
f a b = maybe b (+a) (mapMaybe (*2) (errorDivide a b))
Prof. Dr. Margarita Esponda
19
Motivation mit Fehlerbehandlung
Maybe-Monade
Beispiel:
f a b = maybe a (+b) (mapMaybe (*2) (errorDivide a b))
f 20 0 =>
maybe 20 (+0) (mapMaybe (*2) (errorDivide 20 0))
=> maybe 20 (+0) (mapMaybe (*2) Nothing )
=> maybe 20 (+0) ( Nothing )
=> maybe 20
Prof. Dr. Margarita Esponda
20
Motivation mit Fehlerbehandlung
Maybe-Monade
Beispiel:
f a b = maybe a (+b) (mapMaybe (*2) (errorDivide a b))
f 20 2 =>
maybe 20 (+2) (mapMaybe (*2) (errorDivide 20 2))
=> maybe 20 (+2) (mapMaybe (*2) (Just 10))
=> maybe 20 (+2) ( Just 20)
=> (+2) (Just 20)
=> 22
Prof. Dr. Margarita Esponda
21
Maybe-Monade
data Maybe a = Nothing | Just a
Definition der Maybe-Monade:
instance Monad Maybe where
Nothing
(Just x)
return
Prof. Dr. Margarita Esponda
>>=
>>=
f = Nothing
f = f x
= Just
22
Maybe-Monade
maybe
:: b -> (a -> b) -> Maybe a -> b
isJust
:: Maybe a -> Bool
isNothing
:: Maybe a -> Bool
fromJust
:: Maybe a -> a
fromMaybe
:: a -> Maybe a -> a
listToMaybe
:: [a] -> Maybe a
maybeToList :: Maybe a -> [a]
catMaybes
:: [Maybe a] -> [a]
mapMaybe
:: (a -> Maybe b) -> [a] -> [b]
Prof. Dr. Margarita Esponda
23
Maybe-Monade
msqrt :: Float -> Maybe Float
msqrt a | a<0 = Nothing
msqrt a = Just (sqrt a)
*Main> return 4
4
*Main> return 4 >>= msqrt
Just 2.0
*Main> return 4 >>= msqrt >>= msqrt
Just 1.4142135
*Main>
Prof. Dr. Margarita Esponda
24
Maybe-Monade
Beispiel:
sqrtMaybe :: Int -> [Maybe Int]
sqrtMaybe a | a >= 0 = [ Just ( round (sqrt (fromIntegral a))),
Just ( -round (sqrt (fromIntegral a))) ]
| otherwise = [Nothing]
hugs> sqrtMaybe 3
[Just 2,Just (-2)]
hugs>
sqrtMaybe (-4)
[Nothing]
Prof. Dr. Margarita Esponda
25
Motivation
Nehmen wir an, wir haben folgende zwei Funktionen:
f
:: a
-> b
g :: b
-> c
Wir können ohne weiteres eine Funktionskomposition h
definieren
h :: a -> c
h =g.f
Wenn wir ein Protokoll (trace) von den Funktionen haben
möchten, müssen wir den Typ der Funktionen f, g wie folgt
umdefinieren:
f :: a
-> ( b, String )
g :: b -> ( c, String )
Prof. Dr. Margarita Esponda
26
Motivation
f
:: a -> ( b, String )
g :: b -> ( c, String )
Die Funktion h muss wie folgt umdefiniert werden:
h x = let (res_f, trace_f) = f x
(res_g, trace_g) = g res_f
in ( res_g, trace_g ++ trace_f )
Prof. Dr. Margarita Esponda
27
Motivation
Eine allgemeine Funktion, die sich um diese Typanpassung
kümmert wäre viel besser:
bind :: (a, String) -> (a -> (b, String)) -> (b, String)
bind (x, s) f = let
(result_f, trace_f) = f x
in
( result_f, s ++ trace_f )
h x = f x `bind` g
f x
= ( x, "f_called" )
g x = ( x, "g_called" )
h x = f `bind` g `bind`
Prof. Dr. Margarita Esponda
(/x -> (x, "end…")
28
Was sind Monaden?
Monaden befreien den Programmierer von der Arbeit, den
Datentyp der Funktionen, die man kombiniert, anzupassen.
Sind eine Art Verpackung (Wrapper) für andere Datentypen.
Mit Monaden kann eine kontrollierte Pipeline zwischen
Funktionen gebildet werden.
Prof. Dr. Margarita Esponda
29
Monaden
Monaden bestehen im Allgemeinen aus:
-- einem Monaden-Typ M
data M a = ...
-- einem Typ-Konstruktor, der eine Monade erzeugt
return :: a -> M a
-- einer Verbindungsfunktion, die eine Art Pipeline zwischen
-- den Funktionen ermöglicht
(>>=) :: M a -> (a -> M b) -> M b
Prof. Dr. Margarita Esponda
30
Ein-/Ausgabe-Monaden
I/O-Monaden
Der Wert einer Ein-/Ausgabe-Monade (IO a) stellt eine
Aktion dar.
Wenn die Aktion ausgeführt wird, kann die Ein-/Ausgabe
erfolgen, bevor ein Ergebnis von Typ a zurückgeliefert
wird.
type IO a = World -> ( a, World )
Prof. Dr. Margarita Esponda
31
Ein-/Ausgabe-Monaden
ergebnis :: a
Welt vor
IO a
Welt nach
der Aktion
der Aktion
kein Rückgabewert
main :: IO ()
Die main-Funkion ist
eine Aktion von Typ-IO ()
main = putChar ‘x’
Prof. Dr. Margarita Esponda
32
do-Anweisung
unit
Char
getChar
putChar
main :: IO ()
main
()
Char
= do c <- getChar
Sequenzialisiert die
Ausführung der
Anweisungen
putChar 'c'
Prof. Dr. Margarita Esponda
33
Verbindung der Aktionen
()
Char
getChar
putChar
Um ein Zeichen zu lesen und wieder auszugeben, müssen wir die
Aktionen verbinden.
(>>=) :: IO a -> (a -> IO b) -> IO b
Verbindungsoperator
Prof. Dr. Margarita Esponda
34
(>>=) bind-Kombinator
•
verbindet das Ergebnis der linken Aktion mit der Aktion
auf der rechten Seite des Ausdrucks
•
(>>=) Funktionskomposition zwischen zwei Aktionen
•
Beispiel:
f >>= g
• führt die Funktion f aus und produziert das Ergebnis r1
• verwendet wiederum die Funktion g auf r1 und
produziert r2.
f
Prof. Dr. Margarita Esponda
r1
g
r2
35
(>>=) bind-Kombinator
Beispiel:
echoTwice :: IO ()
echoTwice = getChar >>= (\c ->putChar c >>= (\() -> putChar c ))
(>>) then-Kombinator
realisiert nur eine Sequenzialisierung ohne
Parameterübergabe
echoTwice :: IO ()
echoTwice = getChar >>= \c ->putChar c >> putChar c
Prof. Dr. Margarita Esponda
36
return-Kombinator
Die (return value) Aktion hat keine Ein-/Ausgabe und gibt
einen Wert zurück.
return :: a -> IO a
return
getTwoChars :: IO (Char, Char)
getTwoChars = getChar >>= \c1 -> getChar >>= \c2 -> return (c1,c2)
Prof. Dr. Margarita Esponda
37
do-Anweisung
-- ohne do-Anweisung
getTwoChars :: IO (Char,Char)
getTwoChars = getChar >>= \c1 ->
getChar >>= \c2 ->
return (c1,c2)
-- mit do-Anweisung
getTwoChars :: IO(Char,Char)
getTwoChars = do { c1 <- getChar ;
c2 <- getChar ;
return (c1,c2) }
Prof. Dr. Margarita Esponda
38
do-Anweisung
Folgende Anweisungen sind äquivalent:
do { x1 <- p1; ...; xn <- pn; q }
do
x1 <- p1; ...; xn <- pn; q
do x1 <- p1
...
xn <- pn
q
Prof. Dr. Margarita Esponda
mit Einrücken
39
do-Anweisung
getLine
:: IO String
getLine
=
do
c <- getChar
if c == '\n'
then return ""
else do line <- getLine
return (c: line)
Prof. Dr. Margarita Esponda
40
Monaden
Die Monad-Klasse
class Monad m where
(>>=) :: m a -> (a -> m b) -> m b
(>>) :: m a -> m b -> m b
fail :: String -> m a
return :: a -> m a
Prof. Dr. Margarita Esponda
41
Herunterladen