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