Funktionale Programmierung - Spezielle Aspekte von Haskell

Werbung
Fehlerbehandlung
IO in Haskell
Fehlerbehandlung
IO in Haskell
Gliederung
Funktionale Programmierung
Spezielle Aspekte von Haskell
1
Fehlerbehandlung
D. Rösner
2
IO in Haskell
IO-Aktionen
do-Notation
IO-Aktionen erneut
Sequentialisierung
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
D. Rösner
FP 2014 . . .
Fehlerbehandlung
IO in Haskell
Umgang mit Fehlern
Umgang mit Fehlern cont.
Variante 1: Fehlerbericht und Programmabbruch
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 . . .
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
Fehlerbehandlung
IO in Haskell
Umgang mit Fehlern cont.
Umgang mit Fehlern cont.: Variante 2
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
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
D. Rösner
FP 2014 . . .
Fehlerbehandlung
IO in Haskell
FP 2014 . . .
Fehlerbehandlung
IO in Haskell
Umgang mit Fehlern cont.
Umgang mit Fehlern cont.
Variante 3: Wert vom Fehlertyp Maybe
Definition:
Variante 3
data Maybe = Nothing | Just a
deriving (Eq, Ord, Read, Show)
allgemein: wenn unter Bedingung cond bei einstelliger
Funktion f ein Fehlerfall:
Beispiel:
errDiv :: Int -> Int -> Maybe Int
fErr x
| cond
| otherwise
= Nothing
= Just (f x)
errDiv n m
| (m /= 0) = Just (n ‘div‘ m)
| otherwise = Nothing
D. Rösner
FP 2014 . . .
D. Rösner
FP 2014 . . .
Fehlerbehandlung
IO in Haskell
Fehlerbehandlung
IO in Haskell
Fehlertyp Maybe
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
Beispiele (vgl. [Tho99], Ch. 14.4]):
maybe 42 (1+) (mapMaybe (*3) (errDiv 9 0))
= ...
= ...
= ..
maybe 42 (1+) (mapMaybe (*3) (errDiv 9 1))
= ...
= ...
= ..
maybe n f Nothing = n
maybe n f (Just x) = f x
D. Rösner
Fehlerbehandlung
IO in Haskell
FP 2014 . . .
D. Rösner
IO-Aktionen
do-Notation
IO-Aktionen erneut
Sequentialisierung
I/O in Haskell:
Fehlerbehandlung
IO in Haskell
FP 2014 . . .
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 . . .
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.
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
Typ IO a:
I/O-Aktionen vom Typ a, d.h.
Prelude> getLine
eine Zeile eingelesen
ein Objekt vom Typ IO a steht für ein Programm, das I/O
durchführt und
einen Wert vom Typ a liefert
Prelude> getLine
[a,b] ++ [c,d,e]
s.a. [OGS09], Ch. 7, [Hud00], Ch. 3.1, [Tho99], Ch. 18
Prelude>
Was besagt der Typ von getLine?
D. Rösner
Fehlerbehandlung
IO in Haskell
FP 2014 . . .
IO-Aktionen
do-Notation
IO-Aktionen erneut
Sequentialisierung
I/O in Haskell: cont.
D. Rösner
Fehlerbehandlung
IO in Haskell
FP 2014 . . .
IO-Aktionen
do-Notation
IO-Aktionen erneut
Sequentialisierung
I/O in Haskell: cont.
getChar: Einlesen eines einzelnen Zeichens von Eingabe
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 . . .
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.
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
Fehlerbehandlung
IO in Haskell
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 . . .
IO-Aktionen
do-Notation
IO-Aktionen erneut
Sequentialisierung
I/O in Haskell: die do-Notation
Fehlerbehandlung
IO in Haskell
FP 2014 . . .
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
Beispiele:
einen String exakt 4 mal ausgeben
put4times :: String -> IO ()
put4times str = do putStrLn
putStrLn
putStrLn
putStrLn
s.a. [OGS09], Ch. 7, [Hud00], Ch. 3.1, [Tho99], Ch. 18
D. Rösner
FP 2014 . . .
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
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
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
put4times = putNtimes 4
D. Rösner
Fehlerbehandlung
IO in Haskell
FP 2014 . . .
D. Rösner
IO-Aktionen
do-Notation
IO-Aktionen erneut
Sequentialisierung
I/O in Haskell: die do-Notation
Fehlerbehandlung
IO in Haskell
FP 2014 . . .
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
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)
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.
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 ()
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
s.a. [OGS09], Ch. 7
D. Rösner
Fehlerbehandlung
IO in Haskell
FP 2014 . . .
D. Rösner
IO-Aktionen
do-Notation
IO-Aktionen erneut
Sequentialisierung
I/O-Aktionen in Haskell: cont.
Fehlerbehandlung
IO in Haskell
FP 2014 . . .
IO-Aktionen
do-Notation
IO-Aktionen erneut
Sequentialisierung
I/O-Aktionen in Haskell: cont.
Beispiel (cont.)
Beispiel
-- file: ch07/actions.hs
str2action :: String -> IO ()
str2action input = putStrLn ("Data: " ++ input)
list2actions :: [String] -> [IO ()]
list2actions = map str2action
...
numbers :: [Int]
numbers = [1..10]
strings :: [String]
strings = map show numbers
actions :: [IO ()]
actions = list2actions strings
...
...
[OGS09], Ch. 7, pp. 184
[OGS09], Ch. 7, pp. 184
D. Rösner
FP 2014 . . .
D. Rösner
FP 2014 . . .
Fehlerbehandlung
IO in Haskell
IO-Aktionen
do-Notation
IO-Aktionen erneut
Sequentialisierung
I/O-Aktionen in Haskell: cont.
Fehlerbehandlung
IO in Haskell
IO-Aktionen
do-Notation
IO-Aktionen erneut
Sequentialisierung
I/O-Aktionen in Haskell: cont.
Beispiel (cont.)
Beispiel (kompaktere Version)
...
printitall :: IO ()
printitall = runall actions
-- file: ch07/actions2.hs
str2message :: String -> String
str2message input = "Data: " ++ input
-- Take a list of actions, and execute each of them in turn.
runall :: [IO ()] -> IO ()
runall [] = return ()
runall (firstelem:remainingelems) =
do firstelem
runall remainingelems
str2action :: String -> IO ()
str2action = putStrLn . str2message
main = do str2action "Start of the program"
printitall
str2action "Done!"
Fehlerbehandlung
IO in Haskell
main = do str2action "Start of the program"
mapM_ (str2action . show) numbers
str2action "Done!"
[OGS09], Ch. 7, pp. 184
[OGS09], Ch. 7, pp. 184
D. Rösner
numbers :: [Int]
numbers = [1..10]
FP 2014 . . .
IO-Aktionen
do-Notation
IO-Aktionen erneut
Sequentialisierung
I/O-Aktionen in Haskell: cont.
D. Rösner
Fehlerbehandlung
IO in Haskell
FP 2014 . . .
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:
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
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 . . .
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 [()]
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
()
[OGS09], Ch. 7, pp. 185
D. Rösner
Fehlerbehandlung
IO in Haskell
FP 2014 . . .
D. Rösner
IO-Aktionen
do-Notation
IO-Aktionen erneut
Sequentialisierung
I/O-Aktionen in Haskell: cont.
Fehlerbehandlung
IO in Haskell
FP 2014 . . .
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 . . .
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
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
[OGS09], Ch. 7, pp. 186
D. Rösner
Fehlerbehandlung
IO in Haskell
FP 2014 . . .
IO-Aktionen
do-Notation
IO-Aktionen erneut
Sequentialisierung
Operatoren zur Sequentialisierung
Fehlerbehandlung
IO in Haskell
FP 2014 . . .
IO-Aktionen
do-Notation
IO-Aktionen erneut
Sequentialisierung
Operatoren zur Sequentialisierung
Ausführen einer Liste von Aktionen durch Einfalten von »
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
*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
[OGS09], Ch. 7, pp. 187
D. Rösner
D. Rösner
FP 2014 . . .
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
Fehlerbehandlung
IO in Haskell
FP 2014 . . .
IO-Aktionen
do-Notation
IO-Aktionen erneut
Sequentialisierung
Die Funktion return
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) number
*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
Fehlerbehandlung
IO in Haskell
FP 2014 . . .
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
return erscheint oft am Ende eines do-Blocks
die Haskell-Funktion return ist davon sehr verschieden
-- file: ch07/return3.hs
returnTest :: IO ()
returnTest =
do one <- return 1
let two = 2
putStrLn $ show (one + two)
sie dient dazu, reine Daten in eine Monade (hier: eine
IO-Aktion) einzubringen
*Main> :t return
return :: Monad m => a -> m a
das ist aber nicht zwingend
ein (etwas künstliches) Beispiel:
besserer Name wäre z.B. inject
beachte: unterschiedliche Rolle von ... <- ... und let
... = ...
[OGS09], Ch. 7, pp. 187
[OGS09], Ch. 7, pp. 188
D. Rösner
FP 2014 . . .
D. Rösner
FP 2014 . . .
Fehlerbehandlung
IO in Haskell
IO-Aktionen
do-Notation
IO-Aktionen erneut
Sequentialisierung
Literatur: I
Fehlerbehandlung
IO in Haskell
IO-Aktionen
do-Notation
IO-Aktionen erneut
Sequentialisierung
Literatur: II
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 . . .
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