in-other-words-0.2.0.0: src/Control/Effect/Exceptional.hs
{-# LANGUAGE DerivingVia #-}
module Control.Effect.Exceptional
( -- * Effects
Exceptional
, SafeError
-- * Actions
, catching
, trying
, throwing
, catchSafe
, trySafe
-- * Interpretations
, runExceptional
, runExceptionalJust
, safeErrorToError
, runSafeError
, safeErrorToIO
, safeErrorToErrorIO
-- * Simple variants of interpretations
, runExceptionalJustSimple
, safeErrorToIOSimple
, safeErrorToErrorIOSimple
-- * Threading constraints
, ErrorThreads
-- * MonadCatch
, MonadCatch
-- * Carriers
, ExceptionallyC
, ExceptionalC
, SafeErrorToErrorC
, SafeErrorC
, SafeErrorToIOC'
, SafeErrorToIOC
, SafeErrorToErrorIOC'
, SafeErrorToErrorIOC
, SafeErrorToIOSimpleC
, SafeErrorToErrorIOSimpleC
) where
import Control.Effect
import Control.Effect.Error
import Control.Effect.ErrorIO
import Control.Effect.Union
import Control.Effect.Carrier
import Control.Effect.Internal.Exceptional
import Control.Effect.Internal.Utils
-- For coercion purposes
import Control.Monad.Trans.Except
import Control.Effect.Internal.Error
import Control.Effect.Carrier.Internal.Interpret
import Control.Effect.Carrier.Internal.Intro
-- | Gain access to @eff@ and @'Catch' exc@ within a region,
-- but only if you're ready to handle any unhandled exception @e :: exc@
-- that may arise from the use of @eff@ within that region.
--
-- For example:
--
-- @
-- -- A part of the program unknowing and uncaring that the use of SomeEffect
-- -- may throw exceptions.
-- uncaringProgram :: 'Eff' SomeEffect m => m String
-- uncaringProgram = do
-- doSomeThing
-- doSomeOtherThing
--
-- caringProgram :: 'Eff' ('Exceptional' SomeEffect SomeEffectExc) m => m String
-- caringProgram =
-- 'catching' \@SomeEffect uncaringProgram (\\(exc :: SomeEffectExc) -> handlerForSomeEffectExc exc)
-- @
--
-- It's possible the @'Catch' exc@ effect @'catching'@ gives you access to
-- would override another, identical @'Catch' exc@ effect that you want to use
-- inside the region. To avoid this, use 'catching' together with 'intro1':
--
-- @'catching' \@SomeEffect ('intro1' uncaringProgram) ...@
--
-- If you do this, then @'catching'@ will only introduce @eff@ to be used
-- in @uncaringProgram@, and not @'Catch' exc@.
catching :: forall eff exc m a
. Eff (Exceptional eff exc) m
=> ExceptionallyC eff exc m a
-> (exc -> m a)
-> m a
catching m h =
send $ Exceptional @eff @exc $
Union (There Here) (Catch (unExceptionallyC m) h)
{-# INLINE catching #-}
-- | Gain access to @'Error' exc@ within a region,
-- but only if you're ready to handle any unhandled exception @e :: exc@
-- that may arise from within that region.
catchSafe :: forall exc m a
. Eff (SafeError exc) m
=> ExceptionallyC (Throw exc) exc m a
-> (exc -> m a)
-> m a
catchSafe = catching
{-# INLINE catchSafe #-}
-- | Gain access to @eff@ and @'Catch' exc@ within a region.
-- If any use of @eff@ within that region 'throw's an unhandled exception
-- @e :: exc@, then this returns @Left e@.
trying :: forall eff exc m a
. Eff (Exceptional eff exc) m
=> ExceptionallyC eff exc m a
-> m (Either exc a)
trying m = fmap Right m `catching` (return . Left)
{-# INLINE trying #-}
-- | Gain access to @'Error' exc@ within a region. If any unhandled exception
-- @e :: exc@ is 'throw'n within that region, then this returns @Left e@.
trySafe :: forall exc m a
. Eff (SafeError exc) m
=> ExceptionallyC (Throw exc) exc m a
-> m (Either exc a)
trySafe = trying
{-# INLINE trySafe #-}
-- | Gain access to @eff@ and @'Catch' exc@ within a region,
-- rethrowing any exception @e :: exc@ that may occur from the use of
-- @eff@ within that region.
throwing :: forall eff exc m a
. Effs [Exceptional eff exc, Throw exc] m
=> ExceptionallyC eff exc m a
-> m a
throwing m = m `catching` throw
{-# INLINE throwing #-}
-- | Run an @'Exceptional' eff exc@ effect if both @eff@ and @'Catch' exc@
-- are part of the effect stack.
--
-- In order for this to be safe, you must ensure that the @'Catch' exc@
-- catches all exceptions that arise from the use of @eff@ and that
-- only uses of @eff@ throws those exceptions.
-- Otherwise, the use of 'catching' is liable to catch exceptions
-- not arising from uses of @eff@, or fail to catch
-- exceptions that do arise from uses of @eff@.
runExceptional :: forall eff exc m a
. ( Member eff (Derivs m)
, Eff (Catch exc) m
)
=> ExceptionalC eff exc m a
-> m a
runExceptional = interpretViaHandler
{-# INLINE runExceptional #-}
-- | Run an @'Exceptional' eff exc@ effect if @eff@ is part of the
-- effect stack, provided a function that identifies the kind of exceptions
-- that may arise from the use of @eff@.
--
-- In order for this to be safe, you must ensure that the function
-- identifies all exceptions that arise from the use of @eff@ and that
-- only uses of @eff@ throws those exceptions.
-- Otherwise, the use of 'catching' is liable to catch
-- other exceptions not arising from uses of @eff@, or fail to catch
-- exceptions that do arise from uses of @eff@.
--
-- The type of this interpreter is higher-rank, as it makes use of
-- 'InterpretReifiedC'. __This makes 'runExceptionalJust' difficult to__
-- __use partially applied; for example, you can't compose it using @'.'@.__
-- You may prefer the simpler, but less performant, 'runExceptionalJustSimple'.
runExceptionalJust :: forall eff smallExc bigExc m a
. ( Member eff (Derivs m)
, Eff (Error bigExc) m
)
=> (bigExc -> Maybe smallExc)
-> InterpretReifiedC (Exceptional eff smallExc) m a
-> m a
runExceptionalJust from = interpret $ \(Exceptional e) -> case e of
Union Here eff -> algDerivs (Union membership eff)
Union (There pr) eff -> case extract (Union pr eff) of
Catch m h -> catchJust from m h
{-# INLINE runExceptionalJust #-}
-- | Run an @'Exceptional' eff exc@ effect if @eff@ is part of the
-- effect stack, provided a function that identifies the kind of exceptions
-- that may arise from the use of @eff@.
--
-- In order for this to be safe, you must ensure that the function
-- identifies all exceptions that arise from the use of @eff@ and that
-- only uses of @eff@ throws those exceptions.
-- Otherwise, the use of 'catching' is liable to catch
-- exceptions not arising from uses of @eff@, or fail to catch
-- exceptions that do arise from uses of @eff@.
--
-- This is a less performant version of 'runExceptionalJust', but doesn't have
-- a higher-rank type. This makes 'runExceptionalJustSimple' much easier to use
-- partially applied.
runExceptionalJustSimple :: forall eff smallExc bigExc m a p
. ( Member eff (Derivs m)
, Eff (Error bigExc) m
, Threaders '[ReaderThreads] m p
)
=> (bigExc -> Maybe smallExc)
-> InterpretSimpleC (Exceptional eff smallExc) m a
-> m a
runExceptionalJustSimple from = interpretSimple $ \(Exceptional e) -> case e of
Union Here eff -> algDerivs (Union membership eff)
Union (There pr) eff -> case extract (Union pr eff) of
Catch m h -> catchJust from m h
{-# INLINE runExceptionalJustSimple #-}
-- | Run a @'SafeError' exc@ effect by transforming it into an @'Error' exc@
-- effect.
safeErrorToError :: forall exc m a
. Eff (Error exc) m
=> SafeErrorToErrorC exc m a
-> m a
safeErrorToError = runExceptional
{-# INLINE safeErrorToError #-}
-- | Run a @'SafeError' e@ effect purely.
--
-- @'Derivs' ('SafeErrorC' e m) = 'SafeError' e ': 'Prims' m@
--
-- @'Prims' ('SafeErrorC' e m) = 'Control.Effect.Optional.Optional' ((->) e) ': 'Prims' m@
runSafeError :: forall e m a p
. ( Carrier m
, Threaders '[ErrorThreads] m p
)
=> SafeErrorC e m a
-> m a
runSafeError =
(>>= either (\_ -> bombPure) return)
.# runError
.# safeErrorToError
.# introUnder
.# unSafeErrorC
{-# INLINE runSafeError #-}
bombPure :: a
bombPure = errorWithoutStackTrace
"runSafeError: Escaped exception! Unless you've imported some internal \
\modules and did something REALLY stupid, this is a bug. Make an issue about \
\it on the GitHub repository for in-other-words."
bombIO :: String -> a
bombIO str = errorWithoutStackTrace $
str ++ ": Escaped exception! This is likely because an `async`ed exceptional \
\computation escaped a `catching` through an `Async`. See \
\Control.Effect.Exceptional.Exceptional. If that sounds unlikely, and you \
\didn't import any internal modules and do something really stupid, \
\then this could be a bug. If so, make an issue about \
\it on the GitHub repository for in-other-words."
-- | Runs a @'SafeError' e@ effect by making use of 'IO' exceptions.
--
-- @'Derivs' ('SafeErrorToIOC' e m) = 'SafeError' e ': 'Derivs' m@
--
-- @'Prims' ('SafeErrorToIOC' e m) = 'Control.Effect.Optional.Optional' ((->) 'Control.Exception.SomeException') ': 'Prims' m@
--
-- This has a higher-rank type, as it makes use of 'SafeErrorToIOC'.
-- __This makes 'safeErrorToIO' very difficult to use partially applied.__
-- __In particular, it can't be composed using @'.'@.__
--
-- If performance is secondary, consider using the slower
-- 'safeErrorToIOSimple', which doesn't have a higher-rank type.
safeErrorToIO :: forall e m a
. ( Eff (Embed IO) m
, MonadCatch m
)
=> SafeErrorToIOC e m a
-> m a
safeErrorToIO m =
(>>= either (\_ -> bombIO "safeErrorToIO") return)
$ errorToIO
$ safeErrorToError
$ introUnder
$ unSafeErrorToIOC'
$ m
{-# INLINE safeErrorToIO #-}
-- | Runs a @'SafeError' e@ effect by transforming it into 'ErrorIO'
-- and @'Embed' IO@.
--
-- This has a higher-rank type, as it makes use of 'SafeErrorToErrorIOC'.
-- __This makes 'safeErrorToErrorIO' very difficult to use partially applied.__
-- __In particular, it can't be composed using @'.'@.__
--
-- If performance is secondary, consider using the slower
-- 'safeErrorToErrorIOSimple', which doesn't have a higher-rank type.
safeErrorToErrorIO :: forall e m a
. Effs '[Embed IO, ErrorIO] m
=> SafeErrorToErrorIOC e m a
-> m a
safeErrorToErrorIO m =
(>>= either (\_ -> bombIO "safeErrorToErrorIO") return)
$ errorToErrorIO
$ safeErrorToError
$ introUnder
$ unSafeErrorToErrorIOC'
$ m
{-# INLINE safeErrorToErrorIO #-}
-- | Runs a @'SafeError' e@ effect by making use of 'IO' exceptions.
--
-- @'Derivs' ('SafeErrorToIOSimpleC' e m) = 'SafeError' e ': 'Derivs' m@
--
-- @'Prims' ('SafeErrorToIOSimpleC' e m) = 'Control.Effect.Optional.Optional' ((->) 'Control.Exception.SomeException') ': 'Prims' m@
--
-- This is a less performant version of 'safeErrorToIO' that doesn't have
-- a higher-rank type, making it much easier to use partially applied.
safeErrorToIOSimple :: forall e m a p
. ( Eff (Embed IO) m
, MonadCatch m
, Threaders '[ReaderThreads] m p
)
=> SafeErrorToIOSimpleC e m a
-> m a
safeErrorToIOSimple =
(>>= either (\_ -> bombIO "safeErrorToIOSimple") return)
. errorToIOSimple
.# safeErrorToError
.# introUnder
.# unSafeErrorToIOSimpleC
{-# INLINE safeErrorToIOSimple #-}
-- | Runs a @'SafeError' e@ effect by transforming it into 'ErrorIO'
-- and @'Embed' IO@.
--
-- This is a less performant version of 'safeErrorToErrorIO' that doesn't have
-- a higher-rank type, making it much easier to use partially applied.
safeErrorToErrorIOSimple :: forall e m a p
. ( Effs '[ErrorIO, Embed IO] m
, Threaders '[ReaderThreads] m p
)
=> SafeErrorToErrorIOSimpleC e m a
-> m a
safeErrorToErrorIOSimple =
(>>= either (\_ -> bombIO "safeErrorToErrorIOSimple") return)
. errorToErrorIOSimple
.# safeErrorToError
.# introUnder
.# unSafeErrorToErrorIOSimpleC
{-# INLINE safeErrorToErrorIOSimple #-}