in-other-words-0.2.0.0: src/Control/Effect/Internal/Error.hs
{-# OPTIONS_HADDOCK not-home #-}
{-# LANGUAGE BlockArguments, DerivingVia #-}
module Control.Effect.Internal.Error where
import Data.Coerce
import Control.Applicative
import Control.Monad
import Control.Effect
import Control.Effect.ErrorIO
import Control.Effect.Type.Throw
import Control.Effect.Type.Catch
import Control.Effect.Optional
import Control.Effect.Carrier
import Control.Monad.Trans.Except
import qualified Control.Exception as X
import qualified Control.Monad.Catch as C
-- For coercion purposes
import Control.Effect.Carrier.Internal.Interpret
import Control.Effect.Carrier.Internal.Intro
import Control.Effect.Carrier.Internal.Compose
import Control.Effect.Internal.ErrorIO
import Control.Effect.Internal.Utils
-- For errorToIO
import Data.Unique
import GHC.Exts (Any)
import Unsafe.Coerce
newtype ThrowC e m a = ThrowC { unThrowC :: ExceptT e m a }
deriving ( Functor, Applicative, Monad
, Alternative, MonadPlus
, MonadFix, MonadFail, MonadIO
, MonadThrow, MonadCatch, MonadMask
, MonadBase b, MonadBaseControl b
)
deriving (MonadTrans, MonadTransControl)
newtype ErrorC e m a = ErrorC { unErrorC :: ExceptT e m a }
deriving ( Functor, Applicative, Monad
, Alternative, MonadPlus
, MonadFix, MonadFail, MonadIO
, MonadThrow, MonadCatch, MonadMask
, MonadBase b, MonadBaseControl b
)
deriving (MonadTrans, MonadTransControl)
instance ( Carrier m
, Threads (ExceptT e) (Prims m)
)
=> Carrier (ThrowC e m) where
type Derivs (ThrowC e m) = Throw e ': Derivs m
type Prims (ThrowC e m) = Prims m
algPrims = coerce (thread @(ExceptT e) (algPrims @m))
{-# INLINEABLE algPrims #-}
reformulate n alg = powerAlg (reformulate (n . lift) alg) $ \case
Throw e -> n (ThrowC (throwE e))
{-# INLINEABLE reformulate #-}
instance ( Carrier m
, Threads (ExceptT e) (Prims m)
)
=> Carrier (ErrorC e m) where
type Derivs (ErrorC e m) = Catch e ': Throw e ': Derivs m
type Prims (ErrorC e m) = Optional ((->) e) ': Prims m
algPrims = powerAlg (coerce (algPrims @(ThrowC e m))) $ \case
Optionally h m -> ErrorC (unErrorC m `catchE` (return . h))
{-# INLINEABLE algPrims #-}
reformulate n alg =
powerAlg (
coerceReform (reformulate @(ThrowC e m)) n (weakenAlg alg)
) $ \case
Catch m h -> join $ (alg . inj) $ Optionally h (fmap pure m)
{-# INLINEABLE reformulate #-}
-- | 'ErrorThreads' accepts the following primitive effects:
--
-- * 'Control.Effect.Regional.Regional' @s@
-- * 'Control.Effect.Optional.Optional' @s@ (when @s@ is a functor)
-- * 'Control.Effect.BaseControl.BaseControl' @b@
-- * 'Control.Effect.Type.Unravel.Unravel' @p@
-- * 'Control.Effect.Type.ListenPrim.ListenPrim' @o@ (when @o@ is a 'Monoid')
-- * 'Control.Effect.Type.WriterPrim.WriterPrim' @o@ (when @o@ is a 'Monoid')
-- * 'Control.Effect.Type.ReaderPrim.ReaderPrim' @i@
-- * 'Control.Effect.Mask.Mask'
-- * 'Control.Effect.Bracket.Bracket'
-- * 'Control.Effect.Fix.Fix'
class ( forall e. Threads (ExceptT e) p
) => ErrorThreads p
instance ( forall e. Threads (ExceptT e) p
) => ErrorThreads p
type ReifiesErrorHandler s s' e m =
( ReifiesHandler s (Catch e) (InterpretC (ViaReifiedH s') (Throw e) m)
, ReifiesHandler s' (Throw e) m
)
newtype InterpretErrorC' s s' e m a = InterpretErrorC' {
unInterpretErrorC' ::
InterpretC (ViaReifiedH s) (Catch e)
( InterpretC (ViaReifiedH s') (Throw e)
( m
)) a
} deriving ( Functor, Applicative, Monad
, Alternative, MonadPlus
, MonadFix, MonadFail, MonadIO
, MonadThrow, MonadCatch, MonadMask
, MonadBase b, MonadBaseControl b
)
deriving (MonadTrans, MonadTransControl)
via CompositionBaseT
'[ InterpretC (ViaReifiedH s) (Catch e)
, InterpretC (ViaReifiedH s') (Throw e)
]
deriving instance (Carrier m, ReifiesErrorHandler s s' e m)
=> Carrier (InterpretErrorC' s s' e m)
type InterpretErrorC e m a =
forall s s'
. ReifiesErrorHandler s s' e m
=> InterpretErrorC' s s' e m a
newtype ErrorToIOC' s s' e m a = ErrorToIOC' {
unErrorToIOC' ::
IntroC '[Catch e, Throw e] '[ErrorIO]
( InterpretErrorC' s s' e
( ErrorIOToIOC
( m
))) a
} deriving ( Functor, Applicative, Monad
, Alternative, MonadPlus
, MonadFix, MonadFail, MonadIO
, MonadThrow, MonadCatch, MonadMask
, MonadBase b, MonadBaseControl b
)
deriving (MonadTrans, MonadTransControl)
via CompositionBaseT
'[ IntroC '[Catch e, Throw e] '[ErrorIO]
, InterpretErrorC' s s' e
, ErrorIOToIOC
]
deriving instance ( Carrier m, C.MonadCatch m
, ReifiesErrorHandler s s' e (ErrorIOToIOC m)
)
=> Carrier (ErrorToIOC' s s' e m)
type ErrorToIOC e m a =
forall s s'
. ReifiesErrorHandler s s' e (ErrorIOToIOC m)
=> ErrorToIOC' s s' e m a
-- KingoftheHomeless: We could skip having to use 'OpaqueExc'
-- by requiring the exception type @e@ to be typeable. Or have it be
-- an instance of 'Exception'.
--
-- I choose not to for two reasons:
-- 1. By making use of OpaqueExc and checking unique references,
-- we guarantee that exceptions belonging to an @'Error' e@ effect
-- interpreted with 'errorToErrorIO' won't get caught by 'catch'es
-- belonging to /another/, identical @'Error' e@ effect interpreted
-- using 'errorToErrorIO'. So by using OpaqueExc, we get coherency.
--
-- 2. In case we eventually implement a system for polymorphic effect
-- interpreters inside of application code, like something like this:
-- @
-- manageError :: HasErrorInterpreter s m
-- => ProvidedErrorInterpreterC s e m a
-- -> m (Either e a)
-- @
-- of which 'errorToErrorIO' should be a valid implementation, then
-- we shouldn't place any constraints upon @e@.
data OpaqueExc = OpaqueExc Unique Any
instance Show OpaqueExc where
showsPrec _ (OpaqueExc uniq _) =
showString "errorToIO/errorToErrorIO: Escaped opaque exception. \
\Unique hash is: " . shows (hashUnique uniq) . showString ". \
\This should only happen if the computation that threw the \
\exception was somehow invoked outside of the argument of \
\'errorToIO'; for example, if you 'async' an exceptional \
\computation inside of the argument provided to 'errorToIO', \
\and then 'await' on it *outside* of the argument provided to \
\'errorToIO'. \
\If that or any similar shenanigans seems unlikely, then \
\please open an issue on the GitHub repository."
instance X.Exception OpaqueExc
-- | Runs connected 'Throw' and 'Catch' effects -- i.e. 'Error' --
-- by transforming them into 'ErrorIO' and @'Embed' IO@
--
-- This has a higher-rank type, as it makes use of 'InterpretErrorC'.
-- __This makes 'errorToErrorIO' very difficult to use partially applied.__
-- __In particular, it can't be composed using @'.'@.__
--
-- If performance is secondary, consider using the slower
-- 'errorToErrorIOSimple', which doesn't have a higher-rank type.
errorToErrorIO :: forall e m a
. Effs '[ErrorIO, Embed IO] m
=> InterpretErrorC e m a
-> m (Either e a)
errorToErrorIO main = do
!uniq <- embed newUnique
let
main' =
interpret \case
Throw e -> throwIO (OpaqueExc uniq (unsafeCoerce e))
$ interpret \case
Catch m h -> m `catchIO` \exc@(OpaqueExc uniq' e) ->
if uniq == uniq' then
h (unsafeCoerce e)
else
throwIO exc
$ unInterpretErrorC'
$ main
fmap Right main' `catchIO` \exc@(OpaqueExc uniq' e) ->
if uniq == uniq' then
return $ Left (unsafeCoerce e)
else
throwIO exc
data ErrorToErrorIOAsExcH
instance (Eff ErrorIO m, Exception e)
=> Handler ErrorToErrorIOAsExcH (Throw e) m where
effHandler (Throw e) = throwIO e
instance (Eff ErrorIO m, Exception e)
=> Handler ErrorToErrorIOAsExcH (Catch e) m where
effHandler (Catch m f) = m `catchIO` f
newtype ErrorToErrorIOAsExcC e m a = ErrorToErrorIOAsExcC {
unErrorToErrorIOAsExcC ::
InterpretC ErrorToErrorIOAsExcH (Catch e)
( InterpretC ErrorToErrorIOAsExcH (Throw e)
( m
)) a
} deriving ( Functor, Applicative, Monad
, Alternative, MonadPlus
, MonadFix, MonadFail, MonadIO
, MonadThrow, MonadCatch, MonadMask
, MonadBase b, MonadBaseControl b
)
deriving (MonadTrans, MonadTransControl)
via CompositionBaseT
'[ InterpretC ErrorToErrorIOAsExcH (Catch e)
, InterpretC ErrorToErrorIOAsExcH (Throw e)
]
deriving instance (Eff ErrorIO m, Exception e)
=> Carrier (ErrorToErrorIOAsExcC e m)
-- | Runs connected 'Throw' and 'Catch' effects -- i.e. 'Error' --
-- by transforming them into 'ErrorIO'.
--
-- Unlike 'errorToErrorIO', values of @e@ are thrown and caught directly as 'IO'
-- exceptions. This means that, for example, 'catchIO' is able to catch
-- exceptions of @e@ that you throw with 'Control.Effect.Error.throw',
-- and 'Control.Effect.Error.catch' is able to catch exceptions of type @e@ that
-- are thrown with 'throwIO', or by 'embed'ded 'IO' actions.
--
-- @'Derivs' ('ErrorToErrorIOAsExcC' e m) = 'Catch' e ': 'Throw' e ': 'Derivs' m@
--
-- @'Control.Effect.Primitive.Prims' ('ErrorToErrorIOAsExcC' e m) = 'Control.Effect.Primitive.Prims' m@
--
-- @since 0.2.0.0
errorToErrorIOAsExc
:: (Exception e, Eff ErrorIO m)
=> ErrorToErrorIOAsExcC e m a
-> m a
errorToErrorIOAsExc =
interpretViaHandler
.# interpretViaHandler
.# unErrorToErrorIOAsExcC
{-# INLINE errorToErrorIOAsExc #-}
-- | Runs connected 'Throw' and 'Catch' effects -- i.e. 'Error' --
-- by making use of 'IO' exceptions.
--
-- @'Derivs' ('ErrorToIOC' e m) = 'Catch' e ': 'Throw' e ': 'Derivs' m@
--
-- @'Control.Effect.Primitive.Prims' ('ErrorToIOC' e m) = 'Control.Effect.Optional.Optional' ((->) 'Control.Exception.SomeException') ': 'Control.Effect.Primitive.Prims' m@
--
-- This has a higher-rank type, as it makes use of 'ErrorToIOC'.
-- __This makes 'errorToIO' very difficult to use partially applied.__
-- __In particular, it can't be composed using @'.'@.__
--
-- If performance is secondary, consider using the slower
-- 'errorToIOSimple', which doesn't have a higher-rank type.
errorToIO :: forall e m a
. ( C.MonadCatch m
, Eff (Embed IO) m
)
=> ErrorToIOC e m a
-> m (Either e a)
errorToIO m =
errorIOToIO
$ errorToErrorIO
$ introUnderMany
$ unErrorToIOC'
$ m
{-# INLINE errorToIO #-}
newtype ErrorToIOAsExcC e m a = ErrorToIOAsExcC {
unErrorToIOAsExcC ::
IntroC '[Catch e, Throw e] '[ErrorIO]
( ErrorToErrorIOAsExcC e
( ErrorIOToIOC
m
)) a
} deriving ( Functor, Applicative, Monad
, Alternative, MonadPlus
, MonadFix, MonadFail, MonadIO
, MonadThrow, MonadCatch, MonadMask
, MonadBase b, MonadBaseControl b
)
deriving (MonadTrans, MonadTransControl)
via CompositionBaseT
'[ IntroC '[Catch e, Throw e] '[ErrorIO]
, ErrorToErrorIOAsExcC e
, ErrorIOToIOC
]
deriving instance (Exception e, C.MonadCatch m, Carrier m)
=> Carrier (ErrorToIOAsExcC e m)
-- | Runs connected 'Throw' and 'Catch' effects -- i.e. 'Error' --
-- by treating values of @e@ as 'IO' exceptions.
--
-- Unlike 'errorToIO', values of @e@ are thrown and caught directly as 'IO'
-- exceptions. This means that, for example, 'catchIO' is able to catch
-- exceptions of @e@ that you throw with 'Control.Effect.Error.throw',
-- and 'Control.Effect.Error.catch' is able to catch
-- exceptions of type @e@ that are thrown with 'throwIO', or by 'embed'ded 'IO'
-- actions.
--
-- @'Derivs' ('ErrorToIOAsExcC' e m) = 'Catch' e ': 'Throw' e ': 'Derivs' m@
--
-- @'Control.Effect.Primitive.Prims' ('ErrorToIOAsExcC' e m) = 'Control.Effect.Optional.Optional' ((->) 'Control.Exception.SomeException') ': 'Control.Effect.Primitive.Prims' m@
--
-- @since 0.2.0.0
errorToIOAsExc
:: ( Exception e
, C.MonadCatch m
, Carrier m
)
=> ErrorToIOAsExcC e m a
-> m a
errorToIOAsExc =
errorIOToIO
.# errorToErrorIOAsExc
.# introUnderMany
.# unErrorToIOAsExcC
{-# INLINE errorToIOAsExc #-}
newtype InterpretErrorSimpleC e m a = InterpretErrorSimpleC {
unInterpretErrorSimpleC ::
InterpretSimpleC (Catch e)
( InterpretSimpleC (Throw e)
( m
)) a
} deriving ( Functor, Applicative, Monad
, Alternative, MonadPlus
, MonadFix, MonadFail, MonadIO
, MonadThrow, MonadCatch, MonadMask
, MonadBase b, MonadBaseControl b
)
deriving MonadTrans
via CompositionBaseT
'[ InterpretSimpleC (Catch e)
, InterpretSimpleC (Throw e)
]
deriving instance (Carrier m, Threaders '[ReaderThreads] m p)
=> Carrier (InterpretErrorSimpleC e m)
newtype ErrorToIOSimpleC e m a = ErrorToIOSimpleC {
unErrorToIOSimpleC ::
IntroC '[Catch e, Throw e] '[ErrorIO]
( InterpretErrorSimpleC e
( ErrorIOToIOC
( m
))) a
} deriving ( Functor, Applicative, Monad
, Alternative, MonadPlus
, MonadFix, MonadFail, MonadIO
, MonadThrow, MonadCatch, MonadMask
, MonadBase b, MonadBaseControl b
)
deriving MonadTrans
via CompositionBaseT
'[ IntroC '[Catch e, Throw e] '[ErrorIO]
, InterpretErrorSimpleC e
, ErrorIOToIOC
]
deriving instance ( Eff (Embed IO) m, C.MonadCatch m
, Threaders '[ReaderThreads] m p
)
=> Carrier (ErrorToIOSimpleC e m)
-- | Runs connected 'Throw' and 'Catch' effects -- i.e. 'Error' --
-- by transforming them into 'ErrorIO' and @'Embed' IO@
--
-- This is a less performant version of 'errorToErrorIO' that doesn't have
-- a higher-rank type, making it much easier to use partially applied.
errorToErrorIOSimple :: forall e m a p
. ( Effs '[ErrorIO, Embed IO] m
, Threaders '[ReaderThreads] m p
)
=> InterpretErrorSimpleC e m a
-> m (Either e a)
errorToErrorIOSimple main = do
!uniq <- embed newUnique
let
main' =
interpretSimple \case
Throw e -> throwIO (OpaqueExc uniq (unsafeCoerce e))
$ interpretSimple \case
Catch m h -> m `catchIO` \exc@(OpaqueExc uniq' e) ->
if uniq == uniq' then
h (unsafeCoerce e)
else
throwIO exc
$ unInterpretErrorSimpleC
$ main
fmap Right main' `catchIO` \exc@(OpaqueExc uniq' e) ->
if uniq == uniq' then
return $ Left (unsafeCoerce e)
else
throwIO exc
-- | Runs connected 'Throw' and 'Catch' effects -- i.e. 'Error' --
-- by making use of 'IO' exceptions.
--
-- @'Derivs' ('ErrorToIOSimpleC' e m) = 'Catch' e ': 'Throw' e ': 'Derivs' m@
--
-- @'Control.Effect.Primitive.Prims' ('ErrorToIOSimpleC' e m) = 'Control.Effect.Optional.Optional' ((->) 'Control.Exception.SomeException') ': 'Control.Effect.Primitive.Prims' m@
--
-- This is a less performant version of 'errorToIO' that doesn't have
-- a higher-rank type, making it much easier to use partially applied.
errorToIOSimple :: forall e m a p
. ( Eff (Embed IO) m
, MonadCatch m
, Threaders '[ReaderThreads] m p
)
=> ErrorToIOSimpleC e m a
-> m (Either e a)
errorToIOSimple =
errorIOToIO
#. errorToErrorIOSimple
.# introUnderMany
.# unErrorToIOSimpleC
{-# INLINE errorToIOSimple #-}