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!

Previous
Previous

Further Filtering

Next
Next

Exception Type Details