Why can't I catch exception in different IO than () in Haskell? - haskell

I want to catch all exceptions in IO String function. When I run this code:
import Control.Exception.Base
handler :: SomeException -> IO String
handler _ = return "Gotta catch'em all!"
boom :: IO String
boom = return (show (3 `div` 0))
get :: IO String
get = boom `catch` handler
main :: IO()
main = do
x <- get
print x
I get
exctest: divide by zero
However this code works:
import Control.Exception.Base
handler2 :: SomeException -> IO ()
handler2 _ = print "Gotta catch'em all!"
boom2 :: IO ()
boom2 = print $ show (3 `div` 0)
main :: IO()
main = do
boom2 `catch` handler2
with result
> Gotta catch'em all!
When I change boom in first example to
boom = error "ERR"
The exception is caught ok. Why does it behave this way? What should I do to catch exception in first example?

This has nothing to do with () vs. other types. Note that the following also doesn't catch:
boom = return $ error "ERR"
The reason catch won't do anything about that is that return is lazy, thus the error isn't actually triggered by catch, only if you try to get at the contained value.
It is for precisely this reason that the Exception module has evaluate, which is equivalent to return but strict.
boom :: IO String
boom = evaluate . show $ 3`div`0

Related

How do I override error message on Left in Haskell?

I want to read from file and am using pattern matching on Left/Right to tell is said file exists like so:
tehfile <- readIniFile "somefile_that_does_not_exist.ini"
s <- case tehfile of
Left a -> print "woot"
Right b -> ...
The error I'm getting is the one generated by Data.Ini library: "openFile: does not exist (No such file or directory)".
Ideally, I'd like to override that text entirely or at least append my super useful "woot" string after the one produced by the library. How can I do that and why doesn't it happen in the code I have now?
If you look at the implementation for readIniFile, it's just
readIniFile :: FilePath -> IO (Either String Ini)
readIniFile = fmap parseIni . T.readFile
First it tries to open and read the file, then it tries to parse the contents as an INI config. That first part would fail with an exception if the file doesn't exist, and the second fails with a Left. To print your own message and resume the computation, instead of pattern matching on the Left case you'll want to catch the exception.
λ> import Control.Exception
λ> handler :: IOException -> IO (Either String Ini); handler _ = putStrLn "woot" >> pure (Left "woot")
λ> tehfile <- readIniFile "somefile_that_does_not_exist.ini" `catch` handler
woot
Or you can throw an error with your own message, to end the computation
λ> handler :: IOException -> IO (Either String Ini); handler _ = error "woot"
λ> tehfile <- readIniFile "somefile_that_does_not_exist.ini" `catch` handler
*** Exception: woot
CallStack (from HasCallStack):
error, called at <interactive>...
Or write your own exception and throw that
λ> data Woot = Woot deriving (Show)
λ> instance Exception Woot
λ> handler :: IOException -> IO (Either String Ini); handler _ = throwIO Woot
λ> tehfile <- readIniFile "somefile_that_does_not_exist.ini" `catch` handler
*** Exception: Woot

Why this try catch example is not working

I have following demo code from here:
import System.Environment
import System.IO
import System.IO.Error
main = toTry `catch` handler
toTry :: IO ()
toTry = do (fileName:_) <- getArgs
contents <- readFile fileName
putStrLn $ "The file has " ++ show (length (lines contents)) ++ " lines!"
handler :: IOError -> IO ()
handler e = putStrLn "Whoops, had some trouble!"
But it is giving me error:
runghc trycatch2.hs
trycatch2.hs:5:14: error:
Variable not in scope: catch :: IO () -> (IOError -> IO ()) -> t
Where is the problem and how can it be solved? Thanks.
The example in Learn You a Haskell for the Greater Good is outdated, the catch :: Exception e => IO a -> (e -> IO a) -> IO a function is part of Control.Exception.
System.IO.Error however still has a catch function that is here applicable: catchIOError :: IO a -> (IOError -> IO a) -> IO a, but as the documentation says:
The catchIOError function establishes a handler that receives any IOException raised in the action protected by catchIOError. An IOException is caught by the most recent handler established by one of the exception handling functions. These handlers are not selective: all IOExceptions are caught.
(...)
Non-I/O exceptions are not caught by this variant; to catch all exceptions, use catch from Control.Exception.
So you can fix the problem here by using catchIOError (since you are dealing with an IOError, but as specified in the documentation, this only covers a limited set of exceptions), or you can import catch from Control.Exception:
import Control.Exception(catch)
import System.Environment
import System.IO
import System.IO.Error
main :: IO ()
main = toTry `catch` handler
toTry :: IO ()
toTry = do (fileName:_) <- getArgs
contents <- readFile fileName
putStrLn $ "The file has " ++ show (length (lines contents)) ++ " lines!"
handler :: IOError -> IO ()
handler e = putStrLn "Whoops, had some trouble!"

How to compose writeFile with Either data type?

I have a function that accepts some arguments and returns IO (Either String String), say
testEither :: Int -> IO (Either String String)
testEither 0 = return (Left "we got an error")
testEither _ = return (Right "everything is ok")
(Real function fetches some stuff from real world and might fail)
I want to send output from that function to writeFile fileName. Expected behavior: if I bind testEither 0 to writeFile "test.txt", I fail with Left ..., and if I call it with testEither 1, I get everything is ok in file test.txt.
I guess the type of the whole expression should be something like IO (Either String ()), but I may be wrong.
You can use the ErrorT1 monad transformer to give you pure error handling on top of the IO monad:
import Control.Monad.Error
testEither :: Int -> IO (Either String String)
testEither 0 = return (Left "we got an error")
testEither _ = return (Right "everything is ok")
main = runErrorT $ do
result <- ErrorT $ testEither 0
lift $ writeFile "test.txt" result
1 ErrorT appears to have been replaced with ExceptT in the newest version of mtl, but the functionality should be similar.
This is easy using Either's Traversable instance:
import Data.Traversable
main = do
traverse (writeFile "test.txt") (Left "we got an error")
traverse (writeFile "test.txt") (Right "everything is ok")
This won't happen automatically, but you can easily use pattern matching to perform this action:
writeFileEither :: FilePath -> Either a String -> IO ()
writeFileEither _ (Left _) = return ()
writeFileEither fp (Right text) = writeFile fp text
Then you can bind them together with
main = testEither 1 >>= writeFileEither "test.txt"
Or with do notation:
main = do
result <- testEither 1
writeFileEither "test.txt" result
The main function below is an example that show how to use testEither in IO: if testEither returns an error then the error is written to stderr else the correct result is extracted from Right and written to the file test.txt:
import System.IO
testEither :: Int → IO (Either String String)
testEither 0 = return (Left "we got an error")
testEither _ = return (Right "everything is ok")
main :: IO 𐌏
main = do
res ← testEither 0
case res of
Left err → hPutStrLn stderr ("Error: " ++ err)
Right s → writeFile "test.txt" s

How to catch a divide by zero error in Haskell?

For something like file not found the basic structure of the code below will work, but for this example of division by zero the exception is not caught. How does one catch a divide by zero?
import Control.Exception.Base
import Data.Array
main = toTry `catch` handler
toTry = do
print "hi"
print (show (3 `div` 0))
print "hi"
handler :: IOError -> IO ()
handler e = putStrLn "bad"
You need a handler that catches an ArithException, and matches on DivideByZero.
import Control.Exception.Base
import Data.Array
main = toTry `catch` handler
toTry = do
print "hi"
print (show (3 `div` 0))
print "hi"
handler :: ArithException -> IO ()
handler DivideByZero = putStrLn "Divide by Zero!"
handler _ = putStrLn "Some other error..."

How to use catch inside an ErrorT in Haskell?

Let's say that I'd like to rework the following function:
runCmd :: FilePath -> ErrorT String IO ()
runCmd cmd = do
Left e <- liftIO $ tryIOError $ do
(inp, outp, errp, pid) <- runInteractiveProcess cmd [] Nothing Nothing
mapM_ (`hSetBuffering` LineBuffering) [inp, outp, errp]
forever (hGetLine outp >>= putStrLn) -- IO error when process ends
unless (isEOFError e) $ throwError $ "IO Error: " ++ ioeGetErrorString e
It tries to run cmd and read the output. If it fails with an IO error, I catch it with tryIOError, pass it through to the enclosing ErrorT monad, and then deal with the error.
It's kind of a roundabout way to do it, especially since there are functions like catch and handle that allow me to use a handler to deal with the error. But they are typed IO:
handle :: Exception e => (e -> IO a) -> IO a -> IO a
catch :: Exception e => IO a -> (e -> IO a) -> IO a
How do I cleanly restructure the above code so that I can handle the IO errors and pass it through the ErrorT monad?
If you really want to use ErrorT, you can try something like this
import Control.Exception
import Control.Monad.Error
wrapException :: IO a -> ErrorT String IO a
wrapException io = do
either <- liftIO $ tryJust Just io
case either of
Left e -> throwError . show $ (e :: SomeException)
Right v -> return v
But this isn't perfect because you still are limited to IO in what can throw exceptions. What you can do to help is to use
catchException :: ErrorT String IO a -> ErrorT String IO a
catchException = either throwError return <=< wrapException . runErrorT
What this does is grab any exceptions that are propagating up and trap them back in the ErrorT monad. This still isn't perfect since you need to explicitly wrap all exception throwing pieces of code in it. But it's not terrible.
I would avoid using an ErrorT for this in the first place. Have runCmd simply return an IO () (or throw an IOError if issues arise), and defer error handling to the callers:
runCmd :: FilePath -> IO ()
runCmd cmd =
handleIOError (\e -> if isEOFError e then return () else ioError e) $ do
-- same code as before
(inp, outp, errp, pid) <- runInteractiveProcess cmd [] Nothing Nothing
mapM_ (`hSetBuffering` LineBuffering) [inp, outp, errp]
forever (hGetLine outp >>= putStrLn)
where handleIOError = flip catchIOError
caller :: IO ()
caller = catchIOError (runCmd "/bin/ls") $ \e ->
-- error handling code
If you need to catch the IOError in ErrorT code elsewhere, you can use liftIO there:
errorTCaller :: ErrorT String IO ()
errorTCaller = liftIO caller

Resources