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!"
Related
I'm using the following scenario as an example to learn how to handle errors in a simple way. The scenario is basically read a file path from an environment variable, then read and print the file with the file path.
The following code works, but I don't like the printFile because it has nested case of, a bit hard to read. I wonder if there is a clean way to get rid of it and keep the printFile function flat without using lookupEnv?
How would you simplify this error handling flow?
module Main where
import Control.Exception (IOException, handle, throw)
import System.Environment (getEnv)
import System.IO.Error (isDoesNotExistError)
data MissingError
= MissingEnv String
| MissingFile String
deriving (Show)
main :: IO ()
main = do
eitherFile <- printFile
either print print eitherFile
getEnv' :: String -> MissingError -> IO (Either MissingError String)
getEnv' env err = handle (missingEnv err) $ Right <$> (getEnv env)
readFile' :: FilePath -> MissingError -> IO (Either MissingError String)
readFile' path err = handle (missingFile err) $ Right <$> (readFile path)
missingEnv :: MissingError -> IOException -> IO (Either MissingError String)
missingEnv err = const $ return $ Left err
missingFile :: MissingError -> IOException -> IO (Either MissingError String)
missingFile err e
| isDoesNotExistError e = return $ Left err
| otherwise = throw e
printFile :: IO (Either MissingError String)
printFile = do
eitherFilePath <- getEnv' "FOLDER" (MissingEnv "FOLDER")
case eitherFilePath of
Left err -> return $ Left err
Right path -> readFile' path (MissingFile path)
You can use the ExceptT monad transformer for this. I haven't tried to run the following proposed changes, but it compiles, so I hope it works.
First, import the module that contains ExceptT:
import Control.Monad.Trans.Except
Next, change the printFile function:
printFile :: IO (Either MissingError String)
printFile = runExceptT $ do
path <- ExceptT $ getEnv' "FOLDER" (MissingEnv "FOLDER")
ExceptT $ readFile' path (MissingFile path)
You have functions that return IO (Either MissingError String), so wrapping them in ExceptT gives you do notation that enables you to access the String embedded in what's effectively ExcepT MissingError IO String.
Then unwrap the ExceptT return value with runExceptT.
The suggestion to use ExceptT is of course a good one but IMHO the proposed answer is still somewhat verbose and you can go a bit farther by simply "staying" in the ExceptT monad throughout your code. Also I would not recommend handling IO exceptions all over the place. Even with a small code base you would lose oversight of your code quickly. tryIOError is useful in this regard. And finally rethinking the definition of your errors would also yield easier to understand and a more solid solution. The end result would look something like this:
module Main where
import Data.Bifunctor (first)
import Control.Monad.Except (ExceptT(..), runExceptT)
import System.Environment (getEnv)
import System.IO.Error (tryIOError, isDoesNotExistError)
data MyError = MissingError String
| SomeIOError IOError
deriving (Show)
main :: IO ()
main = do
result <- runExceptT printFile
print result
getEnv' :: String -> ExceptT MyError IO String
getEnv' env = mapIOError ("getting env var " ++ env) $ getEnv env
readFile' :: FilePath -> ExceptT MyError IO String
readFile' path = mapIOError ("reading file " ++ path) $ readFile path
printFile :: ExceptT MyError IO String
printFile = do
path <- getEnv' "FOLDER"
readFile' path
mapIOError :: String -> IO a -> ExceptT MyError IO a
mapIOError msg = ExceptT . fmap (first mapError) . tryIOError
where mapError err | isDoesNotExistError err = MissingError msg
mapError err = SomeIOError err
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
As we know main function has type IO ().
However, it is problem for me, because my program may return error. It means that I am executing from main function something like that:
ErrorT String IO ()
Of course, at this moment I have problem with type errors.
What should I do ?
args <- getArgs
s <- readFile $ head args
myFoo s
Where myFoo :: String -> ErrorT String IO ()
You need to run it with runErrorT:
runErrorT :: ErrorT e m a -> m (Either e a)
Since myFoo returns a ErrorT String IO () this will evaluate to an IO (Either String ()) which you execute in main and match on the result:
args <- getArgs
s <- readFile $ head args
result <- runErrorT (myFoo s)
case result of
Right _ -> putStrLn "ok!"
Left(err) -> putStrLn $ "Error: " ++ err
To expand on #Lee's answer, you can then use exitFailure and exitSuccess from System.Exit to return an appropriate error code to the calling process:
module Main (main) where
import Control.Monad.Error
import System.Environment
import System.Exit
myFoo :: String -> ErrorT String IO ()
myFoo = undefined
main :: IO ()
main = do
args <- getArgs
s <- readFile $ head args
result <- runErrorT (myFoo s)
case result of
Right _ -> do
putStrLn "OK"
exitSuccess
Left (e) -> do
putStrLn $ "Error: " ++ e
exitFailure
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..."
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