Further Filtering
In our previous article we explored the functions catchJust
and handleJust
. These allow us to do some more specific filtering on the exceptions we're trying to catch. With catch
and handle
, we'll catch all exceptions of a particular type. And in many cases, especially where we have pre-defined our own error type, this is a useful behavior.
However, we can also consider cases with built-in error types, like IOError
. There are a lot of different IO errors our program could throw. And sometimes, we only watch to catch a few of them.
Let's consider just two of these examples. In both actions of this function, we'll open a file and print its first line. But in the first example, the file itself does not exist. In the second example, the file exists, but we'll also open the file in "Write Mode" while we're reading it.
main :: IO ()
main = do
action1
action2
where
action1 = do
h <- openFile "does_not_exist.txt" ReadMode
firstLine <- hGetLine h
putStrLn firstLine
hClose h
action2 = do
h <- openFile "does_exist.txt" ReadMode
firstLine <- hGetLine h
putStrLn firstLine
h2 <- openFile "does_exist.txt" WriteMode
hPutStrLn h2 "Hello World"
hClose h
These will cause two different kinds of IOError
. But we can catch them both with a handler function:
main :: IO ()
main = do
handle handler action1
handle handler action2
where
action1 = do
h <- openFile "does_not_exist.txt" ReadMode
firstLine <- hGetLine h
putStrLn firstLine
hClose h
action2 = do
h <- openFile "does_exist.txt" ReadMode
firstLine <- hGetLine h
putStrLn firstLine
h2 <- openFile "does_exist.txt" WriteMode
hPutStrLn h2 "Hello World"
hClose h
handler :: IOError -> IO ()
handler e = print e
And now we can run this and see both errors are printed.
>> stack exec my-program
does_not_exist.txt: openFile: does not exist (No such file or directory)
First line
does_exist.txt: openFile: resource busy (file is locked)
But suppose we only anticipated our program encountering the "does not exist" error. We don't expect a "resource busy" error, so we want our program to crash if it happens so we are forced to fix it. We need to filter the error types and use handleJust
instead.
Luckily, there are many predicates on IOErrors
like isDoesNotExistError
. We can use this to write our own predicate:
-- Library function
isDoesNotExistError :: IOError -> Bool
-- Our predicate
filterIO :: IOError -> Maybe IOError
filterIO e = if isDoesNotExistError e
then Just e
else Nothing
Now let's quickly recall the type signatures of catchJust
and handleJust
:
catchJust :: Exception e =>
(e -> Maybe b) ->
IO a ->
(b -> IO a) ->
IO a
handleJust :: Exception e =>
(e -> Maybe b) ->
(b -> IO a) ->
IO a ->
IO a
We can rewrite our function now so it only captures the "does not exist" error. We'll add the predicate, and use it with handleJust
, along with our existing handler.
main :: IO ()
main = do
handleJust filterIO handler action1
handleJust filterIO handler action2
where
action1 = do
h <- openFile "does_not_exist.txt" ReadMode
firstLine <- hGetLine h
putStrLn firstLine
hClose h
action2 = do
h <- openFile "does_exist.txt" ReadMode
firstLine <- hGetLine h
putStrLn firstLine
h2 <- openFile "does_exist.txt" WriteMode
hPutStrLn h2 "Hello World"
hClose h
handler :: IOError -> IO ()
handler e = putStr "Caught error: " >> print e
filterIO :: IOError -> Maybe IOError
filterIO e = if isDoesNotExistError e
then Just e
else Nothing
When we run the program, we'll see that the first error is caught. We see our custom message "Caught error" instead of the program name. But in the second instance, our program crashes!
>> stack exec my-program
Caught error: does_not_exist.txt: openFile: does not exist (no such file or directory)
First line
my-program: does_exist.txt: openFile: resource busy (file is locked)
Hopefully this provides you with a clear and practical example of how you can use these filtering functions for handling your errors! Next time, we'll take a deeper look at the "bracket" pattern. We touched on this during IO month, but it's an important concept, and there are more helper functions we can incorporate with it! So make sure to stop back here later in the week. And also make sure to subscribe to our monthly newsletter if you haven't already!