Ignat Insarov
Ignat Insarov

Reputation: 4832

How can I classify exceptions purely?

So my code (by design) gives me an exception, and I need to find out if it belongs to a specific "class" of exceptions — in any sense of the word. I can do it like this:

data Case a = ExpectedException | SuddenException | AsynchronousException | Result a

classifyExceptionM :: forall m. MonadCatch m => m () -> m (Case ())
classifyExceptionM a = fmap Result a `catches`
    [ Handler ((\e -> return ExpectedException)     :: ArithException     -> m (Case ()))
    , Handler ((\e -> return SuddenException)       :: ArrayException     -> m (Case ()))
    , Handler ((\e -> return AsynchronousException) :: SomeAsyncException -> m (Case ()))
    ]

This is ugly but it works. Now I wish to perform the same classification in pure code. Yet again, I may throw my exception with the pure Catch monad and catch it again:

classifyException' :: Exception e => e -> Case ()
classifyException' e = either undefined id $ runCatch $ throwM e `catches`
    [ Handler ((\e -> return ExpectedException)     :: ArithException     -> Catch (Case ()))
    , Handler ((\e -> return SuddenException)       :: ArrayException     -> Catch (Case ()))
    , Handler ((\e -> return AsynchronousException) :: SomeAsyncException -> Catch (Case ()))
    ]

Pure, works, but ugly all the same.

What I wish to have is something like this:

classifyException :: Exception e => e -> Case ()
classifyException e = case sortOf e of
    DivideByZero     -> ExpectedException
    IndexOutOfBounds -> SuddenException
    StackOverflow    -> AsynchronousException
    _                -> error "Encountered unclassifiable exception"

— But I could not pattern match on exceptions because of their tragically abstract nature. The closest I can get is this:

classifyException :: SomeException -> Case ()
classifyException (SomeException e) =
    if typeOf e == typeOf DivideByZero then ExpectedException else
       if typeOf e == typeOf (IndexOutOfBounds "") then SuddenException else
          if typeOf e == typeOf (SomeAsyncException StackOverflow) then AsynchronousException else
             error $ "Encountered unclassifiable exception: " ++ show e

Notice how I need to specifically select SomeException as the type of my exception. This is because all exceptions are secretly wrapped in this type when they are thrown, and typeOf any exception I could catch is always SomeException.

It is all just a sorrowful mess. How can I improve?

Upvotes: 0

Views: 94

Answers (1)

moonGoose
moonGoose

Reputation: 1510

I think perhaps your question is more about style than substance, which is why it's not received much attention - the end point is somewhat unclear. Some of your attempts could be tidied, eg.

classifyException' :: Exception e => e -> Case ()
classifyException' e = fromRight (error "blah") $ runCatch $ throwM e `catches`
    [ Handler @ArithException     $ const $ pure ExpectedException
    , Handler @ArrayException     $ const $ pure SuddenException 
    , Handler @SomeAsyncException $ const $ pure AsynchronousException
    ]

Two other alternatives,

classifyException :: SomeException e => e -> Case ()
classifyException = \case
    (fromException -> Just ArithException) -> ExpectedException
    (fromException -> Just ArrayException) -> SuddenException
    (fromException -> Just AsyncException) -> AsynchronousException
    _                                      -> error "Encountered unclassifiable exception"

or (with some language extension soup)

classifyException e =
  if | is @ArithException -> ExpectedException
     | is @ArrayException -> SuddenException
     | is @AsyncException -> AsynchronousException
     | otherwise          -> error "Encountered unclassifiable exception"
  where is :: forall e. Exception e => Bool
        is = isJust $ fromException @e e

Upvotes: 1

Related Questions