Home > database >  How to pattern match on SomeException?
How to pattern match on SomeException?

Time:08-31

Consider the following code:

import Control.Exception
import Data.Typeable

handler :: SomeException -> IO ()
handler (SomeException e) = do
  case e of
    -- COMMENTED OUT DivideByZero -> putStrLn $ "handler: DivideByZero!"
    _ -> putStrLn $ "handler: exception is: "    (show e)    " of type: "    (show $ typeOf e)

blowUp = do
    putStrLn (show (1 `div` 0))

main = catch blowUp handler

It works just fine and prints out:

handler: exception is: divide by zero of type: ArithException

However if I uncomment the DivideByZero line, I get the following compile-time error:

testexc2.hs:7:5: error:
    • Couldn't match expected type ‘e’
                  with actual type ‘ArithException’
      ‘e’ is a rigid type variable bound by
        a pattern with constructor:
          SomeException :: forall e. Exception e => e -> SomeException,
        in an equation for ‘handler’
        at testexc2.hs:5:10-24
    • In the pattern: DivideByZero
      In a case alternative:
          DivideByZero -> putStrLn $ "handler: DivideByZero!"
      In a stmt of a 'do' block:
        case e of
          DivideByZero -> putStrLn $ "handler: DivideByZero!"
          _ -> putStrLn
                 $ "handler: exception is: "
                        (show e)    " of type: "    (show $ typeOf e)
    • Relevant bindings include e :: e (bound at testexc2.hs:5:24)
  |
7 |     DivideByZero -> putStrLn $ "handler: DivideByZero!"
  |

Is there a way to pattern match on SomeException's wrapped exception (other than the wildcard _ pattern)?

If yes: how?

If not: why?

Thanks!

CodePudding user response:

You could use

fromException :: SomeException -> Maybe e 

as in (untested)

handler :: SomeException -> IO ()
handler e@(SomeException e') = case fromException e of
   Just DivideByZero ->
      putStrLn $ "handler: DivideByZero!"
   Nothing ->
      putStrLn $ "handler: exception is: "    (show e')    " of type: "    (show $ typeOf e')

or even

handler :: SomeException -> IO ()
handler e@(SomeException e')
   | Just DivideByZero <- fromException e 
      = putStrLn $ "handler: DivideByZero!"
   | Just _ <- fromException e :: Maybe IOException
      = putStrLn $ "handler: IOException!"
   ...
   | otherwise 
      = putStrLn $ "handler: exception is: "    (show e')    " of type: "    (show $ typeOf e')

It is also possible to play with Typeable directly, e.g. using cast or (maybe) eqT to test if the exception is of a given type.

Even with current GHC, I believe PatternSynonyms could be exploited to allow a syntax similar to yours:

{-# LANGUAGE ViewPatterns, PatternSynonyms, TypeApplications  #-}

import Control.Exception

-- ArithException, only DivideByZero subcase
pattern ExDivisionByZero <- (fromException -> Just DivideByZero)

-- IOException, all subcases
pattern ExIOException    <- (fromException -> Just (_ :: IOException))

handler :: SomeException -> IO ()
handler ExDivisionByZero = putStrLn $ "handler: DivideByZero!"
handler ExIOException    = putStrLn $ "handler: IOException!"
  • Related