Exception Handling

Table of Contents

  • Index
  • Repository
  • 1 Exception Handling

    1.1 Overview

    Exceptions are events that happens during run-time that disrupts the normal flow of execution. The common sources of exceptions are partial functions (functions not defined to all its domain), user-thrown exceptions and IO.

    The Haskell static typing system by itself frees the developer from "NullPointer Exceptions" and "value error" exceptions that happens when the code tries to sum a string and int in untyped languages. The compiler notifies the developer that it happened not allowing the code to run or go on production before it is solved.

    • The type system avoids value error exceptions and null pointer exceptions avoiding unpleasant surprise at run-time.
    • Maybe or Option type avoids null checking and Null pointer exceptions and raising exceptions for invalid data.
    • Either type is similar to Maybe, however it allows to report the failure or what is wrong with input.
    • Summarizing: The strong static typing system will help to avoid many hours of debugging trying to find where the null pointer exception or value error exception comes from, customer complaints and headaches.

    See also:

    1.2 Avoiding Exceptions in Pure functions

    1.2.1 Overview

    For pure functions and functions that performs validation like valideDateOfBirth the best way is to indicate that the function may fail making it explicit on the type system using Maybe or Either returning Nothing for Maybe type or Left <error-type> for Either type.

    1.2.2 Avoiding Exception in partial functions

    Examples of partial functions:

    > head [10, 20, 30] 
    10
    it :: Num a => a
    >
    
    > head [] 
    *** Exception: Prelude.head: empty list
    >
    
    > div 100 0 
    *** Exception: divide by zero
    >
    
    > read "100" :: Int 
    100
    it :: Int
    > read "-100" :: Int 
    -100
    it :: Int
    > read "noise" :: Int 
    *** Exception: Prelude.read: no parse
    >
    

    Example: Avoiding exceptions using Maybe (Option type) or Either:

    :{
    headSafe :: [a] -> Maybe a
    headSafe []     = Nothing 
    headSafe (x:_)  = Just x
    :}
    
    > headSafe [1, 2, 3, 4, 5]
    Just 1
    it :: Num a => Maybe a
    > headSafe []
    Nothing
    it :: Maybe a
    > :t headSafe
    headSafe :: [a] -> Maybe a
    >
    
    :t div 
    > div :: Integral a => a -> a -> a
    >
    
    :{
    divSafe :: Integral a => a -> a -> Maybe a
    divSafe x 0 = Nothing
    divSafe x y = Just (div x y)
    :}
    
    > divSafe 100 20 
    Just 5
    it :: Integral a => Maybe a
    > divSafe 100 0 
    Nothing
    it :: Integral a => Maybe a
    >
    
    -- Use readMaybe instead of read 
    -- 
    > import Text.Read (readMaybe)
    >
    
    > readMaybe "Just 200" :: Maybe (Maybe Int)
    Just (Just 200)
    it :: Maybe (Maybe Int)
    > readMaybe "Nothing" :: Maybe (Maybe Int)
    Just Nothing
    it :: Maybe (Maybe Int)
    > readMaybe "Notasdasdas" :: Maybe (Maybe Int)
    Nothing
    it :: Maybe (Maybe Int)
    > 
    
    > readMaybe "[1, 2, 3, 4, 5]" :: Maybe [Int] 
    Just [1,2,3,4,5]
    it :: Maybe [Int]
    
    > readMaybe "garbage" :: Maybe [Int] 
    Nothing
    it :: Maybe [Int]
    > 
    
    > readMaybe "[100.0, 200.0, 300.0]" :: Maybe [Double] 
    Just [100.0,200.0,300.0]
    it :: Maybe [Double]
    > readMaybe ", 200.0, 300.0]" :: Maybe [Double] 
    Nothing
    it :: Maybe [Double]
    >
    

    1.2.3 Avoid exception in data validation

    Example functions that performs data validation or user validation:

    For function that performs data validation it is better to return the result as an Maybe or Either type instead of raise an Exception. Maybe is better for functions that only have one possible failure or is not necessary to know the type of failure. The Either type is useful to report error, failure information or invalid data.

    import Text.Read (readMabye)
    
    :{
    validateAge :: String -> Int  
    validateAge input =
      case readMaybe input of
        Nothing  -> error "Invalid age. Not a number"
        Just age -> case age of 
                    _ | age < 0    -> error "Error: Invalid age. It must be greater than zero."
                    _ | age <= 18  -> error "Error: Below legal age to sign the contract."
                    _ | age > 200  -> error "Error: Invalid age. Impossible age."
                    _              -> age   
    :}
    
    > validateAge "asdfa" 
    *** Exception: Invalid age. Not a number
    CallStack (from HasCallStack):
      error, called at <interactive>:1083:17 in interactive:Ghci54
    
    > validateAge "-100" 
    *** Exception: Error: Invalid age. It must be greater than zero.
    CallStack (from HasCallStack):
      error, called at <interactive>:1085:35 in interactive:Ghci54
    
    > validateAge "16" 
    *** Exception: Error: Below legal age to sign the contract.
    CallStack (from HasCallStack):
      error, called at <interactive>:1086:35 in interactive:Ghci54
    >
    
    > validateAge "300" 
    *** Exception: Error: Invalid age. Impossible age.
    CallStack (from HasCallStack):
      error, called at <interactive>:1087:35 in interactive:Ghci54
    >
    
    > validateAge "30" 
    30
    it :: Int
    
    > validateAge "20" 
    20
    it :: Int
    
    > validateAge "35"  
    35
    it :: Int
    >
    

    This function can be refactored to:

    import Text.Read (readMabye)
    
    :{
    validateAge :: String -> Either String Int  
    validateAge input =
      case readMaybe input of
        Nothing  -> Left "Invalid input. Not a number"
        Just age -> case age of 
                    _ | age < 0    -> Left "Error: Invalid age. It must be greater than zero."
                    _ | age <= 18  -> Left "Error: Below legal age to sign the contract."
                    _ | age > 200  -> Left "Error: Invalid age. Impossible age."
                    _              -> Right age   
    :}
    
    > validateAge "-100"
    Left "Error: Invalid age. It must be greater than zero."
    it :: Either String Int
    > validateAge "16"
    Left "Error: Below legal age to sign the contract."
    it :: Either String Int
    > validateAge "300"
    Left "Error: Invalid age. Impossible age."
    it :: Either String Int
    > validateAge "400"
    Left "Error: Invalid age. Impossible age."
    it :: Either String Int
    > validateAge "20"
    Right 20
    it :: Either String Int
    > validateAge "19"
    Right 19
    it :: Either String Int
    >
    
    > mapM_ print $ map validateAge ["safsdf", "-100", "garbage", "400", "7", "15", "20", "25"]
    Left "Invalid input. Not a number"
    Left "Error: Invalid age. It must be greater than zero."
    Left "Invalid input. Not a number"
    Left "Error: Invalid age. Impossible age."
    Left "Error: Below legal age to sign the contract."
    Left "Error: Below legal age to sign the contract."
    Right 20
    Right 25
    it :: ()
    >
    

    Algebraic data types are more friendly to pattern matching than strings. So the code above could be refactored to:

    import Text.Read (readMabye)
    
    :{ 
    data AgeError = AgeInvalidInput
                  | AgeBelowLegalAge
                  | AgeImpossible
                  deriving (Eq, Show, Read)
    :}
    
    
    :{
    validateAge :: String -> Either AgeError Int  
    validateAge input =
      case readMaybe input of
        Nothing  -> Left AgeInvalidInput
        Just age -> case age of 
                    _ | age < 0    -> Left AgeImpossible
                    _ | age <= 18  -> Left AgeBelowLegalAge
                    _ | age > 200  -> Left AgeImpossible
                    _              -> Right age   
    :}
    
    > map (\input -> (input, validateAge input)) ["safsdf", "-100", "garbage", "400", "7", "15", "20", "25"]
    [("safsdf",Left AgeInvalidInput),("-100",Left AgeImpossible),("garbage",Left AgeInvalidInput),
     ("400",Left AgeImpossible),("7",Left AgeBelowLegalAge),
     ("15",Left AgeBelowLegalAge),("20",Right 20),
     ("25",Right 25)]
    it :: [([Char], Either AgeError Int)]
    > 
    > 
    
    > mapM_ print $ map validateAge ["safsdf", "-100", "garbage", "400", "7", "15", "20", "25"]
    Left AgeInvalidInput
    Left AgeImpossible
    Left AgeInvalidInput
    Left AgeImpossible
    Left AgeBelowLegalAge
    Left AgeBelowLegalAge
    Right 20
    Right 25
    it :: ()
    >
    
    :{
    showAgeError :: AgeError -> String
    showAgeError age =
       case age of
         AgeBelowLegalAge ->  "Error: Below legal age to sign the contract."
         AgeImpossible     ->  "Error: Invalid age. Impossible age."
         AgeInvalidInput    ->  "Invalid input. Not a number"
    :}
    
    > showAgeError AgeImpossible
    "Error: Invalid age. Impossible age."
    it :: String
    > showAgeError AgeInvalidInput
    "Invalid input. Not a number"
    it :: String
    > showAgeError AgeImpossible
    "Error: Invalid age. Impossible age."
    it :: String
    >
    
    
    :{
    mapLeft :: (e -> b) -> Either e a -> Either b a
    mapLeft fn value =
      case value of
        Right a -> Right a
        Left  e -> Left (fn e)
    :}
    
    > mapLeft showAgeError $ validateAge "20000" 
    Left "Error: Invalid age. Impossible age."
    it :: Either String Int
    > mapLeft showAgeError $ validateAge "-100" 
    Left "Error: Invalid age. Impossible age."
    it :: Either String Int
    > mapLeft showAgeError $ validateAge "10" 
    Left "Error: Below legal age to sign the contract."
    it :: Either String Int
    > mapLeft showAgeError $ validateAge "15" 
    Left "Error: Below legal age to sign the contract."
    it :: Either String Int
    > mapLeft showAgeError $ validateAge "25" 
    Right 25
    it :: Either String Int
    > mapLeft showAgeError $ validateAge "36" 
    Right 36
    it :: Either String Int
    >
    

    Refactoring the function to validate multiple data:

    import Text.Read (readMabye)
    
    :{ 
    data AgeError = AgeInvalidInput
                  | AgeBelowLegalAge
                  | AgeImpossible
                  deriving (Eq, Show, Read)
    :}
    
    type UserData = (String, Int)
    
    
    :{
    data UserDataError = UserAgeError AgeError
                       | UserNameError
                       deriving(Eq, Show, Read)
    :}
    
    :{
    mapLeft :: (e -> b) -> Either e a -> Either b a
    mapLeft fn value =
      case value of
        Right a -> Right a
        Left  e -> Left (fn e)
    :}
    
    
    :{
    validateAge :: String -> Either UserDataError Int  
    validateAge input = mapLeft UserAgeError $ validateAgeAux input 
      where
        validateAgeAux input = 
          case readMaybe input of
            Nothing  -> Left AgeInvalidInput
            Just age -> case age of 
                        _ | age < 0    -> Left AgeImpossible
                        _ | age <= 18  -> Left AgeBelowLegalAge
                        _ | age > 200  -> Left AgeImpossible
                        _              -> Right age   
    :}
    
    
    :{
    validateName :: String -> Either UserDataError String   
    validateName name =
      case name of
        "" -> Left $ UserNameError
        _  -> Right name
    :}
    
    :{
    validateUser :: String -> String -> Either UserDataError UserData
    validateUser name age = do
      userName <- validateName name
      userAge  <- validateAge age
      return (userName, userAge)
    :}
    
    
    -- Testing: 
    --------------------
    
    > validateAge "-200" 
    Left (UserAgeError AgeImpossible)
    it :: Either UserDataError Int
    > validateAge "1000" 
    Left (UserAgeError AgeImpossible)
    it :: Either UserDataError Int
    > validateAge "30" 
    Right 30
    it :: Either UserDataError Int
    > validateAge "25" 
    Right 25
    it :: Either UserDataError Int
    > validateAge "16" 
    Left (UserAgeError AgeBelowLegalAge)
    it :: Either UserDataError Int
    
    > validateAge "25" 
    Right 25
    it :: Either UserDataError Int
    >
    
    > validateName "" 
    Left UserNameError
    it :: Either UserDataError String
    > 
    > validateName "John" 
    Right "John"
    it :: Either UserDataError String
    >
    
    > validateUser "John" "300" 
    Left (UserAgeError AgeImpossible)
    it :: Either UserDataError UserData
    > validateUser "John" "15" 
    Left (UserAgeError AgeBelowLegalAge)
    it :: Either UserDataError UserData
    > validateUser "John" "-100" 
    Left (UserAgeError AgeImpossible)
    it :: Either UserDataError UserData
    > validateUser "" "-100" 
    Left UserNameError
    it :: Either UserDataError UserData
    >
    
    > validateUser "John" "20" 
    Right ("John",20)
    it :: Either UserDataError UserData
    >
    

    1.3 Catching Exceptions

    1.3.1 Overview

    The haskell modules Control.Exception and System.IO.Error provides functions and type constructors to deal with exceptions.

    Exceptions can only be caught inside IO Monad.

    Function   Signature Summary / Short Description.
           
    Control.Exception      
           
    throw :: Exception e => e -> a Throw an exception.
    throwIO :: Exception e => e -> IO a A variant of throw that can only be used within the IO monad.
    catch :: Exception e => IO a -> (e -> IO a) -> IO a Catch exceptions.
    try :: Exception e => IO a -> IO (Either e a) Similar to catch, but returns an Either.
    handle :: Exception e => (e -> IO a) -> IO a -> IO a A version of catch with the arguments swapped around.
    finally      
    evaluate      
           
    System.IO.Error      
           
    ioError :: IOError -> IO a Raise an IOError in the IO monad.
    catchIOError :: IO a -> (IOError -> IO a) -> IO a Like the fuction catch, however catchs only IO exceptions.
    tryIOError :: IO a -> IO (Either IOError a) Like the function try, however only aplicable to IO exceptions.
    userError :: String -> IOError Construct an IOError value with a string describing the error.
           
    isAlreadyExistsError :: IOError -> Bool  
    isDoesNotExistError :: IOError -> Bool  
    isAlreadyInUseError :: IOError -> Bool  
    isFullError :: IOError -> Bool  
    isEOFError :: IOError -> Bool  
    isIllegalOperation :: IOError -> Bool  
    isPermissionError :: IOError -> Bool  
    isUserError :: IOError -> Bool  

    System.IO.Error predicates

    Notes:

    • There are no predicate functions for all IO exception types.
    • The exception type constructor are not exposed, so the only way to get the type of io exception is by using the predicates functions available at module System.IO.Error.

    See module: System.IO.Error for more details.

    Exception type Predicate Exception message Cause:
    (abstract)   or exception string  
    AlreadyExists isAlreadyExistsError already exists File or directory already exists.
           
    NoSuchThing isDoesNotExistError does not exist File, directory or enviroment variable
          doesn't exists.
           
    ResourceBusy ? resource busy  
           
    ResourceExhausted ? resource exhausted Insufficient resources are available
          to perform the operation.
           
    EOF isEOFError end of file Reached end of file while trying to read
          some line or character.
           
    IllegalOperation isIllegalOperation illegal operation The implementation does not support system calls.
           
    PermissionDenied isPermissionError permission denied The process has insufficient privileges to
          perform the operation.
           
    UserError isUserError user error Exception thrown by user.
           
    HardwareFault ? hardware fault  
           
    InappropriateType ? inappropriate type  
           
    Interrupted ? interrupted  
           
    InvalidArgument ? invalid argument  
           
    OtherError ? failed  
           
    ProtocolError ? protocol error  
           
    ResourceVanished ? resource vanished  
           
    SystemError ? system error  
           
    TimeExpired ? timeout  
           
    UnsatisfiedConstraints ? unsatisfied constraints – ultra-precise!  
           
    UnsupportedOperation ? unsupported operation  

    Arithmetic Exceptions: (Module: Control.Exception)

    Type Constructor Exception Message
    Overflow arithmetic overflow
    Underflow arithmetic underflow
    LossOfPrecision loss of precision
    DivideByZero divide by zero
    Denormal denormal
    RatioZeroDenominator Ratio has zero denominator

    Exception Hierarchy:

    Exception

    • SomeException - Root of all exceptions
      • IOError / IOException
      • AsyncException
        • StackOverflow
        • HeapOverflow
        • ThreadKilled
        • ThreadKilled
      • ArithException
        • Overflow
        • Underflow
        • LossOfPrecision
        • DivideByZeror
        • Denormal
        • RatioZeroDenominator
      • ArrayException
      • AssertionFailed

    See also:

    Papers:

    1.3.2 Examples of Exceptions:

    > div 10 0 
    
    *** Exception: divide by zero
    > > 
    > div 10 0 
    *** Exception: divide by zero
    > head [] 
    *** Exception: Prelude.head: empty list
    > let x = undefined :: Int 
    x :: Int
    > x 
    *** Exception: Prelude.undefined
    CallStack (from HasCallStack):
      error, called at libraries/base/GHC/Err.hs:79:14 in base:GHC.Err
      undefined, called at <interactive>:1741:9 in interactive:Ghci116
    > 
    
    > readFile "/etc/issue" 
    "Manjaro Linux \\r  (\\n) (\\l)\n\n\n"
    it :: String
    
    > readFile "/etc/issuesada" 
    *** Exception: /etc/issuesada: openFile: does not exist (No such file or directory)
    > 
    
    > readFile "/etc/shadow" 
    *** Exception: /etc/shadow: openFile: permission denied (Permission denied)
    >
    
    > readFile "/etc/" 
    *** Exception: /etc/: openFile: inappropriate type (is a directory)
    > 
    > readFile "/etcsad/" 
    *** Exception: /etcsad/: openFile: does not exist (No such file or directory)
    > 
    >
    
    > import qualified System.Directory as D
    
    > D.createDirectory "/etc/fstab/my-directory" 
    *** Exception: /etc/fstab/my-directory: createDirectory: inappropriate type (Not a directory)
    
    > D.createDirectory "/dev/sda1/dummy" 
    *** Exception: /dev/sda1/dummy: createDirectory: inappropriate type (Not a directory)
    
    > D.createDirectory "/i dont have permission" 
    *** Exception: /i dont have permission: createDirectory: permission denied (Permission denied)
    >
    

    1.3.3 Catching Exceptions with catch

    1.3.3.1 Overview
    • catch <computation which may fail> <exception handler>
    • Exception e > IO a -> (e -> IO a) -> IO a
    1.3.3.2 Catching all exceptions
    import Control.Exception 
    import Data.Typeable (typeOf)
    
    > :t catch
    catch :: Exception e => IO a -> (e -> IO a) -> IO a
    >
    
    > :t catch (return $ Just $ div 10 0) (\(SomeException e) -> print  e >> return Nothing)
    catch (return $ Just $ div 10 0) (\(SomeException e) -> print  e >> return Nothing)
      :: Integral a => IO (Maybe a)
    
    
    > catch (return $ Just $ div 10 0) (\(SomeException e) -> print  e >> return Nothing)
    Just *** Exception: divide by zero
    > 
    
    > :t catch (return $ Just $ div 10 0) (\(SomeException e) -> print  e >> return Nothing)
    catch (return $ Just $ div 10 0) (\(SomeException e) -> print  e >> return Nothing)
      :: Integral a => IO (Maybe a)
    > 
    
    > catch (print $ div 10 0) (\(SomeException e) -> print $ e)
    divide by zero
    it :: ()
    > 
    
    > :t catch (print $ div 10 0) (\(SomeException e) -> print $ e)
    catch (print $ div 10 0) (\(SomeException e) -> print $ e) :: IO ()
    > 
    
    > catch (print $ div 10 0) (\(SomeException e) -> print $ typeOf e)
    ArithException
    it :: ()
    > 
    
    > :t catch (print $ div 10 0) (\(SomeException e) -> print $ typeOf e)
    catch (print $ div 10 0) (\(SomeException e) -> print $ typeOf e)
      :: IO ()
    > 
    
    
    :{
    testExceptionType :: IO () -> IO ()
    testExceptionType thunk =  catch thunk handler
      where
        -- Catch All Exceptions -- It is not recommended in real life.
        handler :: SomeException -> IO ()
        handler (SomeException e) = putStrLn $ "I caught an exception.\nMessage =  " ++ show e ++ "\nType of exception = " ++ show (typeOf e)
    :}  
    
    
    > testExceptionType (print $ div 10 0)
    I caught an exception.
    Message =  divide by zero
    Type of exception = ArithException
    it :: ()
    > 
    
    > testExceptionType (error "Fatal kernel error")
    I caught an exception.
    Message =  Fatal kernel error
    CallStack (from HasCallStack):
      error, called at <interactive>:82:20 in interactive:Ghci13
    Type of exception = ErrorCall
    it :: ()
    > 
    
    > testExceptionType (readFile "/etc/shadow" >>= putStrLn)
    I caught an exception.
    Message =  /etc/shadow: openFile: permission denied (Permission denied)
    Type of exception = IOException
    it :: ()
    > 
    
    > testExceptionType (readFile "/etc/" >>= putStrLn)
    I caught an exception.
    Message =  /etc/: openFile: inappropriate type (is a directory)
    Type of exception = IOException
    it :: ()
    > 
    
    > testExceptionType (readFile "/" >>= putStrLn)
    I caught an exception.
    Message =  /: openFile: inappropriate type (is a directory)
    Type of exception = IOException
    it :: ()
    > 
    
    
    > testExceptionType (ioError $ userError "I am throwing an Exception")
    I caught an exception.
    Message =  user error (I am throwing an Exception)
    Type of exception = IOException
    it :: ()
    > 
    
    
    > testExceptionType (print $ head [1, 2, 3])
    1
    it :: ()
    > testExceptionType (print $ head [])
    I caught an exception.
    Message =  Prelude.head: empty list
    Type of exception = ErrorCall
    it :: ()
    >   
    
    
      > testExceptionType (putStrLn "Insert a line" >> getLine >>= putStrLn)
    Insert a line
    some line
    some line
    it :: ()
    
    -- User enter Ctrl + C to cancel the current input.
    --
    > testExceptionType (putStrLn "Insert a line" >> getLine >>= putStrLn)
    Insert a line
    ^CI caught an exception.
    Message =  user interrupt
    Type of exception = SomeAsyncException
    it :: ()
    >
    
    > let x = undefined :: String 
    x :: String
    > 
    
    > testExceptionType (putStrLn x)
    I caught an exception.
    Message =  Prelude.undefined
    CallStack (from HasCallStack):
      error, called at libraries/base/GHC/Err.hs:79:14 in base:GHC.Err
      undefined, called at <interactive>:103:9 in interactive:Ghci21
    Type of exception = ErrorCall
    it :: ()
    >
    
    1.3.3.3 Narrowing Exceptions

    Catches only arithmetic exceptions while ignoring other types of exceptions.

    :{
    testException :: IO () -> IO ()
    testException ioAction =  catch ioAction handler
        where
          handler :: ArithException -> IO ()
          handler e =  putStrLn $ "I got an Arithmetic exception which message is = " ++ show e
    :}
    
    > testException (print $ div 100 10)
    10
    it :: ()
    > 
    
    {-- It only will handle Arithmetic exception ignoring all other types of Exceptions. -}
    > testException (print $ div 100 0)
    I got an Arithmetic exception which message is = divide by zero
    it :: ()
    >
    
    > testException (error "Unrecoverable exception.")
    *** Exception: Unrecoverable exception.
    CallStack (from HasCallStack):
      error, called at <interactive>:60:16 in interactive:Ghci9
    > 
    
    > testException (readFile "/etc/DoesntExist" >>= putStrLn)
    *** Exception: /etc/DoesntExist: openFile: does not exist (No such file or directory)
    > 
    
    
    {- Narrow Arithmetic Exceptions -}^
    
    :{  
    testArithExceptions :: IO () -> IO ()
    testArithExceptions thunk = catch thunk handler
      where
        handler :: ArithException -> IO ()
        handler e = case e of
                    Overflow     -> putStrLn "I got an Arithmetic / Overflow Exception"
                    Underflow    -> putStrLn "I got an Arithmetic / Underflow Exception"
                    DivideByZero -> putStrLn "I got an Arithmetic exception / DvideByZero."
                    _            -> putStrLn "FIXME: I don't know how to handle this exception."
    :}                
    
    > testArithExceptions (print $ 1000 `div` 10)
    100
    it :: ()
    >
    
    > testArithExceptions (print $ 1000 `div` 0)
    I got an Arithmetic exception / DvideByZero.
    it :: ()
    > 
    
    > testArithExceptions (throw DivideByZero )
    I got an Arithmetic exception / DvideByZero.
    it :: ()
    > 
    
    > testArithExceptions (throw Overflow)
    I got an Arithmetic / Overflow Exception
    it :: ()
    > 
    
    > testArithExceptions (throw Underflow)
    I got an Arithmetic / Underflow Exception
    it :: ()
    > 
    
    > testArithExceptions (throw LossOfPrecision )
    FIXME: I don't know how to handle this exception.
    it :: ()
    > 
    
    > testArithExceptions (throw RatioZeroDenominator)
    FIXME: I don't know how to handle this exception.
    it :: ()
    > 
    
    > testArithExceptions (readFile "/etc/issue" >>= putStrLn)
    Manjaro Linux \r  (\n) (\l)
    
    
    
    it :: ()
    > 
    
    > testArithExceptions (readFile "/etc/issuesad" >>= putStrLn)
    *** Exception: /etc/issuesad: openFile: does not exist (No such file or directory)
    > 
    
    > testArithExceptions (readFile "/etc/shadow" >>= putStrLn)
    *** Exception: /etc/shadow: openFile: permission denied (Permission denied)
    >
    
    :{
    testIOExceptions :: IO () -> IO ()
    testIOExceptions thunk = catch thunk handler
      where
        -- IOError is an alias to IOException
        handler :: IOError -> IO ()
        handler e = putStrLn $ "I got an IO exception.\nMessage is =" ++ show e
    :}
    
    > testIOExceptions (readFile "/proc/version" >>= putStrLn)
    Linux version 4.4.21-1-MANJARO (builduser@manjaro) (gcc version 6.2.1 20160830 (GCC) ) #1 SMP PREEMPT Thu Sep 15 19:16:23 UTC 2016
    
    it :: ()
    > 
    >
    
    > testIOExceptions (readFile "/proc/91" >>= putStrLn)
    I got an IO exception.
    Message is =/proc/91: openFile: inappropriate type (is a directory)
    it :: ()
    > 
    
    > testIOExceptions (readFile "/proc/" >>= putStrLn)
    I got an IO exception.
    Message is =/proc/: openFile: inappropriate type (is a directory)
    it :: ()
    > 
    
    > testIOExceptions (readFile "/etc/shadow" >>= putStrLn)
    I got an IO exception.
    Message is =/etc/shadow: openFile: permission denied (Permission denied)
    it :: ()
    >
    
    > testIOExceptions (error "Raising an error")
    *** Exception: Raising an error
    CallStack (from HasCallStack):
      error, called at <interactive>:221:19 in interactive:Ghci39
    > 
    
    > testIOExceptions (throw DivideByZero )
    *** Exception: divide by zero
    > 
    
    > testIOExceptions (print $ div 10 0)
    *** Exception: divide by zero
    > 
    
    > testIOExceptions (putStr undefined )
    *** Exception: Prelude.undefined
    CallStack (from HasCallStack):
      error, called at libraries/base/GHC/Err.hs:79:14 in base:GHC.Err
      undefined, called at <interactive>:235:26 in interactive:Ghci40
    > 
    > 
    >
    
    1.3.3.4 Narrowing IO Exceptions

    The IO Exception type is an opaque type or abstract type which the implementation or type constructors are not exposed like ArithException, therefore the only way to find the type of IO exception is by using IO Exception predicates available at System.IO.Error module.

    import Control.Exception
    import System.IO.Error
    
    > 
    > :t catch
    catch :: Exception e => IO a -> (e -> IO a) -> IO a
    > 
    > :t throw
    throw :: Exception e => e -> a
    > 
    > :t throwIO
    throwIO :: Exception e => e -> IO a
    > 
    
    
    {- IOError is an alias for IO Exception -}
    > :info IOError 
    type IOError = IOException      -- Defined in ‘GHC.IO.Exception’
    > 
    
    > :t ioError 
    ioError :: IOError -> IO a
    > 
    
    -- Pasting it in the repl:
    {- The function ioError is used to throw a non-caught Exception inside an Exception handler.
       because this exception handler catches all IO exceptions;
    -}  
    :{
    catch (readFile "/etc/" >>= putStrLn)
          (\e ->  case e of
                  _  | isAlreadyExistsError e -> putStrLn "Error: File alredy exists"
                  _  | isEOFError e           -> putStrLn "Error: End of file"
                  _  | isUserError e          -> putStrLn "Error: User raised an exception"
                  _  | isPermissionError e    -> putStrLn "Error: We don't have permission to read this file"
                  _                           -> putStrLn "Uncaught exception" >> ioError e
           )
    :}
    
    
    
    > :{
    - catch (readFile "/etc/" >>= putStrLn)
    -       (\e ->  case e of
    -               _  | isAlreadyExistsError e -> putStrLn "Error: File alredy exists"
    -               _  | isEOFError e           -> putStrLn "Error: End of file"
    -               _  | isUserError e          -> putStrLn "Error: User raised an exception"
    -               _  | isPermissionError e    -> putStrLn "Error: We don't have permission to read this file"
    -               _                           -> putStrLn "Uncaught exception" >> ioError e
    -        )
    - :}
    Uncaught exception
    *** Exception: /etc/: openFile: inappropriate type (is a directory)
    > 
    
    
    
    :{
    ioExceptionTester :: IO () -> IO ()   
    ioExceptionTester thunk = catch thunk handler 
      where
        handler :: IOError -> IO ()
        handler e = do  
          putStrLn $ "Exception message = " ++ show e 
          case e of
            _ | isAlreadyExistsError e -> putStrLn "Error: Already Exists"
            _ | isDoesNotExistError e  -> putStrLn "Error: Doesn't exists"               
            _ | isEOFError e           -> putStrLn "Error: End of file"
            _ | isIllegalOperation e   -> putStrLn "Error: Illegal operation"
            _ | isPermissionError e    -> putStrLn "Error: Permission error"
            _ | isUserError e          -> putStrLn "Error: User error"              
            _                          -> do putStrLn "Error: I can't handler this type of error."
                                             ioError e -- Raise uncaught exception 
    :}
    
    > ioExceptionTester (readFile "/etc/issue" >>= putStrLn)
    Manjaro Linux \r  (\n) (\l)
    
    
    
    it :: ()
    > 
    
    > ioExceptionTester (readFile "/etc/issuesddfs" >>= putStrLn)
    Exception message = /etc/issuesddfs: openFile: does not exist (No such file or directory)
    Error: Doesn't exists
    it :: ()
    > 
    
    > ioExceptionTester (readFile "/etc/" >>= putStrLn)
    Exception message = /etc/: openFile: inappropriate type (is a directory)
    Error: I can't handler this type of error.
    *** Exception: /etc/: openFile: inappropriate type (is a directory)
    > 
    
    > ioExceptionTester (readFile "/etc/shadow" >>= putStrLn)
    Exception message = /etc/shadow: openFile: permission denied (Permission denied)
    Error: Permission error
    it :: ()
    > 
    
    
    > import qualified System.Directory as D
    D.createDirectory :: FilePath -> IO ()
    >
    
    
    > D.createDirectory "/test"
    *** Exception: /test: createDirectory: permission denied (Permission denied)
    >
    
    
    > ioExceptionTester $ D.createDirectory "/test"
    Exception message = /test: createDirectory: permission denied (Permission denied)
    Error: Permission error
    it :: ()
    >   
    
    > ioExceptionTester $ D.createDirectory "/tmp/test"
    it :: ()
    > ioExceptionTester $ D.createDirectory "/tmp/test"
    Exception message = /tmp/test: createDirectory: already exists (File exists)
    Error: Already Exists
    it :: ()
    > 
    
    > ioExceptionTester $ D.createDirectory "/dev/test"
    Exception message = /dev/test: createDirectory: permission denied (Permission denied)
    Error: Permission error
    it :: ()
    > ioExceptionTester $ D.createDirectory "/dev/sda1"
    Exception message = /dev/sda1: createDirectory: already exists (File exists)
    Error: Already Exists
    it :: ()
    > 
    
    > ioExceptionTester $ throw (userError "I am raising an Error")
    Exception message = user error (I am raising an Error)
    Error: User error
    it :: ()
    > 
    >   
    
    {--============= It doesn't catch non IO exceptions ============== -}
    
    > ioExceptionTester (print $ div 10 0)
    *** Exception: divide by zero
    > 
    
    
    > ioExceptionTester (putStrLn undefined)
    *** Exception: Prelude.undefined
    CallStack (from HasCallStack):
      error, called at libraries/base/GHC/Err.hs:79:14 in base:GHC.Err
      undefined, called at <interactive>:310:29 in interactive:Ghci41
    > 
    
    >  ioExceptionTester (print (read "100" :: Int))
    100
    it :: ()
    
    > ioExceptionTester (print (read "10asd0" :: Int))
    *** Exception: Prelude.read: no parse
    > 
    
    
    > ioExceptionTester $ throw Underflow
    *** Exception: arithmetic underflow
    > 
    >   
    > ioExceptionTester $ throw DivideByZero 
    *** Exception: divide by zero
    
    --- Async Exception - User interrupt 
    > ioExceptionTester (putStrLn "Enter something" >> getLine >>= putStrLn)
    Enter something
    ^CInterrupted.
    >
    

    1.3.4 Catching Exceptions with catchIOError

    The function catchIOError :: IO a -> (IOError -> IO a) -> IO a from module System.IO.Error is similar to the function catch, however it only handles IOError(alias to IOExceptions).

    import Control.Exception
    import System.IO.Error 
    
    :{
    ioExceptionTester thunk = catchIOError thunk handler 
      where
        handler e = do  
          putStrLn $ "Exception message = " ++ show e 
          case e of
            _ | isAlreadyExistsError e -> putStrLn "Error: Already Exists"
            _ | isDoesNotExistError e  -> putStrLn "Error: Doesn't exists"               
            _ | isEOFError e           -> putStrLn "Error: End of file"
            _ | isIllegalOperation e   -> putStrLn "Error: Illegal operation"
            _ | isPermissionError e    -> putStrLn "Error: Permission error"
            _ | isUserError e          -> putStrLn "Error: User error"              
            _                          -> do putStrLn "Error: I can't handler this type of error."
                                             ioError e -- Raise uncaught exception 
    :}
    
    
    > ioExceptionTester (readFile "/etc/issue" >>= putStrLn)
    Manjaro Linux \r  (\n) (\l)
    
    
    
    it :: ()
    > ioExceptionTester (readFile "/etc/issueasd" >>= putStrLn)
    Exception message = /etc/issueasd: openFile: does not exist (No such file or directory)
    Error: Doesn't exists
    it :: ()
    
    > ioExceptionTester (readFile "/etc/" >>= putStrLn)
    Exception message = /etc/: openFile: inappropriate type (is a directory)
    Error: I can't handler this type of error.
    *** Exception: /etc/: openFile: inappropriate type (is a directory)
    > 
    > 
    > ioExceptionTester (readFile "/dev/sda1" >>= putStrLn)
    Exception message = /dev/sda1: hGetContents: invalid argument (invalid byte sequence)
    Error: I can't handler this type of error.
    *** Exception: /dev/sda1: hGetContents: invalid argument (invalid byte sequence)
    > 
    > ioExceptionTester (readFile "/dev/tty0" >>= putStrLn)
    Exception message = /dev/tty0: openFile: permission denied (Permission denied)
    Error: Permission error
    it :: ()
    > 
    > ioExceptionTester (readFile "/dev/shadow" >>= putStrLn)
    Exception message = /dev/shadow: openFile: does not exist (No such file or directory)
    Error: Doesn't exists
    it :: ()
    > 
    > ioExceptionTester (readFile "/etc/shadow" >>= putStrLn)
    Exception message = /etc/shadow: openFile: permission denied (Permission denied)
    Error: Permission error
    it :: ()
    > 
    
    > ioExceptionTester (throw $ userError "I raised an Exception")
    Exception message = user error (I raised an Exception)
    Error: User error
    it :: ()
    > 
    
    
    {- ===== The exceptions below aren't caught by catchIOError ========== -}
    
    > ioExceptionTester (print $ div 10 0)
    *** Exception: divide by zero
    >
    
    > ioExceptionTester (print $ div 10 0)
    *** Exception: divide by zero
    > 
    > let x = undefined
    - 
    x :: a
    > ioExceptionTester (print $ x)
    *** Exception: Prelude.undefined
    CallStack (from HasCallStack):
      error, called at libraries/base/GHC/Err.hs:79:14 in base:GHC.Err
      undefined, called at <interactive>:51:5 in interactive:Ghci4
    > 
    > ioExceptionTester (error "Some error")
    *** Exception: Some error
    CallStack (from HasCallStack):
      error, called at <interactive>:55:20 in interactive:Ghci5
    >
    
    > ioExceptionTester (throw DivideByZero )
    *** Exception: divide by zero
    
    
    > ioExceptionTester (throw Underflow  )
    *** Exception: arithmetic underflow
    >
    

    1.3.5 Catching Exceptions using the function catches

    > import System.IO.Error 
    > import Control.Exception
    >
    > :set -XScopedTypeVariables
    >   
    
    > :t catches
    catches :: IO a -> [Handler a] -> IO a
    > 
    
    > :info Handler 
    data Handler a where
      Handler :: Exception e => (e -> IO a) -> Handler a
            -- Defined in ‘Control.Exception’
    instance Functor Handler -- Defined in ‘Control.Exception’
    > 
    
    
    let arithmeticHandler = putStrLn "Error I got an Arithmetic exception."
    
    :{
    ioExceptionHandler e
      | isAlreadyExistsError e = putStrLn "IO Exception - Already exists error"
      | isUserError e          = putStrLn "IO Exception - User Error"
      | otherwise              = do putStrLn "IO Exception - I don't know how to handle this exception"
                                    throw e 
    :}
    ioExceptionHandler :: IOError -> IO ()
    > 
    
    :{
    testCatches :: IO () -> IO ()
    testCatches thunk = catches thunk handlers
      where handlers = [  Handler $ \ (e :: ArithException) -> arithmeticHandler
                        , Handler $ \ (e :: IOError)        -> ioExceptionHandler e                                         
                        , Handler $ \ (e :: ErrorCall)      -> putStrLn "I got an ErrorCall exception"
                        , Handler $ \ (e :: SomeException)  -> do putStrLn "I don't know how to handle this exception"
                                                                  throw e
                       ]
    
    :}
    
    > 
    > 
    > testCatches (throw DivideByZero)
    Error I got an Arithmetic exception.
    it :: ()
    > testCatches (throw Overflow )
    Error I got an Arithmetic exception.
    it :: ()
    > testCatches (let x = undefined in print x)
    I got an ErrorCall exception
    it :: ()
    > testCatches (error "Some Error")
    I got an ErrorCall exception
    it :: ()
    > testCatches (readFile "/etc/issue" >>= putStrLn)
    Manjaro Linux \r  (\n) (\l)
    
    
    
    it :: ()
    > testCatches (readFile "/etc/issuesf" >>= putStrLn)
    IO Exception - I don't know how to handle this exception
    *** Exception: /etc/issuesf: openFile: does not exist (No such file or directory)
    > 
    > testCatches (throw $ userError "throw my exception")
    IO Exception - User Error
    it :: ()
    > 
    >
    

    Author: nobody

    Created: 2018-05-07 Mon 10:11

    Emacs 25.3.1 (Org mode 8.2.10)

    Validate