{-# LANGUAGE BlockArguments #-} module Control.Effect.Error ( -- * Effects Throw(..) , Catch(..) , Error -- * Actions , throw , catch , try , catchJust , tryJust , note , fromEither -- * Main Interpreters , runThrow , runError , errorToIO -- * Other interpreters , errorToErrorIO , throwToThrow , catchToError , errorToError -- * Simple variants , errorToIOSimple , errorToErrorIOSimple , throwToThrowSimple , catchToErrorSimple , errorToErrorSimple -- * Threading constraints , ErrorThreads -- * MonadCatch , C.MonadCatch -- * Carriers , 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 -- For coercion purposes 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 -- For errorToIO 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 #-} -- | Run a 'Throw' effect purely. -- -- Unlike 'runError', this does not provide the ability to catch exceptions. -- However, it also doesn't impose any primitive effects, meaning 'runThrow' doesn't -- restrict what interpreters are run before it. -- -- @'Derivs' ('ThrowC' e m) = 'Throw' e ': 'Derivs' m@ -- -- @'Control.Effect.Primitive.Prims' ('ThrowC' e m) = 'Control.Effect.Primitive.Prims' m@ runThrow :: forall e m a p . ( Carrier m , Threaders '[ErrorThreads] m p ) => ThrowC e m a -> m (Either e a) runThrow = coerce {-# INLINE runThrow #-} -- | Runs connected 'Throw' and 'Catch' effects -- i.e. 'Error' -- purely. -- -- @'Derivs' ('ErrorC' e m) = 'Catch' e ': 'Throw' e ': 'Derivs' m@ -- -- @'Control.Effect.Primitive.Prims' ('ErrorC' e m) = 'Control.Effect.Optional.Optional' ((->) e) ': 'Control.Effect.Primitive.Prims' m@ runError :: forall e m a p . ( Carrier m , Threaders '[ErrorThreads] m p ) => ErrorC e m a -> m (Either e a) runError = coerce {-# INLINE runError #-} -- | Transforms a @'Throw' smallExc@ effect into a @'Throw' bigExc@ effect, -- by providing a function to convert exceptions of the smaller exception type -- @smallExc@ to the larger exception type @bigExc@. -- -- This has a higher-rank type, as it makes use of 'InterpretReifiedC'. -- __This makes 'throwToThrow' very difficult to use partially applied.__ -- __In particular, it can't be composed using @'.'@.__ -- -- If performance is secondary, consider using the slower -- 'throwToThrowSimple', which doesn't have a higher-rank type. 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 #-} -- | Transforms a @'Catch' smallExc@ effect into an @'Error' bigExc@ effect, by -- providing a function that identifies when exceptions of the larger exception type -- @bigExc@ correspond to exceptions of the smaller exception type @smallExc@. -- -- This has a higher-rank type, as it makes use of 'InterpretReifiedC'. -- __This makes 'catchToError' very difficult to use partially applied.__ -- __In particular, it can't be composed using @'.'@.__ -- -- If performance is secondary, consider using the slower -- 'catchToErrorSimple', which doesn't have a higher-rank type. 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 -- | Transforms connected 'Throw' and 'Catch' effects -- i.e. 'Error' -- -- into another 'Error' effect by providing functions to convert -- between the two types of exceptions. -- -- This has a higher-rank type, as it makes use of 'InterpretErrorC'. -- __This makes 'errorToError' very difficult to use partially applied.__ -- __In particular, it can't be composed using @'.'@.__ -- -- If performance is secondary, consider using the slower -- 'errorToErrorSimple', which doesn't have a higher-rank type. 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 -- can't use 'catchToError' directly, -- since it can't tell if it should use smallExc or bigExc for its 'Throw'. -- We fix that by using 'intro1'. $ interpret \case Catch m h -> m `catch` \e -> case from e of Just e' -> h e' Nothing -> intro1 $ throw e $ runComposition $ m0 {-# INLINE errorToError #-} -- 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 -- 'throwToThrowSimple', 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 $ 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 -- | 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 $ runComposition $ m {-# INLINE errorToIO #-} -- | Transforms a @'Throw' smallExc@ effect into a @'Throw' bigExc@ effect, -- by providing a function to convert exceptions of the smaller exception type -- @smallExc@ to the larger exception type @bigExc@. -- -- This is a less performant version of 'throwToThrow' that doesn't have -- a higher-rank type, making it much easier to use partially applied. 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 #-} -- | Transforms a @'Catch' smallExc@ effect into an @'Error' bigExc@ effect, by -- providing a function that identifies when exceptions of the larger exception type -- @bigExc@ correspond to exceptions of the smaller exception type @smallExc@. -- -- This is a less performant version of 'catchToError' that doesn't have -- a higher-rank type, making it much easier to use partially applied. 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) ] -- | Transforms connected 'Throw' and 'Catch' effects -- i.e. 'Error' -- -- into another 'Error' effect by providing functions to convert -- between the two types of exceptions. -- -- This is a less performant version of 'errorToError' that doesn't have -- a higher-rank type, making it much easier to use partially applied. 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 ] -- | 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 $ runComposition $ 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 .# runComposition {-# INLINE errorToIOSimple #-}