Funktionale Programmierung - Spezielle Aspekte von Haskell

Werbung
Fehlerbehandlung
IO in Haskell
Funktionale Programmierung
Spezielle Aspekte von Haskell
D. Rösner
Institut für Wissens- und Sprachverarbeitung
Fakultät für Informatik
Otto-von-Guericke Universität Magdeburg
c
Sommer 2014, 2. Mai 2014, 2011-14
D.Rösner
D. Rösner
FP 2014 . . .
Fehlerbehandlung
IO in Haskell
Gliederung
1
Fehlerbehandlung
2
IO in Haskell
IO-Aktionen
do-Notation
IO-Aktionen erneut
Sequentialisierung
D. Rösner
FP 2014 . . .
Fehlerbehandlung
IO in Haskell
Umgang mit Fehlern
unterschiedliche Möglichkeiten (vgl. [Tho99], Ch. 14.4]):
Fehler beim Auftreten berichten und Programm beenden
im Fehlerfall dennoch einen (normalen) Wert zurückgeben
einen Fehlertyp (error type) verwenden
D. Rösner
FP 2014 . . .
Fehlerbehandlung
IO in Haskell
Umgang mit Fehlern cont.
Variante 1: Fehlerbericht und Programmabbruch
Funktion error
error :: String -> a
Beispiel:
Auswertung von error "Division durch Null!"
führt zu Programmabbruch und Ausgabe von
Program error: Division durch Null!
D. Rösner
FP 2014 . . .
Fehlerbehandlung
IO in Haskell
Umgang mit Fehlern cont.
Variante 2: Rückgabe eines gewöhnlichen Werts (dummy
value)
Was soll der Kopf einer leeren Liste sein?
hd :: a -> [a] -> a
hd y (x:_) = x
hd y []
= y
D. Rösner
FP 2014 . . .
Fehlerbehandlung
IO in Haskell
Umgang mit Fehlern cont.: Variante 2
allgemein: wenn unter Bedingung cond bei einstelliger
Funktion f ein Fehlerfall:
fErr y x
| cond
| otherwise
= y
= f x
Problem: Liegt Fehler oder normaler Wert vor?
D. Rösner
FP 2014 . . .
Fehlerbehandlung
IO in Haskell
Umgang mit Fehlern cont.
Variante 3: Wert vom Fehlertyp Maybe
Definition:
data Maybe = Nothing | Just a
deriving (Eq, Ord, Read, Show)
Beispiel:
errDiv :: Int -> Int -> Maybe Int
errDiv n m
| (m /= 0) = Just (n ‘div‘ m)
| otherwise = Nothing
D. Rösner
FP 2014 . . .
Fehlerbehandlung
IO in Haskell
Umgang mit Fehlern cont.
Variante 3
allgemein: wenn unter Bedingung cond bei einstelliger
Funktion f ein Fehlerfall:
fErr x
| cond
| otherwise
= Nothing
= Just (f x)
D. Rösner
FP 2014 . . .
Fehlerbehandlung
IO in Haskell
Fehlertyp Maybe
„Durchreichen“ eines Fehlerwerts bei
Funktionsanwendung:
mapMaybe :: (a ->
b) -> Maybe a -> Maybe b
mapMaybe g Nothing = Nothing
mapMaybe g (Just x) = Just (g x)
bei Funktionsanwendung Werte vom Typ Maybe durch
normale Rückgabe ersetzen:
maybe :: b -> (a ->
b) -> Maybe a -> b
maybe n f Nothing = n
maybe n f (Just x) = f x
D. Rösner
FP 2014 . . .
Fehlerbehandlung
IO in Haskell
Fehlertyp Maybe
Beispiele (vgl. [Tho99], Ch. 14.4]):
maybe 42 (1+) (mapMaybe (*3) (errDiv 9 0))
= ...
= ...
= ..
maybe 42 (1+) (mapMaybe (*3) (errDiv 9 1))
= ...
= ...
= ..
D. Rösner
FP 2014 . . .
Fehlerbehandlung
IO in Haskell
IO-Aktionen
do-Notation
IO-Aktionen erneut
Sequentialisierung
I/O in Haskell:
Warum ist I/O eine Herausforderung für Haskell?
I/O ist inhärent mit Seiteneffekten behaftet
daher kann I/O nicht im ’reinen’ Teil von Haskell erfolgen
I/O ist aber für Anwendungen unverzichtbar
Herausforderung: I/O so mit dem ’reinen’ Teil von Haskell
verbinden, dass letzterer nicht korrumpiert wird
relevante Literatur: s. u.a. [OGS09], Ch. 7, [Hud00], Ch.
3.1, [Tho99], Ch. 18
D. Rösner
FP 2014 . . .
Fehlerbehandlung
IO in Haskell
IO-Aktionen
do-Notation
IO-Aktionen erneut
Sequentialisierung
I/O in Haskell:
vordefinierte Funktionen für I/O aus dem Haskell prelude
show: Wandeln von bel. Werten (aus Typklasse Show) in
Strings
show :: Show a => a -> String
Prelude> show 3
"3"
Prelude> show [3, 4]
"[3,4]"
relevante Literatur: s. u.a. [Hud00], Ch. 3.1, [Tho99], Ch. 18
D. Rösner
FP 2014 . . .
Fehlerbehandlung
IO in Haskell
IO-Aktionen
do-Notation
IO-Aktionen erneut
Sequentialisierung
I/O in Haskell: cont.
getLine: Einlesen einer Zeile als String
getLine :: IO String
Prelude> getLine
eine Zeile eingelesen
Prelude> getLine
[a,b] ++ [c,d,e]
Prelude>
Was besagt der Typ von getLine?
D. Rösner
FP 2014 . . .
Fehlerbehandlung
IO in Haskell
IO-Aktionen
do-Notation
IO-Aktionen erneut
Sequentialisierung
I/O in Haskell: cont.
Typ IO a:
I/O-Aktionen vom Typ a, d.h.
ein Objekt vom Typ IO a steht für ein Programm, das I/O
durchführt und
einen Wert vom Typ a liefert
s.a. [OGS09], Ch. 7, [Hud00], Ch. 3.1, [Tho99], Ch. 18
D. Rösner
FP 2014 . . .
Fehlerbehandlung
IO in Haskell
IO-Aktionen
do-Notation
IO-Aktionen erneut
Sequentialisierung
I/O in Haskell: cont.
Spezialfall: IO ()
wenn nur die I/O-Aktionen, aber nicht der Rückgabewert
interessieren
der spezielle Haskell-Typ () enthält nur ein Element
(ebenfalls () geschrieben)
gesprochen: unit
Analogie: void in C oder Java
s.a. [OGS09], Ch. 7, [Hud00], Ch. 3.1, [Tho99], Ch. 18
D. Rösner
FP 2014 . . .
Fehlerbehandlung
IO in Haskell
IO-Aktionen
do-Notation
IO-Aktionen erneut
Sequentialisierung
I/O in Haskell: cont.
getChar: Einlesen eines einzelnen Zeichens von Eingabe
getChar :: IO Char
Prelude> getChar
a
Prelude> getChar
*
Prelude> getChar
^
Prelude>
D. Rösner
FP 2014 . . .
Fehlerbehandlung
IO in Haskell
IO-Aktionen
do-Notation
IO-Aktionen erneut
Sequentialisierung
I/O in Haskell: cont.
putStr: Schreiben eines Strings auf Ausgabe
putStr :: String -> IO ()
Prelude> putStr "das ist die Ausgabe"
das ist die Ausgabe
Prelude> putStr ("Zeile 1 gefolgt von" ++ "\n"
++ "Zeile 2")
Zeile 1 gefolgt von
Zeile 2
Prelude>
D. Rösner
FP 2014 . . .
Fehlerbehandlung
IO in Haskell
IO-Aktionen
do-Notation
IO-Aktionen erneut
Sequentialisierung
I/O in Haskell: cont.
durch Funktionskomposition lassen sich komplexere
I/O-Funktionen gewinnen
Beispiel: Ausgabe eines Strings auf eine abgeschlossene
Zeile
putStrLn :: String -> IO ()
putStrLn = putStr . (++ "\n")
Beispiel: Ausgabe eines bel. Objekts als String auf eine
abgeschlossene Zeile
print :: Show a => a -> IO ()
print = putStrLn . show
D. Rösner
FP 2014 . . .
Fehlerbehandlung
IO in Haskell
IO-Aktionen
do-Notation
IO-Aktionen erneut
Sequentialisierung
I/O in Haskell: die do-Notation
die do-Notation erlaubt
I/O-Aktionen zu sequentialisieren und
durch I/O-Aktionen zurückgegebene Werte zu erfassen,
mit reinen Funktionen zu verarbeiten und
Ergebnisse an andere Aktionen weiterzugeben
mögliche Sicht: do-Notation erlaubt Schreibweisen wie mit
einer auf Haskell aufgesetzten imperativen Sprache (mit
Kommandos und Zuweisungen), die aber das funktionale
Modell von Haskell nicht verletzt
s.a. [OGS09], Ch. 7, [Hud00], Ch. 3.1, [Tho99], Ch. 18
D. Rösner
FP 2014 . . .
Fehlerbehandlung
IO in Haskell
IO-Aktionen
do-Notation
IO-Aktionen erneut
Sequentialisierung
I/O in Haskell: die do-Notation
Beispiele:
einen String exakt 4 mal ausgeben
put4times :: String -> IO ()
put4times str = do putStrLn
putStrLn
putStrLn
putStrLn
D. Rösner
FP 2014 . . .
str
str
str
str
Fehlerbehandlung
IO in Haskell
IO-Aktionen
do-Notation
IO-Aktionen erneut
Sequentialisierung
I/O in Haskell: die do-Notation
Beispiele:
einen String n mal ausgeben
putNtimes :: Int -> String -> IO ()
putNtimes n str = if n <= 1
then putStrLn str
else do putStrLn str
putNtimes (n-1) str
put4times = putNtimes 4
D. Rösner
FP 2014 . . .
Fehlerbehandlung
IO in Haskell
IO-Aktionen
do-Notation
IO-Aktionen erneut
Sequentialisierung
I/O in Haskell: die do-Notation
innerhalb eines do können Ergebnisse von I/O benannt
und damit weiterverwendet werden
Syntax: name <- wert_IO_Aktion
Semantik: in nachfolgenden Ausdrücken kann mit name
der Wert der I/O-Aktion referenziert werden
D. Rösner
FP 2014 . . .
Fehlerbehandlung
IO in Haskell
IO-Aktionen
do-Notation
IO-Aktionen erneut
Sequentialisierung
I/O in Haskell: die do-Notation
Beispiel:
lies zwei Zeilen ein und gib sie in vertauschter Reihenfolge
und jeweils umgedreht aus
reverse2lines :: IO ()
reverse2lines = do line1 <line2 <putStrLn
putStrLn
D. Rösner
FP 2014 . . .
getLine
getLine
(reverse line2)
(reverse line1)
Fehlerbehandlung
IO in Haskell
IO-Aktionen
do-Notation
IO-Aktionen erneut
Sequentialisierung
I/O in Haskell: die do-Notation
mit return kann veranlasst werden, dass aus einem do
ein Wert eines bestimmten Typs zurückgegeben wird
beachte: der Rückgabewert ist eine I/O-Aktion
Beispiel:
lies zwei Zeilen ein und gib sie – mit einem Leerzeichen als
Separator – aneinandergehängt als einen String in einer
I/O-Aktion zurück
concat2lines :: IO String
concat2lines = do line1 <- getLine
line2 <- getLine
return (line1 ++ " " ++ line2)
concat2lines ist eine I/O-Aktion
D. Rösner
FP 2014 . . .
Fehlerbehandlung
IO in Haskell
IO-Aktionen
do-Notation
IO-Aktionen erneut
Sequentialisierung
I/O in Haskell: cont.
Wie ist das Verhältnis von reinem Code zu I/O-Aktionen?
I/O-Aktionen
sind als Daten Werte erster Ordnung (first-class values) in
Haskell
sind nahtlos in das Typsystem von Haskell integriert
können in aggregierten Datentypen verwendet und dann
mit reinem Code manipuliert werden
produzieren ihre Effekte nur, wenn sie ausgeführt werden,
nicht aber wenn sie evaluiert werden
können nur ausgeführt werden innerhalb anderer (gerade
ausgeführter) I/O-Aktionen (oder auf oberster Ebene:
main)
main ist ebenfalls IO-Aktion vom Typ IO ()
s.a. [OGS09], Ch. 7
D. Rösner
FP 2014 . . .
Fehlerbehandlung
IO in Haskell
IO-Aktionen
do-Notation
IO-Aktionen erneut
Sequentialisierung
I/O in Haskell: cont.
Wie ist das Verhältnis von reinem Code zu I/O-Aktionen?
reiner Code kann innerhalb von IO-Aktionen ausgeführt
werden
umgekehrt gilt das nicht:
I/O-Aktionen können nicht innerhalb von reinem Code
ausgeführt werden
I/O-Aktionen können nur ausgeführt werden innerhalb
anderer (gerade ausgeführter) I/O-Aktionen
durch diese Isolation kann reiner Code nicht ’kontaminiert’
werden
s.a. [OGS09], Ch. 7
D. Rösner
FP 2014 . . .
Fehlerbehandlung
IO in Haskell
IO-Aktionen
do-Notation
IO-Aktionen erneut
Sequentialisierung
I/O-Aktionen in Haskell: cont.
Beispiel
-- file: ch07/actions.hs
str2action :: String -> IO ()
str2action input = putStrLn ("Data: " ++ input)
list2actions :: [String] -> [IO ()]
list2actions = map str2action
...
[OGS09], Ch. 7, pp. 184
D. Rösner
FP 2014 . . .
Fehlerbehandlung
IO in Haskell
IO-Aktionen
do-Notation
IO-Aktionen erneut
Sequentialisierung
I/O-Aktionen in Haskell: cont.
Beispiel (cont.)
...
numbers :: [Int]
numbers = [1..10]
strings :: [String]
strings = map show numbers
actions :: [IO ()]
actions = list2actions strings
...
[OGS09], Ch. 7, pp. 184
D. Rösner
FP 2014 . . .
Fehlerbehandlung
IO in Haskell
IO-Aktionen
do-Notation
IO-Aktionen erneut
Sequentialisierung
I/O-Aktionen in Haskell: cont.
Beispiel (cont.)
...
printitall :: IO ()
printitall = runall actions
-- Take a list of actions, and execute each of them in turn.
runall :: [IO ()] -> IO ()
runall [] = return ()
runall (firstelem:remainingelems) =
do firstelem
runall remainingelems
main = do str2action "Start of the program"
printitall
str2action "Done!"
[OGS09], Ch. 7, pp. 184
D. Rösner
FP 2014 . . .
Fehlerbehandlung
IO in Haskell
IO-Aktionen
do-Notation
IO-Aktionen erneut
Sequentialisierung
I/O-Aktionen in Haskell: cont.
Beispiel (kompaktere Version)
-- file: ch07/actions2.hs
str2message :: String -> String
str2message input = "Data: " ++ input
str2action :: String -> IO ()
str2action = putStrLn . str2message
numbers :: [Int]
numbers = [1..10]
main = do str2action "Start of the program"
mapM_ (str2action . show) numbers
str2action "Done!"
[OGS09], Ch. 7, pp. 184
D. Rösner
FP 2014 . . .
Fehlerbehandlung
IO in Haskell
IO-Aktionen
do-Notation
IO-Aktionen erneut
Sequentialisierung
I/O-Aktionen in Haskell: cont.
Warum mapM_?
map ist eine reine Funktion
map kann daher keine Aktionen ausführen
*Main> :t str2action . show
str2action . show :: Show a => a -> IO ()
mapM_ kann eine Liste von Aktionen ausführen
bei Variante mapM würden die Ergebnisse in einer Liste
zusammengefasst
[OGS09], Ch. 7, pp. 184
D. Rösner
FP 2014 . . .
Fehlerbehandlung
IO in Haskell
IO-Aktionen
do-Notation
IO-Aktionen erneut
Sequentialisierung
I/O-Aktionen in Haskell: cont.
mapM_ und mapM gelten für beliebige Instanzen der Klasse
Monad
Typ von mapM:
ghci> :type mapM
mapM :: (Monad m) => (a -> m b) -> [a] -> m [b]
Typ von mapM_:
ghci> :type mapM_
mapM_ :: (Monad m) => (a -> m b) -> [a] -> m ()
hier: IO-Aktionen gehören zur Monade IO
[OGS09], Ch. 7, pp. 184
D. Rösner
FP 2014 . . .
Fehlerbehandlung
IO in Haskell
IO-Aktionen
do-Notation
IO-Aktionen erneut
Sequentialisierung
I/O-Aktionen in Haskell: cont.
im Beispiel sind sowohl map, als auch mapM und mapM_
anwendbar, aber mit Ergebnissen unterschiedlichen Typs
reiner Code:
*Main> :t map (str2action . show)
map (str2action . show) :: Show a => [a] -> [IO ()]
monadischer Code:
*Main> :t mapM_ (str2action . show) numbers
mapM_ (str2action . show) numbers :: IO ()
monadischer Code:
*Main> :t mapM (str2action . show) numbers
mapM (str2action . show) numbers :: IO [()]
[OGS09], Ch. 7, pp. 185
D. Rösner
FP 2014 . . .
Fehlerbehandlung
IO in Haskell
IO-Aktionen
do-Notation
IO-Aktionen erneut
Sequentialisierung
I/O-Aktionen in Haskell: cont.
mapM_
*Main> mapM_ (str2action . show) numbers
Data: 1
Data: 2
Data: 3
Data: 4
Data: 5
Data: 6
Data: 7
Data: 8
Data: 9
Data: 10
*Main> it
()
D. Rösner
FP 2014 . . .
Fehlerbehandlung
IO in Haskell
IO-Aktionen
do-Notation
IO-Aktionen erneut
Sequentialisierung
I/O-Aktionen in Haskell: cont.
mapM
*Main> mapM (str2action . show) numbers
Data: 1
Data: 2
Data: 3
Data: 4
Data: 5
Data: 6
Data: 7
Data: 8
Data: 9
Data: 10
[(),(),(),(),(),(),(),(),(),()]
*Main> it
[(),(),(),(),(),(),(),(),(),()]
*Main> length it
10
D. Rösner
FP 2014 . . .
Fehlerbehandlung
IO in Haskell
IO-Aktionen
do-Notation
IO-Aktionen erneut
Sequentialisierung
I/O-Aktionen in Haskell: cont.
map
*Main> map (str2action . show) numbers
<interactive>:33:1:
No instance for (Show (IO ()))
arising from a use of ‘print’
Possible fix: add an instance declaration for (Show (IO ()))
In a stmt of an interactive GHCi command: print it
m.a.W. (Elemente der Liste der) IO-Aktionen nicht in Show
aber mit reinen Funktionen verarbeitbar:
*Main> length $ map (str2action . show) numbers
10
D. Rösner
FP 2014 . . .
Fehlerbehandlung
IO in Haskell
IO-Aktionen
do-Notation
IO-Aktionen erneut
Sequentialisierung
Operatoren zur Sequentialisierung
do-Notation ist ’syntaktischer Zucker’
statt mit do-Blöcken kann in monadischem Code mit zwei
Operatoren zur Sequentialisierung gearbeitet werden
», sprich: . . . sequence . . .
»=, sprich: . . . bind . . . bzw. . . . then . . .
mit » werden zwei Aktionen nacheinander ausgeführt; der
Wert ist das Resultat der zweiten Aktion, das Resultat der
ersten Aktion wird ignoriert
mit »= wird eine Aktion ausgeführt, dann deren Ergebnis
an eine Funktion weitergereicht, die eine zweite Aktion
produziert; diese wird ebenfalls ausgeführt, der Wert ist
das Resultat der zweiten Aktion
[OGS09], Ch. 7, pp. 186
D. Rösner
FP 2014 . . .
Fehlerbehandlung
IO in Haskell
IO-Aktionen
do-Notation
IO-Aktionen erneut
Sequentialisierung
Operatoren zur Sequentialisierung
Beispiel (Version mit do-Block)
-- file: ch07/basicio.hs
main = do
putStrLn "Greetings! What is your name?"
inpStr <- getLine
putStrLn $ "Welcome to Haskell, " ++ inpStr ++ "!"
[OGS09], Ch. 7, pp. 186
D. Rösner
FP 2014 . . .
Fehlerbehandlung
IO in Haskell
IO-Aktionen
do-Notation
IO-Aktionen erneut
Sequentialisierung
Operatoren zur Sequentialisierung
Beispiel (Version ohne do-Block)
-- file: ch07/basicio-nodo.hs
main =
putStrLn "Greetings! What is your name?" >>
getLine >>=
(\inpStr -> putStrLn $ "Welcome to Haskell, " ++ inpStr ++ "!")
der Haskell-Compiler übersetzt do-Blöcke in analoger
Weise
[OGS09], Ch. 7, pp. 187
D. Rösner
FP 2014 . . .
Fehlerbehandlung
IO in Haskell
IO-Aktionen
do-Notation
IO-Aktionen erneut
Sequentialisierung
Operatoren zur Sequentialisierung
Ausführen einer Liste von Aktionen durch Einfalten von »
*Main> foldr1 (>>) $ map (str2action . show) numbers
Data: 1
Data: 2
Data: 3
Data: 4
Data: 5
Data: 6
Data: 7
Data: 8
Data: 9
Data: 10
analog für foldl1
D. Rösner
FP 2014 . . .
Fehlerbehandlung
IO in Haskell
IO-Aktionen
do-Notation
IO-Aktionen erneut
Sequentialisierung
Operatoren zur Sequentialisierung
Verwendung von foldr bzw. foldl
*Main> foldr (>>) (return ()) $ map (str2action . show) numbers
Data: 1
Data: 2
Data: 3
Data: 4
Data: 5
Data: 6
Data: 7
Data: 8
Data: 9
Data: 10
beachte: return () als ’leere IO-Aktion’
D. Rösner
FP 2014 . . .
Fehlerbehandlung
IO in Haskell
IO-Aktionen
do-Notation
IO-Aktionen erneut
Sequentialisierung
Operatoren zur Sequentialisierung
wie immer: die kreierte IO-Aktion muss ausgeführt werden
*Main> let bas = foldr (>>) (return ()) $ map (str2action . show)
*Main> bas
Data: 1
Data: 2
Data: 3
Data: 4
Data: 5
Data: 6
Data: 7
Data: 8
Data: 9
Data: 10
*Main> :t bas
bas :: IO ()
D. Rösner
FP 2014 . . .
Fehlerbehandlung
IO in Haskell
IO-Aktionen
do-Notation
IO-Aktionen erneut
Sequentialisierung
Die Funktion return
viele Programmiersprachen haben ein Schlüsselwort
return, um Berechnungen zu beenden und einen Wert
an die rufende Funktion zurückzugeben
die Haskell-Funktion return ist davon sehr verschieden
sie dient dazu, reine Daten in eine Monade (hier: eine
IO-Aktion) einzubringen
*Main> :t return
return :: Monad m => a -> m a
besserer Name wäre z.B. inject
[OGS09], Ch. 7, pp. 187
D. Rösner
FP 2014 . . .
Fehlerbehandlung
IO in Haskell
IO-Aktionen
do-Notation
IO-Aktionen erneut
Sequentialisierung
Die Funktion return
return erscheint oft am Ende eines do-Blocks
das ist aber nicht zwingend
ein (etwas künstliches) Beispiel:
-- file: ch07/return3.hs
returnTest :: IO ()
returnTest =
do one <- return 1
let two = 2
putStrLn $ show (one + two)
beachte: unterschiedliche Rolle von ... <- ... und let
... = ...
[OGS09], Ch. 7, pp. 188
D. Rösner
FP 2014 . . .
Fehlerbehandlung
IO in Haskell
IO-Aktionen
do-Notation
IO-Aktionen erneut
Sequentialisierung
Literatur: I
Paul Hudak.
The Haskell School of Expression – Learning Functional
Programming through Multimedia.
Cambridge University Press, Cambridge, UK, 2000.
ISBN 0-521-64338-4.
Bryan O’Sullivan, John Goerzen, and Don Stewart.
Real World Haskell.
O’Reilly Media, Sebastopol, CA 95472, 2009.
ISBN 978-0-596-51498-3; online available at
http://book.realworldhaskell.org/.
D. Rösner
FP 2014 . . .
Fehlerbehandlung
IO in Haskell
IO-Aktionen
do-Notation
IO-Aktionen erneut
Sequentialisierung
Literatur: II
Simon Thompson.
Haskell - The Craft of Functional Programming.
Addison Wesley Longman Ltd., Essex, 1999.
2nd edition, ISBN 0-201-34275-8; Accompanying Web site:
http://www.cs.ukc.ac.uk/people/staff/sjt/craft2e.
D. Rösner
FP 2014 . . .
Herunterladen