{-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE LambdaCase #-} {-# LANGUAGE TypeFamilies #-} {-# LANGUAGE TypeOperators #-} -- | Rescue semantics & helpers -- -- Essentially a type-directed version of 'Control.Monad.Catch'. -- -- This is the opposite of 'Control.Monad.Raise', which embeds en error. -- 'Rescue' takes a potential error out of the surrounding context -- and either handles or exposes it. module Control.Monad.Rescue ( rescue , handle -- * Guaranteed runs , reattempt , onRaise , lastly -- * Reexports , module Control.Monad.Raise , module Control.Monad.Rescue.Class , module Control.Monad.Rescue.Constraint ) where import Data.Result.Types import Data.WorldPeace import Control.Monad.Raise import Control.Monad.Rescue.Class import Control.Monad.Rescue.Constraint import Numeric.Natural -- $setup -- -- >>> :set -XDataKinds -- >>> :set -XFlexibleContexts -- >>> :set -XTypeApplications -- -- >>> import Control.Monad.Trans.Rescue -- >>> import Data.Proxy -- >>> import Data.WorldPeace as OpenUnion -- -- >>> data FooErr = FooErr deriving Show -- >>> data BarErr = BarErr deriving Show -- >>> data QuuxErr = QuuxErr deriving Show -- | Handle all exceptions -- -- >>> type MyErrs = '[FooErr, BarErr] -- >>> myErrs = Proxy @MyErrs -- -- >>> :{ -- goesBoom :: Int -> Rescue MyErrs String -- goesBoom x = -- if x > 50 -- then return (show x) -- else raise FooErr -- :} -- -- >>> handler = catchesOpenUnion (\foo -> "Foo: " <> show foo, \bar -> "Bar:" <> show bar) -- >>> rescue (goesBoom 42) (pure . handler) :: Rescue MyErrs String -- RescueT (Identity (Right "Foo: FooErr")) rescue :: MonadRescueFrom n m => n a -> (ErrorCase n -> m a) -> m a rescue action handler = either handler pure =<< attempt action handle :: ( MonadRaise m , MonadRescueFrom n m , Handles err n m ) => n a -> (err -> m a) -> m a handle action handler = either runHandler pure =<< attempt action where runHandler = openUnionHandle raise handler onRaise :: ( MonadRescue m , RaisesOnly m errs ) => (OpenUnion errs -> m ()) -> m a -> m (Result errs a) onRaise errHandler action = attempt action >>= \case Left err -> do errHandler err return $ Err err Right val -> return $ Ok val -- | 'retry' without asynchoronous exception cleanup. -- Useful when not dealing with external resources that may -- be dangerous to close suddenly. reattempt :: MonadRescue m => Natural -> m a -> m a reattempt 0 action = action reattempt times action = attempt action >>= \case Left _ -> reattempt (times - 1) action Right val -> return val -- | Run an additional step, and throw away the result. -- Return the result of the action passed. lastly :: ( Errors m `Contains` Errors m , MonadRaise m , MonadRescueFrom m m ) => m a -> m b -> m a lastly action finalizer = do errOrOk <- attempt action _ <- finalizer ensure errOrOk