{-# LANGUAGE BlockArguments #-}
module Control.Effect.Error
(
Throw(..)
, Catch(..)
, Error
, throw
, catch
, try
, catchJust
, tryJust
, note
, fromEither
, runThrow
, runError
, errorToIO
, errorToErrorIO
, throwToThrow
, catchToError
, errorToError
, errorToIOSimple
, errorToErrorIOSimple
, throwToThrowSimple
, catchToErrorSimple
, errorToErrorSimple
, ErrorThreads
, C.MonadCatch
, ThrowC
, ErrorC
, ErrorToIOC
, ErrorToIOC'
, ReifiesErrorHandler
, InterpretErrorC
, InterpretErrorC'
, ErrorToIOSimpleC
, InterpretErrorSimpleC
) where
import Data.Function
import Data.Coerce
import Control.Effect
import Control.Effect.ErrorIO
import Control.Effect.Type.Throw
import Control.Effect.Type.Catch
import Control.Effect.Internal.Error
import qualified Control.Exception as X
import qualified Control.Monad.Catch as C
import Control.Effect.Internal.Utils
import Control.Monad.Trans.Except
import Control.Effect.Carrier.Internal.Interpret
import Control.Effect.Carrier.Internal.Intro
import Control.Effect.Carrier.Internal.Compose
import Control.Monad.Trans.Identity
import Data.Unique
import GHC.Exts (Any)
import Unsafe.Coerce
throw :: Eff (Throw e) m => e -> m a
throw = send . Throw
{-# INLINE throw #-}
catch :: Eff (Catch e) m => m a -> (e -> m a) -> m a
catch m h = send (Catch m h)
{-# INLINE catch #-}
try :: Eff (Catch e) m => m a -> m (Either e a)
try m = fmap Right m `catch` (return . Left)
{-# INLINE try #-}
catchJust :: forall smallExc bigExc m a
. Eff (Error bigExc) m
=> (bigExc -> Maybe smallExc)
-> m a
-> (smallExc -> m a)
-> m a
catchJust f m h = m `catch` \e -> maybe (throw e) h (f e)
{-# INLINE catchJust #-}
tryJust :: forall smallExc bigExc m a
. Eff (Error bigExc) m
=> (bigExc -> Maybe smallExc)
-> m a
-> m (Either smallExc a)
tryJust f m = fmap Right m &(catchJust f)$ (return . Left)
{-# INLINE tryJust #-}
note :: Eff (Throw e) m => e -> Maybe a -> m a
note _ (Just a) = return a
note e Nothing = throw e
{-# INLINE note #-}
fromEither :: Eff (Throw e) m => Either e a -> m a
fromEither = either throw pure
{-# INLINE fromEither #-}
runThrow :: forall e m a p
. ( Carrier m
, Threaders '[ErrorThreads] m p
)
=> ThrowC e m a
-> m (Either e a)
runThrow = coerce
{-# INLINE runThrow #-}
runError :: forall e m a p
. ( Carrier m
, Threaders '[ErrorThreads] m p
)
=> ErrorC e m a
-> m (Either e a)
runError = coerce
{-# INLINE runError #-}
throwToThrow :: forall smallExc bigExc m a
. Eff (Throw bigExc) m
=> (smallExc -> bigExc)
-> InterpretReifiedC (Throw smallExc) m a
-> m a
throwToThrow to = interpret $ \case
Throw e -> throw (to e)
{-# INLINE throwToThrow #-}
catchToError :: forall smallExc bigExc m a
. Eff (Error bigExc) m
=> (bigExc -> Maybe smallExc)
-> InterpretReifiedC (Catch smallExc) m a
-> m a
catchToError from = interpret $ \case
Catch m h -> m &(catchJust from)$ h
{-# INLINE catchToError #-}
type ReifiesErrorHandler s s' e m =
( ReifiesHandler s (Catch e) (InterpretC (ViaReifiedH s') (Throw e) m)
, ReifiesHandler s' (Throw e) m
)
type InterpretErrorC' s s' smallExc = CompositionC
'[ InterpretC (ViaReifiedH s) (Catch smallExc)
, InterpretC (ViaReifiedH s') (Throw smallExc)
]
type InterpretErrorC e m a =
forall s s'
. ReifiesErrorHandler s s' e m
=> InterpretErrorC' s s' e m a
errorToError :: forall smallExc bigExc m a
. Eff (Error bigExc) m
=> (smallExc -> bigExc)
-> (bigExc -> Maybe smallExc)
-> InterpretErrorC smallExc m a
-> m a
errorToError to from m0 =
throwToThrow to
$ interpret \case
Catch m h -> m `catch` \e -> case from e of
Just e' -> h e'
Nothing -> intro1 $ throw e
$ runComposition
$ m0
{-# INLINE errorToError #-}
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
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
$ runComposition
$ main
fmap Right main' `catchIO` \exc@(OpaqueExc uniq' e) ->
if uniq == uniq' then
return $ Left (unsafeCoerce e)
else
throwIO exc
type ErrorToIOC' s s' e = CompositionC
'[ IntroC '[Catch e, Throw e] '[ErrorIO]
, InterpretErrorC' s s' e
, ErrorIOToIOC
]
type ErrorToIOC e m a =
forall s s'
. ReifiesErrorHandler s s' e (ErrorIOToIOC m)
=> ErrorToIOC' s s' e m a
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
$ runComposition
$ m
{-# INLINE errorToIO #-}
throwToThrowSimple :: forall smallExc bigExc m a p
. ( Eff (Throw bigExc) m
, Threaders '[ReaderThreads] m p
)
=> (smallExc -> bigExc)
-> InterpretSimpleC (Throw smallExc) m a
-> m a
throwToThrowSimple to = interpretSimple $ \case
Throw e -> throw (to e)
{-# INLINE throwToThrowSimple #-}
catchToErrorSimple :: forall smallExc bigExc m a p
. ( Eff (Error bigExc) m
, Threaders '[ReaderThreads] m p
)
=> (bigExc -> Maybe smallExc)
-> InterpretSimpleC (Catch smallExc) m a
-> m a
catchToErrorSimple from = interpretSimple $ \case
Catch m h -> catchJust from m h
{-# INLINE catchToErrorSimple #-}
type InterpretErrorSimpleC e = CompositionC
'[ InterpretSimpleC (Catch e)
, InterpretSimpleC (Throw e)
]
errorToErrorSimple :: forall smallExc bigExc m a p
. ( Eff (Error bigExc) m
, Threaders '[ReaderThreads] m p
)
=> (smallExc -> bigExc)
-> (bigExc -> Maybe smallExc)
-> InterpretErrorSimpleC smallExc m a
-> m a
errorToErrorSimple to from =
throwToThrowSimple to
. interpretSimple \case
Catch m h -> intro1 $ catchJust from (lift m) (lift #. h)
.# runComposition
{-# INLINE errorToErrorSimple #-}
type ErrorToIOSimpleC e = CompositionC
'[ IntroC '[Catch e, Throw e] '[ErrorIO]
, InterpretErrorSimpleC e
, ErrorIOToIOC
]
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
$ runComposition
$ main
fmap Right main' `catchIO` \exc@(OpaqueExc uniq' e) ->
if uniq == uniq' then
return $ Left (unsafeCoerce e)
else
throwIO exc
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
.# runComposition
{-# INLINE errorToIOSimple #-}