09 - Theoretische Informatik

Werbung
Funktoren I/O Monadische Funktionen
Einführung in die Funktionale
Programmierung mit Haskell
Input/Output
Steffen Jost
LFE Theoretische Informatik, Institut für Informatik,
Ludwig-Maximilians Universität, München
20. Juni 2013
Steffen Jost
Einführung in die Funktionale Programmierung mit Haskell
Funktoren I/O Monadische Funktionen
Planung
Freitag 21.Juni: keine Übung
Mittwoch 26.Juni: Übung statt Vorlesung
Klausur: 19.7. 10h
Vorläufige Planung:
Mi Vorlesung
Do Vorlesung
17. Apr 13
18. Apr 13
24. Apr 13
25. Apr 13
1. Mai 13
2. Mai 13
8. Mai 13
9. Mai 13
15. Mai 13
16. Mai 13
22. Mai 13
23. Mai 13
29. Mai 13
30. Mai 13
5. Jun 13
6. Jun 13
12. Jun 13
13. Jun 13
19. Jun 13
20. Jun 13
26. Jun 13
27. Jun 13
3. Jul 13
4. Jul 13
10. Jul 13
11. Jul 13
17. Jul 13
18. Jul 13
Fr Übung
19. Apr 13
26. Apr 13
3. Mai 13
10. Mai 13
17. Mai 13
24. Mai 13
31. Mai 13
7. Jun 13
14. Jun 13
21. Jun 13
28. Jun 13
5. Jul 13
12. Jul 13
19. Jul 13
Steffen Jost
Sollumfang:
Raumbuchung:
3+2
4+2
42 Termine, davon
3 Feiertage
4 Übung nachholen
6 Entfällt
1 Klausurtermin
= 20V + 12Ü
Einführung in die Funktionale Programmierung mit Haskell
Funktoren I/O Monadische Funktionen
Typklasse Functor
Im Module Data.Functor findet sich folgende Definition:
class Functor f where
fmap :: (a -> b) -> f a -> f b
Damit können wir ganz generisch Funktionen anwenden, deren
Argumenttyp eigentlich in einen anderen Typ eingewickelt wurde:
data Maybe a = Nothing | Just a
instance Functor Maybe where
fmap f (Just x) = Just (f x)
fmap _ Nothing = Nothing
> fmap (+1) (Just 2)
Just 3
> fmap (+1) Nothing
Nothing
Steffen Jost
Einführung in die Funktionale Programmierung mit Haskell
Funktoren I/O Monadische Funktionen
Typklasse Functor
Im Module Data.Functor findet sich folgende Definition:
class Functor f where
fmap :: (a -> b) -> f a -> f b
Damit können wir ganz generisch Funktionen anwenden, deren
Argumenttyp eigentlich in einen anderen Typ eingewickelt wurde:
data Maybe a = Nothing | Just a
instance Functor Maybe where
fmap f (Just x) = Just (f x)
fmap _ Nothing = Nothing
Der Parameter f der Typklasse Functor steht also nicht für einen
konkreten Typ wie z.B. Maybe Int, sondern für einen
Typkonstruktor wie z.B. Maybe.
Steffen Jost
Einführung in die Funktionale Programmierung mit Haskell
Funktoren I/O Monadische Funktionen
Funktoren Beispiele
class Functor f where
fmap :: (a -> b) -> f a -> f b
(<$>) :: (a -> b) -> f a -> f b
(<$>) = fmap
-- nur ein Synonym in Infix Notation
Wir können Funktion auch punktweise auf Listen anwenden:
instance Functor [] where
fmap = map
oder auch auf Bäume:
data Tree a = Leaf a | Node (Tree a) a (Tree a)
instance Functor Tree where
fmap f (Leaf a)
= Leaf (f a)
fmap f (Node l a r) = Node (f <$> l) (f a) (f <$> r)
Für die Funktion fmap gibt es auch das Infix Synonym <$>
Steffen Jost
Einführung in die Funktionale Programmierung mit Haskell
Funktoren I/O Monadische Funktionen
Funktoren
In der Mathematik ist ein Funktor eine strukturerhaltende
Abbildung zwischen zwei Kategorien.
fmap :: (a -> b) -> f a -> f b
In Haskell nimmt fmap eine Abbildung von Typ a nach Typ b und
liefert eine Abbildung auf Typen, auf welche jeweils der
Typkonstruktor f angewendet wurde.
Man sagt auch fmap f wendet die Funktion f punktweise an.
Ein Funktor sollte folgende Gesetze erfüllen:
1
Identität: fmap id == id
2
Komposition: fmap f . fmap g == fmap (f.g)
Haskell kann diese Gesetze leider jedoch nicht erzwingen, dass
muss der Programmierer alleine sicherstellen.
Steffen Jost
Einführung in die Funktionale Programmierung mit Haskell
Funktoren I/O Monadische Funktionen
Grundidee Main IO-Funktionen Datei Operationen
I/O vs. Haskell
Rein Funktionale Welt:
Haskell Funktionen sind rein und kennen keine Seiteneffekte.
⇒ Ein Aufruf von fmap löscht nicht die Festplatte.
⇒ Funktionen liefern das gleiche Ergebnis für gleiche Argumente.
⇒ Haskell Code ist leichter zu verstehen und sehr modular.
versus
Input/Output:
Input/Output besteht nur aus Seiteneffekten!
Eine Funktion, welche einen String auf den Bildschirm ausgibt
hat nur den Seiteneffekt, den Bildschirm zu verändern.
Schlimmer: Funktionen, welche von Tastatur einlesen, haben
keine Argumente und liefert oft andere Ergebnisse!
Noch schlimmer: I/O-Funktionen sollen die Festplatte löschen
können!
Steffen Jost
Einführung in die Funktionale Programmierung mit Haskell
Funktoren I/O Monadische Funktionen
Grundidee Main IO-Funktionen Datei Operationen
Haskell’s I/O
Haskell versucht nicht das Rad neu zu erfinden:
Input/Output ist am einfachsten im imperativen Stil
durchzuführen, auch in Haskell!
Haskell verrät seine Grundprinzipien dennoch nicht:
Bei allem schmutzigen ein/aus bleibt Haskell trotzdem rein!
Haskell macht es mit Monade:
Die Lösung liegt im Konzept der Monade. Dieses Konzept finden
viele schwierig, weshalb wir uns zuerst an die Verwendung von
Monaden im Speziallfall der IO-Monade gewöhnen wollen, bevor
wir das abstrakte Konzept untersuchen.
Steffen Jost
Einführung in die Funktionale Programmierung mit Haskell
Funktoren I/O Monadische Funktionen
Grundidee Main IO-Funktionen Datei Operationen
Haskell verändert die Welt:
Damit eine rein Funktionale Ein-/Ausgabe Funktion die Welt
verändern kann, muss die Welt als Argument übergeben werden.
Die veränderte Welt wird von der Funktion als Ergebnis mit
zurückgegeben:
type IO a = Welt -> (a,Welt)
putStr
getLine
:: String -> IO ()
::
IO String
-- String -> Welt -> ((),Welt)
-Welt -> (String,Welt)
Damit ist schon mal klar, das die Reihenfolge mehrerer
I/O-Funktionen eingehalten werden muss: Die veränderte Welt der
einen Funktion, wird immer weiter an die nächste Übergeben!
Hinweis:
Der Typkonstruktor IO ist nicht wirklich so definiert, aber man
könnte es so definieren — und wir können es uns so vorstellen!
Steffen Jost
Einführung in die Funktionale Programmierung mit Haskell
Funktoren I/O Monadische Funktionen
Grundidee Main IO-Funktionen Datei Operationen
Übungsaufgabe
Schreiben Sie zur Übung die beiden Funktionen nacheinander
und komposition mit folgenden Typsignaturen:
type Welt = ()
type IO a = Welt -> (a,Welt)
nacheinander :: IO a -> IO b -> IO b
komposition :: IO a -> (a -> IO b) -> IO b
Nacheinander führt einfach zwei IO-Aktionen hintereinander aus.
Dazu muss aus dem Ergebnis der ersten Aktion die verändert Welt
ausgepackt und als Argument für die zweite Aktion übergeben
werden. Man muss die Welt also durchfädeln (engl. threading).
Steffen Jost
Einführung in die Funktionale Programmierung mit Haskell
Funktoren I/O Monadische Funktionen
Grundidee Main IO-Funktionen Datei Operationen
DO-Notation
Um mehrere IO-Aktionen hintereinander auszuführen, gibt es in
Haskell die imperativ anmutende DO-Notation:
voo :: IO ()
voo = do
putStr
"Hallo "
putStrLn "Welt!"
In jeder Zeile darf eine IO-Aktion stehen.
Eine “IO-Aktion” ist ein Ausdruck des Typs IO a.
Der Typparameter für IO darf sich von Zeile zu Zeile
unterscheiden; also z.B. IO Int, IO Bool,. . .
DO-Ausdrücke sind durch Einrückung begrenzt: steht etwas
weiter links als die erste IO-Aktion, ist der DO-Block beendet.
Gesamter DO-Ausdruck hat den Typ des letzten Ausdrucks.
Steffen Jost
Einführung in die Funktionale Programmierung mit Haskell
Funktoren I/O Monadische Funktionen
Grundidee Main IO-Funktionen Datei Operationen
DO-Notation (2)
Alle IO-Aktionen haben ein Ergebnis (neben der veränderten Welt):
Eine IO-Aktion des Typs IO a liefert einen Wert des Typs a.
Rückgabewerte können wir mit Rückpfeil <- an Variablen binden:
voo :: IO String
voo = do putStrLn "Ihr Name bitte: "
name <- getLine
_ <- putStr "Sie heissen "
putStr name
putStrLn "?"
getLine
Wenn uns das Ergebnis der IO-Aktion egal ist, dann können wir
den Rückpfeil auch einfach weglassen.
Nur bei der letzten Aktion dürfen wir keinen Rückpfeil verwenden
— das Ergebnis ist ja das Ergebnis der Funktion voo.
Steffen Jost
Einführung in die Funktionale Programmierung mit Haskell
Funktoren I/O Monadische Funktionen
Grundidee Main IO-Funktionen Datei Operationen
DO-Notation (3)
Falls eine IO-Aktion Argumente verlangt, z.B.
putStrLn :: String -> IO ()
so können wir diese normal funktional bearbeiten:
voo :: IO String
voo =
do
putStrLn "Ihr Name bitte: "
name <- getLine
putStrLn ("Sie also heissen " ++ name
++ " ?" ++ sicher)
getLine
where
sicher = "\nSind Sie sich sicher?"
Ein DO-Block ist wirklich nur ein Ausdruck des Typs IO a!
’\n’ :: Char ist das Zeichen für Zeilchenvorschub
Steffen Jost
Einführung in die Funktionale Programmierung mit Haskell
Funktoren I/O Monadische Funktionen
Grundidee Main IO-Funktionen Datei Operationen
DO-Notation (3)
Falls eine IO-Aktion Argumente verlangt, z.B.
putStrLn :: String -> IO ()
so können wir diese normal funktional bearbeiten:
voo :: IO String
voo = let sicher = "\nSind Sie sich sicher?" in
do
putStrLn "Ihr Name bitte: "
name <- getLine
putStrLn $ "\Sie also heissen " ++ name
++ " ?" ++ sicher)
getLine
Ein DO-Block ist wirklich nur ein Ausdruck des Typs IO a!
’\n’ :: Char ist das Zeichen für Zeilchenvorschub
Steffen Jost
Einführung in die Funktionale Programmierung mit Haskell
Funktoren I/O Monadische Funktionen
Grundidee Main IO-Funktionen Datei Operationen
DO-Notation (4)
Ähnlich zu List-Comprehensions, können wir mit let auch wieder
rein funktionale Abkürzungen definieren:
voo :: IO String
voo = do
let begrüssung = "Ihr Name bitte: "
putStr begrüssung
name <- getLine
let frage = "Sie also heissen " ++
++ (map Data.Char.toUpper name)
++ " ?" ++ sicher
sicher = "\nSind Sie sich sicher?"
putStr frage
getLine
let ist wieder durch Einrückung begrenzt
Sowohl let als auch <- können Pattern-Matching verwenden
– genau wie in List-Comprehensions auch
Steffen Jost
Einführung in die Funktionale Programmierung mit Haskell
Funktoren I/O Monadische Funktionen
Grundidee Main IO-Funktionen Datei Operationen
Main Methode
Bisher haben wir Haskell nur im Interpreter laufen lassen. Wenn
wir eine ausführbare Datei erstellen wollen, dann braucht unser
Haskell Programm eine Funktion mit dem Namen main:
helloworld.hs:
main = putStrLn "Hello World!"
> ghc helloworld.hs
[1 of 1] Compiling Main
Linking helloworld ...
> ./helloworld
Hello World!
( helloworld.hs, helloworld.o )
Tipp: Besteht ein Programm aus mehreren Modulen, dann sollte
die Option --make angegeben werden, damit alle notwendigen
Module ebenfalls gleich kompiliert werden.
Steffen Jost
Einführung in die Funktionale Programmierung mit Haskell
Funktoren I/O Monadische Funktionen
Grundidee Main IO-Funktionen Datei Operationen
Main Methode
Die main Funktion hat meist den Typ IO ()
Natürlich können wir auch in der Funktion main mehrere
IO-Aktionen durchführen:
main = do
putStrLn "Psst! Wie heisst Du?"
name <- getLine
putStrLn $ "Hey " ++ (map Data.Char.toUpper) name
voo :: String -> IO ()
voo name = putStrLn $ name ++ " ist doof!"
Nur main verfügt über die Welt. Die IO-Aktionen in voo sind ohne
Wirkung, da main die Funktion voo nicht aufruft!
Steffen Jost
Einführung in die Funktionale Programmierung mit Haskell
Funktoren I/O Monadische Funktionen
Grundidee Main IO-Funktionen Datei Operationen
Main Methode
Wenn main aber voo aufruft, dann werden dessen Aktionen an der
entsprechenden Stelle ausgeführt. Natürlich kann voo weitere
Funktionen mit Typ IO a aufrufen.
main = do
putStrLn "Psst! Wie heisst Du?"
name <- getLine
voo name
putStrLn $ "Hey " ++ (map Data.Char.toUpper) name
voo :: String -> IO ()
voo name = putStrLn $ name ++ " ist doof!"
In jedem Falle gilt:
Man kann am Typ einer Funktion erkennen ob diese Seiteneffekte
haben kann – und wie wir später sehen werden kann der Typ auch
Einschränken welche Art Seiteneffekte.
Steffen Jost
Einführung in die Funktionale Programmierung mit Haskell
Funktoren I/O Monadische Funktionen
Grundidee Main IO-Funktionen Datei Operationen
Trennung der Welten
Es empfiehlt sich jedoch sehr dringend, funktionalen Code von
I/O-Code so weit wie möglich zu trennen:
main = do
line <- getLine
if null line
then putStrLn "(no input)"
else do
putStrLn $ reverseWords line
main
reverseWords :: String -> String
reverseWords = unwords . map reverse . words
DO-Ausdrücke können wie alle anderen Ausdrücke überall
auftauchen, wo Ihr Typ gefragt ist.
Auch IO-Aktionen können Rekursion nutzen
Steffen Jost
Einführung in die Funktionale Programmierung mit Haskell
Funktoren I/O Monadische Funktionen
Grundidee Main IO-Funktionen Datei Operationen
Ausgabe
putChar :: Char -> IO ()
Gibt ein einzelnes Zeichen aus.
putStr
:: String -> IO ()
putStrLn :: String -> IO () -- fügt ’\n’ ans Ende an
Gegeben einen String aus. Aufgrund der verzögerten
Auswertung kann es sein, dass nur komplette Zeilen
ausgegeben werden.
print
:: Show a => a -> IO ()
Gibt einen Wert der Typklasse Show aus.
Identisch zu putStrLn . show
Steffen Jost
Einführung in die Funktionale Programmierung mit Haskell
Funktoren I/O Monadische Funktionen
Grundidee Main IO-Funktionen Datei Operationen
GHCi und I/O
Der Interpreter GHCi erlaubt an der Eingabeaufforderung übrigens
auch beliebige IO-Aktionen.
In der Tat wird auf das Ergebnis einer jeden Eingabe ohnehin die
Funktion print angewendet:
> [1..5]
[1,2,3,4,5]
it :: [Integer]
> print [1..5]
[1,2,3,4,5]
it :: ()
Lediglich das Ergebnis ändert sich, da print ja den Typ
Show a => a -> IO () hat.
Steffen Jost
Einführung in die Funktionale Programmierung mit Haskell
Funktoren I/O Monadische Funktionen
Grundidee Main IO-Funktionen Datei Operationen
Eingabe
getChar :: IO Char Liest ein einzelnes Zeichen ein.
getLine :: IO String
Liest so lange ein, bis ein Zeilchenvorschub durch drücken der
Return-Taste erkannt wird.
getContents :: IO String
Liest alles ein, was der Benutzer jemals eingeben wird
(oder bis ein Dateiende-Zeichen (Ctrl-D) erkannt wird )
interact :: (String -> String) -> IO ()
Verarbeitet den gesamten Input mit der gegebenen Funktion
und gibt das Ergebnis zeilenweise aus.
Steffen Jost
Einführung in die Funktionale Programmierung mit Haskell
Funktoren I/O Monadische Funktionen
Grundidee Main IO-Funktionen Datei Operationen
getContents
Die Funktion getContents :: IO String liest die gesamte
Benutzereingabe auf einmal ein. Aufgrund der verzögerten
Auswertung von Haskell macht dies Sinn:
import Data.Char
main = do
input <- getContents
let shorti
= shortLinesOnly input
bigshort = map toUpper shorti
putStr bigshort
shortLinesOnly :: String -> String
shortLinesOnly = unlines . shortfilter . lines
where
shortfilter = filter (\line -> length line < 11)
Steffen Jost
Einführung in die Funktionale Programmierung mit Haskell
Funktoren I/O Monadische Funktionen
Grundidee Main IO-Funktionen Datei Operationen
interact
Die Funktion interact :: (String -> String) -> IO ()
erlaubt uns, dies noch knapper auszudrücken:
import Data.Char
main = interact mangleinput
where
mangleinput = (map toUpper) . shortLinesOnly
shortLinesOnly :: String -> String
shortLinesOnly = unlines . shortfilter . lines
where
shortfilter = filter (\line -> length line < 11)
Steffen Jost
Einführung in die Funktionale Programmierung mit Haskell
Funktoren I/O Monadische Funktionen
Grundidee Main IO-Funktionen Datei Operationen
DO-Notation (5)
Nachtrag
Aufgrund der letzten Übungserfahrung wollen wir noch mal den
Unterschied zwischen <- und let verdeutlichen.
Was gibt dieses Programm aus?
main = do putStrLn “A:“
a2 <- getLine
b1 <-putStrLn “B“
let b2 = getLine
let c1 = putStrLn “C“
c2 <- getLine
putStrLn $ “A:“++a2++“ B:“++b2++“ C:“++c2
Steffen Jost
Einführung in die Funktionale Programmierung mit Haskell
Funktoren I/O Monadische Funktionen
Grundidee Main IO-Funktionen Datei Operationen
DO-Notation (5)
Nachtrag
Aufgrund der letzten Übungserfahrung wollen wir noch mal den
Unterschied zwischen <- und let verdeutlichen.
Was gibt dieses Programm aus?
main = do putStrLn “A:“
a2 <- getLine
b1 <-putStrLn “B“
let b2 = getLine
let c1 = putStrLn “C“
c2 <- getLine
c1
b2 <- b2
putStrLn $ “A:“++a2++“ B:“++b2++“ C:“++c2
Antwort: A:a B:c C:b
<- führt IO-Aktion sofort durch.
let führt keine Aktion durch, kürzt nur ab.
Steffen Jost
Einführung in die Funktionale Programmierung mit Haskell
Funktoren I/O Monadische Funktionen
Grundidee Main IO-Funktionen Datei Operationen
Dateizugriff
Das Modul System.IO bietet Varianten der IO-Funktionen für den
Zugriff auf verschiedene Ein-/Ausgabegeräte an.
Die Varianten erwarten ein zusätzliche Argument des Typs Handle:
hPutStr
hPutStrLn
hPrint
hGetLine
hGetContents
::
Handle ->
::
Handle ->
:: Show a => Handle ->
::
Handle ->
::
Handle ->
String -> IO ()
String -> IO ()
a -> IO ()
IO String
IO String
Das Modul exportiert auch stdin,stdout,stderr :: Handle.
Es gilt:
putStr = hPutStr stdout
getLine = hGetLine stdin
Steffen Jost
Einführung in die Funktionale Programmierung mit Haskell
Funktoren I/O Monadische Funktionen
Grundidee Main IO-Funktionen Datei Operationen
Datei Handles
Handles kann man auf verschiedene Arten bekommen:
readFile
:: FilePath -> IO String.
writeFile :: FilePath -> String -> IO ()
appendFile :: FilePath -> String -> IO ()
openFile
:: FilePath -> IOMode -> IO Handle
hClose
:: Handle -> IO ()
withFile :: FilePath -> IOMode -> (Handle -> IO a) -> IO a
type FilePath = String
⇒ Betriebssystem abhängig
writeFile löscht Datei beim Öffnen
data IOMode = ReadMode
| WriteMode | AppendMode
| ReadWriteMode deriving (Enum,Ord,Eq,Show,
withFile schließt die Datei in jedem Falle
Steffen Jost
Einführung in die Funktionale Programmierung mit Haskell
Funktoren I/O Monadische Funktionen
Grundidee Main IO-Funktionen Datei Operationen
Beispiele Dateizugriff:
Beispiel 1
import System.IO
import Data.Char
main = do
contents <- readFile "whisper.txt"
writeFile "shout.txt" (map toUpper contents)
Beispiel 2
main = do
hIn <- openFile "whisper.txt" ReadMode -- Öffnen
hOut <- openFile "shout.txt" WriteMode
-- Arbeiten
input <- hGetContents hIn
let biginput = map toUpper input
hPutStrLn hOut biginput
hClose hIn
-- Schliessen
hClose hOut
Steffen Jost
Einführung in die Funktionale Programmierung mit Haskell
Funktoren I/O Monadische Funktionen
Grundidee Main IO-Funktionen Datei Operationen
Beispiele Dateizugriff:
Beispiel 1
import System.IO
import Data.Char
main = do
contents <- readFile "whisper.txt"
writeFile "shout.txt" (map toUpper contents)
Beispiel 2
main = do
hIn <- openFile "whisper.txt" ReadMode -- Öffnen
hOut <- openFile "shout.txt" WriteMode
-- Arbeiten
input <- hGetContents hIn
let biginput = map toUpper input
hPutStrLn hOut biginput
hClose hIn
-- Schliessen
hClose hOut
Steffen Jost
Einführung in die Funktionale Programmierung mit Haskell
Funktoren I/O Monadische Funktionen
Grundidee Main IO-Funktionen Datei Operationen
Beispiele Dateizugriff:
Beispiel 3
main = do
withFile "something.txt" ReadMode (\handle -> do
contents1 <- hGetContents handle
let contents2 = foo contents1
putStr contents2
)
Dabei kann man sich withFile vorstellen als:
withFile’ :: FilePath -> IOMode -> (Handle -> IO a)
-> IO a
withFile’ path mode f = do
handle <- openFile path mode
result <- f handle
hClose handle
return result
Steffen Jost
Einführung in die Funktionale Programmierung mit Haskell
Funktoren I/O Monadische Funktionen
Grundidee Main IO-Funktionen Datei Operationen
Verfeinerter Zugriff
Positionieren
hGetPosn :: Handle -> IO HandlePosn
hSetPosn :: HandlePosn -> IO ()
hSeek :: Handle -> SeekMode -> Integer -> IO ()
Nicht alle Handle-Arten unterstützen Positionierung
Datentyp HandlePosn nur in Typklassen Eq und Show.
Kann man sich also nur merken und wiederverwenden.
Pufferung Man kann üblicherweise auch die Pufferung
beeinflussen. hFlush erzwingt das leeren des Puffers.
hSetBuffering :: Handle -> BufferMode -> IO ()
hGetBuffering :: Handle -> IO BufferMode
hFlush :: Handle -> IO ()
Warnung: Die vorgestellten Funktionen nur für einfach
I/O-Aufgaben verwenden. Bei großen Dateien bzw. Bechmarks (!)
sollte Modul Data.Bytestring verwendet werden. Nahezu
identische Funktionen mit Bytestring anstatt String.
Steffen Jost
Einführung in die Funktionale Programmierung mit Haskell
Funktoren I/O Monadische Funktionen
Monadische Funktionen
Wir stellen nun einige wichtige grundlegende Funktionen aus dem
Standardmodul Control.Monad vor.
Die Funktionen sind nicht nur für I/O nützlich, sondern allgemein
für beliebige Monaden. Für unsere Zwecke reicht aber erst einmal
die Verwendung innerhalb der I/O-Monade.
Den Typ foo :: Monad m => a -> m b lesen wir einfach als
foo ::
a -> IO b
Wir bemerken aber bereits, dass der Typklasse Monad lediglich
Typkonstruktoren angehören, ganz ähnlich wie bei Typklasse
Functor.
Typkonstruktoren: IO, Maybe, [], Tree, . . .
Steffen Jost
Einführung in die Funktionale Programmierung mit Haskell
Funktoren I/O Monadische Funktionen
return
Die Funktion return :: a -> IO a generiert eine “leere”
IO-Aktion, welche die Welt nicht verändert und das Argument
unverändert zurückgibt.
Dies ist nützlich, wenn wir von dem Typ her eine IO-Aktion
brauchen, aber eigentlich keine durchführen wollen:
main = do
line <- getLine
if null line
then return ()
else do
putStrLn $ reverseWords line
main
Steffen Jost
Einführung in die Funktionale Programmierung mit Haskell
Funktoren I/O Monadische Funktionen
return
Die Funktion return :: a -> IO a generiert eine “leere”
IO-Aktion, welche die Welt nicht verändert und das Argument
unverändert zurückgibt.
Es ist auch nützlich, wenn wir das Ergebnis einer anderen
IO-Aktion noch weiterverarbeiten wollen:
getUpperLine :: IO String
getUpperLine = do
line <- getLine
return $ map toUpper line
Bemerke:
Ausdruck map toUpper line hat den Typ String, wir brauchen
am Ende dieses DO-Blocks den Typ IO String
Steffen Jost
Einführung in die Funktionale Programmierung mit Haskell
Funktoren I/O Monadische Funktionen
Alle Monaden sind Funktoren
Damit ist auch sofort klar, wie wir den Typkonstruktor IO zum
Funktor machen können:
instance Functor IO where
fmap f action = do
result <- action
return (f result)
Wir führen die Aktion aus, und wenden danach die Funktion an.
Die “Welt” heben wir während der Anwendung von f auf und
geben sie am Ende mit return wieder dazu — eine rein
funktionale Funktion konnte die Welt ja nicht verändern!
getUpperLine = map toUpper <$> getLine
map toUpper :: String -> String
getLine :: IO String
<$>
::
(a -> b)
->
f a
->
f b
In der Tat sind alle Monaden auch Funktoren.
Steffen Jost
Einführung in die Funktionale Programmierung mit Haskell
Funktoren I/O Monadische Funktionen
Alle Monaden sind Funktoren
Damit ist auch sofort klar, wie wir den Typkonstruktor IO zum
Funktor machen können:
instance Functor IO where
fmap f action = do
result <- action
return (f result)
Wir führen die Aktion aus, und wenden danach die Funktion an.
Die “Welt” heben wir während der Anwendung von f auf und
geben sie am Ende mit return wieder dazu — eine rein
funktionale Funktion konnte die Welt ja nicht verändern!
getUpperLine = map toUpper <$> getLine
map toUpper :: String -> String
getLine :: IO String
<$>
::
(a -> b)
->
f a
->
f b
In der Tat sind alle Monaden auch Funktoren.
Steffen Jost
Einführung in die Funktionale Programmierung mit Haskell
Funktoren I/O Monadische Funktionen
when & unless
Bedingte Ausführung von IO-Aktionen erlauben uns
when
:: Bool -> IO () -> IO ()
unless :: Bool -> IO () -> IO ()
Die Funktion when führt die übergebene Aktion nur dann aus,
wenn das erste (funktionale) Argument zu True auswertet.
Bei der Funktion unless ist das umgekehrt. Sie führt die
übergebene Aktion nur dann aus, wenn das erste (funktionale)
Argument zu False auswertet.
Steffen Jost
Einführung in die Funktionale Programmierung mit Haskell
Funktoren I/O Monadische Funktionen
forever
Wir können eine IO-Aktion mit der Funktion forever bis zum
Programmabbruch wiederholen lassen:
forever :: IO a -> IO b
Beispiel
import Control.Monad
import Data.Char
main = forever $ do
putStr "Give me some input: "
l <- getLine
putStrLn $ map toUpper l
Steffen Jost
Einführung in die Funktionale Programmierung mit Haskell
Funktoren I/O Monadische Funktionen
sequence
Mehrere IO-Aktionen können wir hintereinander ausführen lassen:
sequence :: [IO a] -> IO [a]
sequence_ :: [IO a] -> IO ()
Beispiel
> sequence $ map print [1..5]
1
2
3
4
5
[(),(),(),(),()]
Wenn das aufgesammelte Ergebnis nicht interessiert, z.B. weil die
verwendeten IO-Aktion wie im Beispiel immer nur () zurückgeben,
dann können wir auch die Variante sequence_ verwenden.
Steffen Jost
Einführung in die Funktionale Programmierung mit Haskell
Funktoren I/O Monadische Funktionen
mapM
Wir können eine Sequenz von IO-Aktionen vorher auch noch
transformieren lassen:
mapM :: (a -> IO b) -> [a] -> IO [b]
mapM_ :: (a -> IO b) -> [a] -> IO ()
Dabei ist mapM f ist tatsächlich äquivalent zu sequence . map f
Beispiel
> mapM_ print [1..5]
1
2
3
4
5
()
Die Variante mapM_ verwirft lediglich das Endergebnis, d.h. nur die
Seiteneffekt interessiert uns.
Steffen Jost
Einführung in die Funktionale Programmierung mit Haskell
Funktoren I/O Monadische Funktionen
forM
Zum Abschluß noch ein (vielleicht) alter Bekannter:
forM :: [a] -> (a -> IO b) -> IO [b]
forM_ :: [a] -> (a -> IO b) -> IO ()
Beispiel
Man kann flip mapM und anonymen Funktionen
fremd-aussehende Programme schreiben:
import Control.Monad
main = do
colors <- forM [1,2,3,4] (\a -> do
putStrLn $ "Welche Farbe assoziierst Du mit "
++ show a ++ "?"
color <- getLine
return color)
putStrLn "Farbe der Zahlen 1, 2, 3 und 4 sind: "
mapM putStrLn colors
Steffen Jost
Einführung in die Funktionale Programmierung mit Haskell
Funktoren I/O Monadische Funktionen
Zusammenfassung
Haskell erlaubt I/O im imperativen Stil
Unter der Haube bleibt alles rein funktional dank Monaden
IO-Aktionen verändern den Zustand der Welt, welche
zwischen allen ausgeführten IO-Aktionen herumgereicht wird
IO-Aktionen sind Monaden.
Alle Monaden sind Funktoren.
Steffen Jost
Einführung in die Funktionale Programmierung mit Haskell
Herunterladen