Vstup a výstup, zpracování chyb a výjimek IB016 Seminář z funkcionálního programování Mnoho autorů napříč věky Fakulta informatiky, Masarykova univerzita Jaro 2022 IB016: Cvičení 08 Jaro 2022 1 / 18 Vstup a výstup – motivace chceme číst vstup od uživatele jakou aritu by měla funkce mít? jaký typ by měla funkce vracet? jak modelovat chybu? IB016: Cvičení 08 Jaro 2022 2 / 18 Vstup a výstup – motivace chceme číst vstup od uživatele jakou aritu by měla funkce mít? jaký typ by měla funkce vracet? jak modelovat chybu? funkce jsou v Haskellu referenčně transparentní IB016: Cvičení 08 Jaro 2022 2 / 18 Vstup a výstup – motivace chceme číst vstup od uživatele jakou aritu by měla funkce mít? jaký typ by měla funkce vracet? jak modelovat chybu? funkce jsou v Haskellu referenčně transparentní co s tím? IB016: Cvičení 08 Jaro 2022 2 / 18 Vstup a výstup – motivace chceme číst vstup od uživatele jakou aritu by měla funkce mít? jaký typ by měla funkce vracet? jak modelovat chybu? funkce jsou v Haskellu referenčně transparentní co s tím? → použijeme monády! IB016: Cvičení 08 Jaro 2022 2 / 18 typový konstruktor IO unární konstruktor IO a instance Functor, Applicative, Monad z IO není úniku, funkce volající IO akci nemůže vracet typ bez konstruktoru IO nemáme runIO, vše musíme volat z funkce main (viz dále) hodnoty můžeme extrahovat pomocí operátoru bind nebo pomocí šipky v do bloku monáda IO zajišťuje, že operace budou prováděny sekvenčně a budou mít odpovídající efekty na svět IB016: Cvičení 08 Jaro 2022 3 / 18 Hello World! hello-world.hs: main = do putStrLn "Hello World" překlad zajistíme zavoláním ghc případně můžeme zabudovaný použít příkaz runhaskell , který soubor přeloží a spustí putStrLn :: String -> IO () -- putStr nepřidává "\n" getLine :: IO String pure :: a -> IO a (>>=) :: IO a -> (a -> IO b) -> IO b IB016: Cvičení 08 Jaro 2022 4 / 18 Práce se soubory main = do putStrLn "Enter filename:" filename <- getLine content <- readFile filename putStrLn $ filename ++ " capitalized:" putStrLn $ map toUpper content FilePath je jen typový alias pro String základní funkce pro práci se soubory: readFile :: FilePath -> IO String writeFile :: FilePath -> String -> IO () appendFile :: FilePath -> String -> IO () IB016: Cvičení 08 Jaro 2022 5 / 18 Pokročilejší práce se soubory Modul System.IO: multiplatformní práce se soubory type FilePath = String data IOMode = ReadMode | WriteMode | AppendMode | ReadWriteMode základní funkce readFile, writeFile, appendFile datový typ Handle pro práci s proudy stdin, stdout, stderr :: Handle openFile :: FilePath -> IOMode -> IO Handle hClose :: Handle -> IO () hGetContents :: Handle -> IO String hPutStr :: Handle -> String -> IO () withFile :: FilePath -> IOMode -> (Handle -> IO r) -> IO r pro automatické uzavření handle po dokončení IO operace a další, vizte Hoogle/dokumentaci IB016: Cvičení 08 Jaro 2022 6 / 18 Utility pro práci s cestami a jmény souborů Modul System.FilePath: utility pro práci s cestami a jmény souborů platformně nezávislé IB016: Cvičení 08 Jaro 2022 7 / 18 Utility pro práci s cestami a jmény souborů Modul System.FilePath: utility pro práci s cestami a jmény souborů platformně nezávislé "some_dir" "some_file" <.> "ext" IB016: Cvičení 08 Jaro 2022 7 / 18 Utility pro práci s cestami a jmény souborů Modul System.FilePath: utility pro práci s cestami a jmény souborů platformně nezávislé "some_dir" "some_file" <.> "ext" isAbsolute "/foo" ∗ True, isRelative "bar" ∗ True IB016: Cvičení 08 Jaro 2022 7 / 18 Utility pro práci s cestami a jmény souborů Modul System.FilePath: utility pro práci s cestami a jmény souborů platformně nezávislé "some_dir" "some_file" <.> "ext" isAbsolute "/foo" ∗ True, isRelative "bar" ∗ True takeExtension "a.out" ∗ ".out" IB016: Cvičení 08 Jaro 2022 7 / 18 Utility pro práci s cestami a jmény souborů Modul System.FilePath: utility pro práci s cestami a jmény souborů platformně nezávislé "some_dir" "some_file" <.> "ext" isAbsolute "/foo" ∗ True, isRelative "bar" ∗ True takeExtension "a.out" ∗ ".out" a spousta dalších importujeme System.FilePath, ten podle systému importuje buď POSIX nebo Windows variantu výrazně vhodnější než pracovat s cestami ručně IB016: Cvičení 08 Jaro 2022 7 / 18 Práce s adresářovou strukturou Modul System.Directory: multiplatformní manipulace s adresářovou strukturou, kopírování a odstraňování souborů, základní práce s právy. IB016: Cvičení 08 Jaro 2022 8 / 18 Práce s adresářovou strukturou Modul System.Directory: multiplatformní manipulace s adresářovou strukturou, kopírování a odstraňování souborů, základní práce s právy. getCurrentDirectory, listDirectory doesFileExist "/etc/passwd" ∗ True doesDirectoryExist "/etc" ∗ True getTemporaryDirectory ∗ "/tmp" getPermissions, setPermissions (jen základní práva, POSIX práva v POSIX-specifických modulech) executable <$> getPermissions "/bin/ls" ∗ True getFileSize findFile . . . IB016: Cvičení 08 Jaro 2022 8 / 18 Kombinování akcí IO, utility Modul Control.Monad; funguje nejen pro IO, ale pro všechny monády. mapM :: (Traversable t, Monad m) => (a -> m b) -> t a -> m (t b) filterM :: Applicative m => (a -> m Bool) -> [a] -> m [a] obdoba běžných funkcí, volané funkce jsou akce IO forM: jako mapM, převrácené argumenty varianty mapM_, forM_ ignorují výsledek IB016: Cvičení 08 Jaro 2022 9 / 18 Kombinování akcí IO, utility Modul Control.Monad; funguje nejen pro IO, ale pro všechny monády. mapM :: (Traversable t, Monad m) => (a -> m b) -> t a -> m (t b) filterM :: Applicative m => (a -> m Bool) -> [a] -> m [a] obdoba běžných funkcí, volané funkce jsou akce IO forM: jako mapM, převrácené argumenty varianty mapM_, forM_ ignorují výsledek sequence :: (Traversable t, Monad m) => t (m a) -> m (t a) spuštění seznamu akcí IO, obdobně sequence_ IB016: Cvičení 08 Jaro 2022 9 / 18 Kombinování akcí IO, utility Modul Control.Monad; funguje nejen pro IO, ale pro všechny monády. mapM :: (Traversable t, Monad m) => (a -> m b) -> t a -> m (t b) filterM :: Applicative m => (a -> m Bool) -> [a] -> m [a] obdoba běžných funkcí, volané funkce jsou akce IO forM: jako mapM, převrácené argumenty varianty mapM_, forM_ ignorují výsledek sequence :: (Traversable t, Monad m) => t (m a) -> m (t a) spuštění seznamu akcí IO, obdobně sequence_ void :: Functor f => f a -> f () a další, vizte dokumentaci IB016: Cvičení 08 Jaro 2022 9 / 18 Užitečné funkce pro interakci a argumenty příkazové řádky Modul System.Environment poskytuje funkce pro práci s argumenty příkazové řádky a proměnnými prostředí. getArgs :: IO [String] getProgName :: IO String getEnvironment :: IO [(String, String)] a další IB016: Cvičení 08 Jaro 2022 10 / 18 Užitečné funkce pro interakci a argumenty příkazové řádky Modul System.Environment poskytuje funkce pro práci s argumenty příkazové řádky a proměnnými prostředí. getArgs :: IO [String] getProgName :: IO String getEnvironment :: IO [(String, String)] a další Při interakci (nejen) s uživatelem when :: Applicative f => Bool -> f () -> f () forever :: Applicative f => f a -> f b interact :: (String -> String) -> IO () Pozor na buffering! Více v dokumentaci. IB016: Cvičení 08 Jaro 2022 10 / 18 Výjimky IB016: Cvičení 08 Jaro 2022 11 / 18 Řešení chyb v čistém kódu Maybe, Either 1 Povšimněte si, že jde o IB016: Cvičení 08 Jaro 2022 12 / 18 Řešení chyb v čistém kódu Maybe, Either využití toho, že obojí jsou funktory/monády fmap (+ 2) (lookup "a" [...]) lookup "a" [...] >>= foo 1 Povšimněte si, že jde o IB016: Cvičení 08 Jaro 2022 12 / 18 Řešení chyb v čistém kódu Maybe, Either využití toho, že obojí jsou funktory/monády fmap (+ 2) (lookup "a" [...]) lookup "a" [...] >>= foo maybe1 :: b -> (a -> b) -> Maybe a -> b fromMaybe :: a -> Maybe a -> a (z Data.Maybe) 1 Povšimněte si, že jde o katamorfismus IB016: Cvičení 08 Jaro 2022 12 / 18 Řešení chyb v čistém kódu Maybe, Either využití toho, že obojí jsou funktory/monády fmap (+ 2) (lookup "a" [...]) lookup "a" [...] >>= foo maybe1 :: b -> (a -> b) -> Maybe a -> b fromMaybe :: a -> Maybe a -> a (z Data.Maybe) either1 :: (a -> c) -> (b -> c) -> Either a b -> c 1 Povšimněte si, že jde o katamorfismy IB016: Cvičení 08 Jaro 2022 12 / 18 Chyby v IO kódu Věci, kde je selhání běžné: Maybe, Either findExecutable :: String -> IO (Maybe FilePath) zabalení v IO komplikuje práci IB016: Cvičení 08 Jaro 2022 13 / 18 Chyby v IO kódu Věci, kde je selhání běžné: Maybe, Either findExecutable :: String -> IO (Maybe FilePath) zabalení v IO komplikuje práci Věci, kde je selhání neočekávané/závažná chyba: výjimky podobně jako v imperativních jazycích (C++, C#, Java) používáme pro výjimečné případy pokud nechceme chybu ošetřovat hned, jak nastane o něco komplikovanější než v imperativních jazycích typicky neočekávaný stav souborového systému, nedostatečná práva,. . . IB016: Cvičení 08 Jaro 2022 13 / 18 Výjimky v Haskellu I lze chytat jen v IO do funkcionálního paradigmatu se nehodí modul Control.Exception IB016: Cvičení 08 Jaro 2022 14 / 18 Výjimky v Haskellu I lze chytat jen v IO do funkcionálního paradigmatu se nehodí modul Control.Exception výjimky je možné zachytávat v závislosti na typu typová třída Exception, vyžaduje Show každý typ výjimky je instancí IB016: Cvičení 08 Jaro 2022 14 / 18 Výjimky v Haskellu I lze chytat jen v IO do funkcionálního paradigmatu se nehodí modul Control.Exception výjimky je možné zachytávat v závislosti na typu typová třída Exception, vyžaduje Show každý typ výjimky je instancí při zachytávání nutné, aby byl jednoznačně určen typ výjimky aby se odvodilo, zda má být chycena často vyžaduje explicitní otypování SomeException pro libovolnou IB016: Cvičení 08 Jaro 2022 14 / 18 Výjimky v Haskellu I lze chytat jen v IO do funkcionálního paradigmatu se nehodí modul Control.Exception výjimky je možné zachytávat v závislosti na typu typová třída Exception, vyžaduje Show každý typ výjimky je instancí při zachytávání nutné, aby byl jednoznačně určen typ výjimky aby se odvodilo, zda má být chycena často vyžaduje explicitní otypování SomeException pro libovolnou catch :: Exception e => IO a -> (e->IO a) -> IO a expr `catch` \ex -> print (ex :: IOException)→ >> handleIOExc try :: Exception e => IO a -> IO (Either e a) IB016: Cvičení 08 Jaro 2022 14 / 18 Výjimky v Haskellu II správa zdrojů – bracket: bracket :: IO a -- ^ získání zdroje -> (a -> IO b) -- ^ uvolnění zdroje -> (a -> IO c) -- ^ operace se zdrojem -> IO c withFile name mode = bracket (openFile name mode) hClose throwIO :: Exception e => e -> IO a vyhazování výjimek je v čistém kódu možné, ale silně nevhodné IB016: Cvičení 08 Jaro 2022 15 / 18 Výjimky v Haskellu III Interakce mezi výjimkami a leností je problém >λ= pure (4 `div` 0) `catch` \ex -> print (ex :: SomeException) >> pure 0 IB016: Cvičení 08 Jaro 2022 16 / 18 Výjimky v Haskellu III Interakce mezi výjimkami a leností je problém >λ= pure (4 `div` 0) `catch` \ex -> print (ex :: SomeException) >> pure 0 *** Exception: divide by zero výjimka nechycena! IB016: Cvičení 08 Jaro 2022 16 / 18 Výjimky v Haskellu III Interakce mezi výjimkami a leností je problém >λ= pure (4 `div` 0) `catch` \ex -> print (ex :: SomeException) >> pure 0 *** Exception: divide by zero výjimka nechycena! pure vrací nevyhodnocenou věc, až při vyhodnocení se vyhodí výjimka catch dříve než vyhodnocení pokud výjimku vyhazuje IO-funkce, problém typicky není IB016: Cvičení 08 Jaro 2022 16 / 18 Výjimky v Haskellu IV Při špatné interakci s leností je třeba vynutit vyhodnocení evaluate :: a -> IO a – vyhodnotí po vnější datový konstruktor vrací IO-akci, která vyhodí výjimku, pokud vyhodnocení vyhodilo výjimku >λ= evaluate (4 `div` 0) `catch` \ex -> print (ex :: SomeException) >> pure 0 divide by zero IB016: Cvičení 08 Jaro 2022 17 / 18 Výjimky v Haskellu IV Při špatné interakci s leností je třeba vynutit vyhodnocení evaluate :: a -> IO a – vyhodnotí po vnější datový konstruktor vrací IO-akci, která vyhodí výjimku, pokud vyhodnocení vyhodilo výjimku >λ= evaluate (4 `div` 0) `catch` \ex -> print (ex :: SomeException) >> pure 0 divide by zero POZOR! >λ= evaluate [4 `div` 0] `catch` \ex -> print (ex :: SomeException) >> pure [] [*** Exception: divide by zero IB016: Cvičení 08 Jaro 2022 17 / 18 Výjimky v Haskellu IV Při špatné interakci s leností je třeba vynutit vyhodnocení evaluate :: a -> IO a – vyhodnotí po vnější datový konstruktor vrací IO-akci, která vyhodí výjimku, pokud vyhodnocení vyhodilo výjimku >λ= evaluate (4 `div` 0) `catch` \ex -> print (ex :: SomeException) >> pure 0 divide by zero POZOR! >λ= evaluate [4 `div` 0] `catch` \ex -> print (ex :: SomeException) >> pure [] [*** Exception: divide by zero úplné vyhodnocení pomocí Control.DeepSeq.force (evaluate (force x)) IB016: Cvičení 08 Jaro 2022 17 / 18 Výjimky v Haskellu: příklad import Control.Exception import System.Environment import System.IO main = handle ioExpHandler $ do [from, to] <- getArgs withFile from ReadMode $ \hFrom -> withFile to WriteMode $ \hTo -> until (hIsEOF hFrom) $ do line <- hGetLine hFrom hPutStrLn hTo line where ioExpHandler :: IOException -> IO () ioExpHandler e = putStrLn $ "fatal: " ++ show e until :: IO Bool -> IO a -> IO () until bool act = bool >>= \x -> case x of False -> act >> until bool act True -> pure () IB016: Cvičení 08 Jaro 2022 18 / 18