Reinheit und IO
Haskell-Funktionen sind rein: Gleiche Eingabe, gleiche Ausgabe, keine Nebenwirkungen. Ein- und Ausgabe (Konsole, Dateien, Zufall, Zeit) steht deshalb im Typ: IO a ist eine Beschreibung einer Aktion, die einen Wert vom Typ a liefert. Aktionen setzt man in do-Blöcken zusammen.
import Data.IORef
import Control.Monad (forM_, when, unless, replicateM_, forM)
main :: IO ()
main = do
putStrLn "Start"
let x = 5 * 5 -- let: reiner Wert
r <- return (x + 1) -- <-: Ergebnis einer Aktion binden
print r
forM_ [1 .. 3] $ \i -> putStrLn ("Runde " ++ show i)
when (x > 10) $ putStrLn "x ist groß"
unless (x > 100) $ putStrLn "x ist nicht riesig"
replicateM_ 2 (putStr "ho ")
putStrLn ""
zaehler <- newIORef (0 :: Int) -- veränderlicher Zustand, explizit in IO
forM_ [1 .. 5] $ \i -> modifyIORef zaehler (+ i)
readIORef zaehler >>= print
quadrate <- forM [1 .. 4] (\i -> return (i * i))
print quadrate
mapM_ print (zip [1 :: Int ..] "abc")
sequence_ [putStr "a", putStr "b", putStrLn "c"]
let aktion = putStrLn "Aktionen sind Werte"
aktion >> aktionStart 26 Runde 1 Runde 2 Runde 3 x ist gro? x ist nicht riesig ho ho 15 [1,4,9,16] (1,'a') (2,'b') (3,'c') abc Aktionen sind Werte Aktionen sind Werte
Ein wichtiger Gedanke: IO-Code kann reinen Code aufrufen, aber nie umgekehrt (ohne unsafePerformIO). So bleibt der größte Teil des Programms rein, testbar und leicht zu verstehen; die Nebenwirkungen liegen "am Rand".
Eingabe und Ausgabe
import System.IO
import Text.Read (readMaybe)
verarbeite :: String -> String
verarbeite eingabe = unlines (map ausgabeZeile (zip [1 :: Int ..] (lines eingabe)))
where ausgabeZeile (n, z) = show n ++ ": " ++ reverse z
main :: IO ()
main = do
hSetBuffering stdout NoBuffering
-- Interaktiv wäre: name <- getLine; contents <- getContents; interact verarbeite
putStr (verarbeite "abc\nhallo\n")
hPutStrLn stderr "(das geht auf den Fehlerkanal)"
print (readMaybe "12" :: Maybe Int)
let eingabe = "3 4 5"
zahlen = map read (words eingabe) :: [Int]
putStrLn ("Summe: " ++ show (sum zahlen))
writeFile "/tmp/haskell_demo.txt" "Zeile 1\nZeile 2\n"
appendFile "/tmp/haskell_demo.txt" "Zeile 3\n"
inhalt <- readFile "/tmp/haskell_demo.txt"
putStrLn (show (length (lines inhalt)) ++ " Zeilen")
withFile "/tmp/haskell_demo.txt" ReadMode $ \h -> do
erste <- hGetLine h
putStrLn erste1: cba 2: ollah Just 12 Summe: 12 3 Zeilen Zeile 1
Ausführlich: getLine (eine Zeile), getContents (alles), interact f (stdin durch eine Funktion schicken), readFile/writeFile, System.Environment (getArgs, lookupEnv), System.Exit (exitWith, ExitFailure).
Fehler in IO
import Control.Exception
import System.IO.Error (isDoesNotExistError)
main :: IO ()
main = do
r1 <- try (evaluate (1 `div` 0 :: Int)) :: IO (Either ArithException Int)
print r1
r2 <- try (readFile "/gibt/es/nicht") :: IO (Either IOException String)
putStrLn (either (const "Datei fehlt") id r2)
r3 <- try (evaluate (head ([] :: [Int])))
case r3 of
Left (e :: SomeException) -> putStrLn "head auf leerer Liste abgefangen"
Right v -> print v
(putStrLn "Arbeit" >> throwIO (userError "kaputt")) `catch` \e -> putStrLn ("Gefangen: " ++ show (e :: IOException))
(putStrLn "mit finally" `finally` putStrLn "Aufräumen")
r4 <- try (readFile "/gibt/es/nicht")
case r4 of
Left e | isDoesNotExistError e -> putStrLn "nicht vorhanden"
Left _ -> putStrLn "anderer Fehler"
Right _ -> putStrLn "ok"Left divide by zero Datei fehlt head auf leerer Liste abgefangen Arbeit Gefangen: user error (kaputt) mit finally Aufr?umen nicht vorhanden
Bevorzuge Maybe/Either für erwartbare Fehler in reinem Code und Exceptions nur für IO-Probleme.
Zustand in reinem Code: die State-Monade
Statt veränderlicher Variablen führt man den Zustand als Parameter. Die State-Monade (Paket mtl) macht das bequem:
import Control.Monad.State
zaehle :: State Int Int
zaehle = do
n <- get
put (n + 1)
modify (* 2)
gets (+ 100)
stapel :: State [Int] ()
stapel = do
modify (1 :)
modify (2 :)
modify (3 :)
modify tail
labeln :: [String] -> State Int [(Int, String)]
labeln = mapM (\s -> do { n <- get; put (n + 1); return (n, s) })
main :: IO ()
main = do
print (runState zaehle 5)
print (execState stapel [])
print (evalState (labeln ["a", "b", "c"]) 10)
print (evalState (replicateM 4 (state (\s -> (s * s, s + 1)))) 1)(112,12) [2,1] [(10,"a"),(11,"b"),(12,"c")] [1,4,9,16]
Eigener kleiner Parser mit Applicative
import Data.Char (isDigit, isSpace)
newtype Parser a = Parser { runParser :: String -> Maybe (a, String) }
instance Functor Parser where
fmap f (Parser p) = Parser $ \s -> case p s of
Nothing -> Nothing
Just (a, rest) -> Just (f a, rest)
instance Applicative Parser where
pure a = Parser $ \s -> Just (a, s)
Parser pf <*> Parser pa = Parser $ \s -> case pf s of
Nothing -> Nothing
Just (f, rest) -> case pa rest of
Nothing -> Nothing
Just (a, rest') -> Just (f a, rest')
instance Monad Parser where
Parser p >>= f = Parser $ \s -> case p s of
Nothing -> Nothing
Just (a, rest) -> runParser (f a) rest
zeichen :: (Char -> Bool) -> Parser Char
zeichen ok = Parser $ \s -> case s of
(c : cs) | ok c -> Just (c, cs)
_ -> Nothing
viele :: Parser a -> Parser [a]
viele p = Parser $ \s -> case runParser p s of
Nothing -> Just ([], s)
Just (a, rest) -> case runParser (viele p) rest of
Just (as, rest') -> Just (a : as, rest')
Nothing -> Just ([a], rest)
zahl :: Parser Int
zahl = read <$> ((:) <$> zeichen isDigit <*> viele (zeichen isDigit))
leer :: Parser ()
leer = () <$ viele (zeichen isSpace)
summe :: Parser Int
summe = do
a <- zahl
leer
_ <- zeichen (== '+')
leer
b <- zahl
return (a + b)
main :: IO ()
main = do
print (runParser zahl "123abc")
print (runParser zahl "abc")
print (runParser summe "12 + 30 Rest")
print (fst <$> runParser summe "7+x")Just (123,"abc") Nothing Just (42," Rest") Nothing
Merke
IO abeschreibt Aktionen;do-Blöcke setzen sie zusammen,<-bindet Ergebnisse- Reiner Code kann keinen IO-Code aufrufen: Nebenwirkungen bleiben am Rand
forM_,mapM_,when,unless,IOReffür Schleifen und Zustand in IO- Fehler:
try,catch,evaluate,finally; im reinen CodeMaybe/Either Stateführt Zustand ohne Variablen; Parser lassen sich ausFunctor/Applicative/Monadbauen
Aufgabe
Schreibe ein Programm, das Zeilen von stdin liest und ihre Längen ausgibt (interact).