{-# LANGUAGE ScopedTypeVariables, CPP, ConstraintKinds #-} {-# OPTIONS_GHC -fno-warn-duplicate-exports #-} -- | Extra functions for "Control.Exception". -- These functions provide retrying, showing in the presence of exceptions, -- and functions to catch\/ignore exceptions, including monomorphic (no 'Exception' context) versions. -- -- If you want to use a safer set of exceptions see the -- <https://hackage.haskell.org/package/safe-exceptions safe-exceptions> package. module Control.Exception.Extra( module Control.Exception, Partial, retry, retryBool, errorWithoutStackTrace, showException, stringException, errorIO, -- * Exception catching/ignoring ignore, catch_, handle_, try_, catchJust_, handleJust_, tryJust_, catchBool, handleBool, tryBool ) where #if __GLASGOW_HASKELL__ >= 800 import GHC.Stack #endif import Control.Exception import Control.Monad import Data.List.Extra import Data.Functor import Partial import Prelude -- | Fully evaluate an input String. If the String contains embedded exceptions it will produce @\<Exception\>@. -- -- > stringException "test" == return "test" -- > stringException ("test" ++ undefined) == return "test<Exception>" -- > stringException ("test" ++ undefined ++ "hello") == return "test<Exception>" -- > stringException ['t','e','s','t',undefined] == return "test<Exception>" stringException :: String -> IO String stringException x = do r <- try_ $ evaluate $ list [] (\x xs -> x `seq` x:xs) x case r of Left e -> return "<Exception>" Right [] -> return [] Right (x:xs) -> (x:) <$> stringException xs -- | Show a value, but if the result contains exceptions, produce -- @\<Exception\>@. Defined as @'stringException' . show@. -- Particularly useful for printing exceptions to users, remembering that exceptions -- can themselves contain undefined values. showException :: Show e => e -> IO String showException = stringException . show #if __GLASGOW_HASKELL__ < 800 -- | A variant of 'error' that does not produce a stack trace. errorWithoutStackTrace :: String -> a errorWithoutStackTrace = error #endif -- | Ignore any exceptions thrown by the action. -- -- > ignore (print 1) == print 1 -- > ignore (fail "die") == return () ignore :: IO () -> IO () ignore = void . try_ -- | An 'IO' action that when evaluated calls 'error', in the 'IO' monad. -- Note that while 'fail' in 'IO' raises an 'IOException', this function raises an 'ErrorCall' exception with a call stack. -- -- > catch (errorIO "Hello") (\(ErrorCall x) -> return x) == return "Hello" -- > seq (errorIO "foo") (print 1) == print 1 errorIO :: Partial => String -> IO a errorIO x = withFrozenCallStack $ evaluate $ error x #if __GLASGOW_HASKELL__ < 800 withFrozenCallStack :: a -> a withFrozenCallStack = id #endif -- | Retry an operation at most /n/ times (/n/ must be positive). -- If the operation fails the /n/th time it will throw that final exception. -- -- > retry 1 (print "x") == print "x" -- > retry 3 (fail "die") == fail "die" retry :: Int -> IO a -> IO a retry i x | i <= 0 = error "Control.Exception.Extra.retry: count must be 1 or more" retry i x = retryBool (\(e :: SomeException) -> True) i x -- | Retry an operation at most /n/ times (/n/ must be positive), while the exception value and type match a predicate. -- If the operation fails the /n/th time it will throw that final exception. retryBool :: Exception e => (e -> Bool) -> Int -> IO a -> IO a retryBool p i x | i <= 0 = error "Control.Exception.Extra.retryBool: count must be 1 or more" retryBool p 1 x = x retryBool p i x = do res <- tryBool p x case res of Left _ -> retryBool p (i-1) x Right v -> return v -- | A version of 'catch' without the 'Exception' context, restricted to 'SomeException', -- so catches all exceptions. catch_ :: IO a -> (SomeException -> IO a) -> IO a catch_ = Control.Exception.catch -- | Like 'catch_' but for 'catchJust' catchJust_ :: (SomeException -> Maybe b) -> IO a -> (b -> IO a) -> IO a catchJust_ = catchJust -- | Like 'catch_' but for 'handle' handle_ :: (SomeException -> IO a) -> IO a -> IO a handle_ = handle -- | Like 'catch_' but for 'handleJust' handleJust_ :: (SomeException -> Maybe b) -> (b -> IO a) -> IO a -> IO a handleJust_ = handleJust -- | Like 'catch_' but for 'try' try_ :: IO a -> IO (Either SomeException a) try_ = try -- | Like 'catch_' but for 'tryJust' tryJust_ :: (SomeException -> Maybe b) -> IO a -> IO (Either b a) tryJust_ = tryJust -- | Catch an exception if the predicate passes, then call the handler with the original exception. -- As an example: -- -- @ -- readFileExists x == catchBool isDoesNotExistError (readFile \"myfile\") (const $ return \"\") -- @ catchBool :: Exception e => (e -> Bool) -> IO a -> (e -> IO a) -> IO a catchBool f a b = catchJust (bool f) a b -- | Like 'catchBool' but for 'handle'. handleBool :: Exception e => (e -> Bool) -> (e -> IO a) -> IO a -> IO a handleBool f a b = handleJust (bool f) a b -- | Like 'catchBool' but for 'try'. tryBool :: Exception e => (e -> Bool) -> IO a -> IO (Either e a) tryBool f a = tryJust (bool f) a bool :: (e -> Bool) -> (e -> Maybe e) bool f x = if f x then Just x else Nothing