rescue-0.2.0: library/Control/Monad/Rescue.hs
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE TypeFamilies #-}
-- | 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
, reattempt
, onRaise
, lastly
-- * Reexports
, module Control.Monad.Raise
, module Control.Monad.Rescue.Class
) where
import Data.Result.Types
import Data.WorldPeace
import Control.Monad.Raise
import Control.Monad.Rescue.Class
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)
-- RescueT (Identity (Right "Foo: FooErr"))
rescue
:: ( MonadRescue m
, RaisesOnly m errs
)
=> m a
-> (OpenUnion errs -> m a)
-> m a
rescue action handler = either handler pure =<< attempt action
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 :: (Contains (Errors m) (Errors m), MonadRescue m) => m a -> m b -> m a
lastly action finalizer = do
errOrOk <- attempt action
_ <- finalizer
ensure errOrOk