Error Handling and Exceptions
23.1 Pure Errors: Maybe, Either, and Friends
Use Maybe when there's one obvious way to fail, Either e when callers need to know why, and ExceptT (Module 26) to combine Either with other effects.
data AppError = NotFound String | InvalidInput String | Unauthorized
deriving (Show, Eq)
findUser :: Int -> Either AppError String
findUser 1 = Right "ana"
findUser n = Left (NotFound ("user " ++ show n))23.2 Exceptions in IO
throwIO / throw Raise (prefer throwIO in IO; throw from pure code is imprecise) try, catch, handle, catches
bracket, bracket_, finally, onException Force a pure value inside IO so its exceptions are caught there
error, undefined, errorWithoutStackTrace Programmer errors (crash with a message) throwTo, killThread
Asynchronous exceptions (timeouts, killThread) can arrive at any point. Use bracket/mask for safety, and don't catch SomeException carelessly (it also catches async exceptions). Libraries like safe-exceptions/unliftio provide safer defaults.
HasCallStack constraints add stack traces to error: head' :: HasCallStack => [a] -> a.
import Control.Exception
data BankError = InsufficientFunds Double | AccountLocked
deriving Show
instance Exception BankError -- custom exception type
withdraw :: Double -> Double -> IO Double
withdraw balance amount
| amount > balance = throwIO (InsufficientFunds (amount - balance))
| otherwise = pure (balance - amount)
main :: IO ()
main = do
r <- try (withdraw 100 150) :: IO (Either BankError Double)
case r of
Left (InsufficientFunds short) -> putStrLn ("Short by " ++ show short)
Left e -> putStrLn ("Other error: " ++ show e)
Right b -> putStrLn ("New balance: " ++ show b)
-- catch with a handler
readFile "missing.txt" `catch` \(e :: IOException) -> do -- ScopedTypeVariables (GHC2021)
putStrLn ("IO error: " ++ show e)
pure ""
-- handle multiple exception types
result <- (evaluate (1 `div` 0 :: Int) >> pure "ok") `catches`
[ Handler (\(e :: ArithException) -> pure ("arith: " ++ show e))
, Handler (\(e :: SomeException) -> pure ("other: " ++ displayException e))
]
putStrLn result
-- cleanup
(putStrLn "working" >> throwIO AccountLocked) `catch` (\(e :: BankError) -> print e)
`finally` putStrLn "always
…