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 . . .