Just Catching a Few Things
We've now seen a few of the different nuances in handling exceptions within our code. Earlier this month we learned about the "catch" and "handle" functions, which are the backbone of capturing exceptions in our code. And then last time around we saw the importance of how these catch particular types of exceptions.
Today we'll go over a new pair of handling functions. These allow us to narrow down the range of exceptions we'll handle, rather than catching every exception of a particular type. These functions are catchJust
, and its flipped counterpart, handleJust
. Here are the type signatures:
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
The defining features of these handler functions is the filter predicate at the start: the first argument of type e -> Maybe b
. This takes the input exception type and returns a Maybe
value. That maybe value can be some transformation on the exception input.
Let's make a simple example using our ListException
type. Let's recall what this type looks like:
data ListException =
ListIsEmpty String |
NotEnoughElements String Int Int
deriving (Show)
instance Exception ListException
As a simple example, let's write a predicate that will only capture our ListIsEmpty
exception. It will return the name of the function causing the error.
isEmptyList :: ListException -> Maybe String
isEmptyList (ListIsEmpty functionName) = Just functionName
isEmptyList _ = Nothing
Now we'll write a function that will process a list and print its first element. But if it is empty, it will print the name of the function. This will use catchJust
.
printFirst :: [Int] -> IO Int
printFirst input = catchJust isEmptyList action handler
where
action :: IO Int
action = do
let result = myHead input
print result
return result
handler :: String -> IO Int
handler functionName = do
putStrLn $ "Caught Empty List exception from function: " ++ functionName ++ ". Returning 0!"
print 0
return 0
Now when run this, we'll see the error message we expect:
main :: IO ()
main = do
result1 <- printFirst []
result2 <- printFirst [2, 3, 4]
print $ result1 + result2
...
Caught Empty List exception from function: myHead. Returning 0!
0
2
2
But if we change the function to use "sum2Pairs" instead (which throws NotEnoughElements
, rather than ListIsEmpty
), then we'll still see the exception!
sum2Pairs :: (Num a) => [a] -> (a, a)
sum2Pairs (a : b : c : d : _) = (a + b, c + d)
sum2Pairs input = throw (NotEnoughElements "sum2Pairs" 4 (length input))
newMain :: IO ()
newMain = do
result1 <- printSums []
result2 <- printSums [2, 3, 4, 5]
print $ (result1, result2)
...
>> stack exec my-program
my-program: NotEnoughElements "sum2Pairs" 4 0
We can modify the predicate so that it always catches exceptions from a particular function and gives different error messages depending on the exception thrown:
isSum2Pairs :: ListException -> Maybe ListException
isSum2Pairs e@(ListIsEmpty function) = if function == "sum2Pairs'"
then Just e
else Nothing
isSum2Pairs e@(NotEnoughElements function _ _) = if function == "sum2Pairs'"
then Just e
else Nothing
Now let's modify sum2Pairs
so that it can throw either error type, depending on its input:
sum2Pairs' :: (Num a) => [a] -> (a, a)
sum2Pairs' (a : b : c : d : _) = (a + b, c + d)
sum2Pairs' [] = throw (ListIsEmpty "sum2Pairs'")
sum2Pairs' input = throw (NotEnoughElements "sum2Pairs'" 4 (length input))
When we use this updated version in our main function, we'll see we get a variety of outputs!
printSums' :: [Int] -> IO (Int, Int)
printSums' input = catchJust isSum2Pairs action handler
where
action :: IO (Int, Int)
action = do
let result = sum2Pairs' input
print result
return result
handler :: ListException -> IO (Int, Int)
handler e = do
putStrLn $ "Caught exception: " ++ show e ++ ". Returning (0, 0)!"
print (0, 0)
return (0, 0)
newMain :: IO ()
newMain = do
result1 <- printSums' []
result2 <- printSums' [2, 3, 4]
print $ (result1, result2)
...
>> stack exec my-program
Caught exception: ListIsEmpty "sum2Pairs'". Returning (0, 0)!
(0,0)
Caught exception: NotEnoughElements "sum2Pairs'" 4 3. Returning (0, 0)!
(0,0)
((0,0),(0,0))
Next time, we'll look at a more practical usage of this approach with IO Errors! Until then, make sure you subscribe to our monthly newsletter so you can stay up to date with the latest news!