fused-effects-exceptions 0.2.0.0 → 1.0.0.0
raw patch · 8 files changed
+500/−106 lines, 8 filesdep +fused-effects-exceptionsdep +tastydep +tasty-hunitdep −safe-exceptionsdep −unliftio-coredep ~basedep ~fused-effectsPVP ok
version bump matches the API change (PVP)
Dependencies added: fused-effects-exceptions, tasty, tasty-hunit, transformers
Dependencies removed: safe-exceptions, unliftio-core
Dependency ranges changed: base, fused-effects
API changes (from Hackage documentation)
- Control.Effect.Catch: CatchC :: ReaderC (Handler m) m a -> CatchC m a
- Control.Effect.Catch: CatchIO :: m output -> (e -> m output) -> (output -> m k) -> Catch m k
- Control.Effect.Catch: [runCatchC] :: CatchC m a -> ReaderC (Handler m) m a
- Control.Effect.Catch: catch :: (Member Catch sig, Carrier sig m, Exception e) => m a -> (e -> m a) -> m a
- Control.Effect.Catch: catchSync :: (Member Catch sig, Carrier sig m, Exception e, MonadIO m) => m a -> (e -> m a) -> m a
- Control.Effect.Catch: data Catch m k
- Control.Effect.Catch: instance (Control.Effect.Carrier.Carrier sig m, Control.Monad.IO.Class.MonadIO m) => Control.Effect.Carrier.Carrier (Control.Effect.Catch.Catch Control.Effect.Sum.:+: sig) (Control.Effect.Catch.CatchC m)
- Control.Effect.Catch: instance Control.Effect.Carrier.Effect Control.Effect.Catch.Catch
- Control.Effect.Catch: instance Control.Effect.Carrier.HFunctor Control.Effect.Catch.Catch
- Control.Effect.Catch: instance Control.Monad.IO.Class.MonadIO m => Control.Monad.IO.Class.MonadIO (Control.Effect.Catch.CatchC m)
- Control.Effect.Catch: instance Control.Monad.IO.Unlift.MonadUnliftIO m => Control.Monad.IO.Unlift.MonadUnliftIO (Control.Effect.Catch.CatchC m)
- Control.Effect.Catch: instance GHC.Base.Applicative m => GHC.Base.Applicative (Control.Effect.Catch.CatchC m)
- Control.Effect.Catch: instance GHC.Base.Functor m => GHC.Base.Functor (Control.Effect.Catch.Catch m)
- Control.Effect.Catch: instance GHC.Base.Functor m => GHC.Base.Functor (Control.Effect.Catch.CatchC m)
- Control.Effect.Catch: instance GHC.Base.Monad m => GHC.Base.Monad (Control.Effect.Catch.CatchC m)
- Control.Effect.Catch: newtype CatchC m a
- Control.Effect.Catch: runCatch :: MonadUnliftIO m => CatchC m a -> m a
+ Control.Carrier.State.IORef: StateC :: ReaderC (IORef s) m a -> StateC s m a
+ Control.Carrier.State.IORef: [runStateC] :: StateC s m a -> ReaderC (IORef s) m a
+ Control.Carrier.State.IORef: evalState :: forall s m a. MonadIO m => s -> StateC s m a -> m a
+ Control.Carrier.State.IORef: execState :: forall s m a. MonadIO m => s -> StateC s m a -> m s
+ Control.Carrier.State.IORef: instance (Control.Monad.IO.Class.MonadIO m, Control.Algebra.Algebra sig m, Control.Effect.Class.Effect sig) => Control.Algebra.Algebra (Control.Effect.State.Internal.State s Control.Effect.Sum.:+: sig) (Control.Carrier.State.IORef.StateC s m)
+ Control.Carrier.State.IORef: instance (GHC.Base.Alternative m, GHC.Base.Monad m) => GHC.Base.MonadPlus (Control.Carrier.State.IORef.StateC s m)
+ Control.Carrier.State.IORef: instance Control.Monad.Fail.MonadFail m => Control.Monad.Fail.MonadFail (Control.Carrier.State.IORef.StateC s m)
+ Control.Carrier.State.IORef: instance Control.Monad.Fix.MonadFix m => Control.Monad.Fix.MonadFix (Control.Carrier.State.IORef.StateC s m)
+ Control.Carrier.State.IORef: instance Control.Monad.IO.Class.MonadIO m => Control.Monad.IO.Class.MonadIO (Control.Carrier.State.IORef.StateC s m)
+ Control.Carrier.State.IORef: instance GHC.Base.Alternative m => GHC.Base.Alternative (Control.Carrier.State.IORef.StateC s m)
+ Control.Carrier.State.IORef: instance GHC.Base.Applicative m => GHC.Base.Applicative (Control.Carrier.State.IORef.StateC s m)
+ Control.Carrier.State.IORef: instance GHC.Base.Functor m => GHC.Base.Functor (Control.Carrier.State.IORef.StateC s m)
+ Control.Carrier.State.IORef: instance GHC.Base.Monad m => GHC.Base.Monad (Control.Carrier.State.IORef.StateC s m)
+ Control.Carrier.State.IORef: newtype StateC s m a
+ Control.Carrier.State.IORef: runState :: MonadIO m => s -> StateC s m a -> m (s, a)
+ Control.Effect.Exception: AllocationLimitExceeded :: AllocationLimitExceeded
+ Control.Effect.Exception: AssertionFailed :: String -> AssertionFailed
+ Control.Effect.Exception: BlockedIndefinitelyOnMVar :: BlockedIndefinitelyOnMVar
+ Control.Effect.Exception: BlockedIndefinitelyOnSTM :: BlockedIndefinitelyOnSTM
+ Control.Effect.Exception: CompactionFailed :: String -> CompactionFailed
+ Control.Effect.Exception: Deadlock :: Deadlock
+ Control.Effect.Exception: Denormal :: ArithException
+ Control.Effect.Exception: DivideByZero :: ArithException
+ Control.Effect.Exception: ErrorCallWithLocation :: String -> String -> ErrorCall
+ Control.Effect.Exception: Handler :: (e -> m a) -> Handler m a
+ Control.Effect.Exception: HeapOverflow :: AsyncException
+ Control.Effect.Exception: IndexOutOfBounds :: String -> ArrayException
+ Control.Effect.Exception: LossOfPrecision :: ArithException
+ Control.Effect.Exception: MaskedInterruptible :: MaskingState
+ Control.Effect.Exception: MaskedUninterruptible :: MaskingState
+ Control.Effect.Exception: NestedAtomically :: NestedAtomically
+ Control.Effect.Exception: NoMethodError :: String -> NoMethodError
+ Control.Effect.Exception: NonTermination :: NonTermination
+ Control.Effect.Exception: Overflow :: ArithException
+ Control.Effect.Exception: PatternMatchFail :: String -> PatternMatchFail
+ Control.Effect.Exception: RatioZeroDenominator :: ArithException
+ Control.Effect.Exception: RecConError :: String -> RecConError
+ Control.Effect.Exception: RecSelError :: String -> RecSelError
+ Control.Effect.Exception: RecUpdError :: String -> RecUpdError
+ Control.Effect.Exception: StackOverflow :: AsyncException
+ Control.Effect.Exception: ThreadKilled :: AsyncException
+ Control.Effect.Exception: TypeError :: String -> TypeError
+ Control.Effect.Exception: UndefinedElement :: String -> ArrayException
+ Control.Effect.Exception: Underflow :: ArithException
+ Control.Effect.Exception: Unmasked :: MaskingState
+ Control.Effect.Exception: UserInterrupt :: AsyncException
+ Control.Effect.Exception: [LiftWith] :: forall (sig :: Type -> Type) (m :: Type -> Type) k a. () => (forall (ctx :: Type -> Type). Functor ctx => ctx () -> (forall a1. () => ctx (m a1) -> sig (ctx a1)) -> sig (ctx a)) -> (a -> m k) -> Lift sig m k
+ Control.Effect.Exception: [SomeAsyncException] :: forall e. Exception e => e -> SomeAsyncException
+ Control.Effect.Exception: [SomeException] :: forall e. Exception e => e -> SomeException
+ Control.Effect.Exception: allowInterrupt :: Has (Lift IO) sig m => m ()
+ Control.Effect.Exception: assert :: () => Bool -> a -> a
+ Control.Effect.Exception: asyncExceptionFromException :: Exception e => SomeException -> Maybe e
+ Control.Effect.Exception: asyncExceptionToException :: Exception e => e -> SomeException
+ Control.Effect.Exception: bracket :: Has (Lift IO) sig m => m a -> (a -> m b) -> (a -> m c) -> m c
+ Control.Effect.Exception: bracketOnError :: Has (Lift IO) sig m => m a -> (a -> m b) -> (a -> m c) -> m c
+ Control.Effect.Exception: bracket_ :: Has (Lift IO) sig m => m a -> m b -> m c -> m c
+ Control.Effect.Exception: catch :: (Exception e, Has (Lift IO) sig m) => m a -> (e -> m a) -> m a
+ Control.Effect.Exception: catchJust :: (Exception e, Has (Lift IO) sig m) => (e -> Maybe b) -> m a -> (b -> m a) -> m a
+ Control.Effect.Exception: catches :: Has (Lift IO) sig m => m a -> [Handler m a] -> m a
+ Control.Effect.Exception: class (HFunctor sig, Monad m) => Algebra (sig :: Type -> Type -> Type -> Type) (m :: Type -> Type) | m -> sig
+ Control.Effect.Exception: class (Typeable e, Show e) => Exception e
+ Control.Effect.Exception: data AllocationLimitExceeded
+ Control.Effect.Exception: data ArithException
+ Control.Effect.Exception: data ArrayException
+ Control.Effect.Exception: data AsyncException
+ Control.Effect.Exception: data BlockedIndefinitelyOnMVar
+ Control.Effect.Exception: data BlockedIndefinitelyOnSTM
+ Control.Effect.Exception: data Deadlock
+ Control.Effect.Exception: data ErrorCall
+ Control.Effect.Exception: data Handler m a
+ Control.Effect.Exception: data IOException
+ Control.Effect.Exception: data Lift (sig :: Type -> Type) (m :: Type -> Type) k
+ Control.Effect.Exception: data MaskingState
+ Control.Effect.Exception: data NestedAtomically
+ Control.Effect.Exception: data NonTermination
+ Control.Effect.Exception: data SomeAsyncException
+ Control.Effect.Exception: data SomeException
+ Control.Effect.Exception: displayException :: Exception e => e -> String
+ Control.Effect.Exception: evaluate :: Has (Lift IO) sig m => a -> m a
+ Control.Effect.Exception: finally :: Has (Lift IO) sig m => m a -> m b -> m a
+ Control.Effect.Exception: fromException :: Exception e => SomeException -> Maybe e
+ Control.Effect.Exception: getMaskingState :: Has (Lift IO) sig m => m MaskingState
+ Control.Effect.Exception: handle :: (Exception e, Has (Lift IO) sig m) => (e -> m a) -> m a -> m a
+ Control.Effect.Exception: handleJust :: (Exception e, Has (Lift IO) sig m) => (e -> Maybe b) -> (b -> m a) -> m a -> m a
+ Control.Effect.Exception: instance GHC.Base.Functor m => GHC.Base.Functor (Control.Effect.Exception.Handler m)
+ Control.Effect.Exception: interruptible :: Has (Lift IO) sig m => m a -> m a
+ Control.Effect.Exception: ioError :: Has (Lift IO) sig m => IOError -> m a
+ Control.Effect.Exception: liftWith :: Has (Lift n) sig m => (forall (ctx :: Type -> Type). Functor ctx => ctx () -> (forall a1. () => ctx (m a1) -> n (ctx a1)) -> n (ctx a)) -> m a
+ Control.Effect.Exception: mapException :: (Exception e1, Exception e2) => (e1 -> e2) -> a -> a
+ Control.Effect.Exception: mask :: Has (Lift IO) sig m => ((forall a. m a -> m a) -> m b) -> m b
+ Control.Effect.Exception: mask_ :: Has (Lift IO) sig m => m a -> m a
+ Control.Effect.Exception: newtype AssertionFailed
+ Control.Effect.Exception: newtype CompactionFailed
+ Control.Effect.Exception: newtype NoMethodError
+ Control.Effect.Exception: newtype PatternMatchFail
+ Control.Effect.Exception: newtype RecConError
+ Control.Effect.Exception: newtype RecSelError
+ Control.Effect.Exception: newtype RecUpdError
+ Control.Effect.Exception: newtype TypeError
+ Control.Effect.Exception: onException :: Has (Lift IO) sig m => m a -> m b -> m a
+ Control.Effect.Exception: pattern ErrorCall :: () => () => String -> ErrorCall
+ Control.Effect.Exception: run :: () => Identity a -> a
+ Control.Effect.Exception: sendM :: (Has (Lift n) sig m, Functor n) => n a -> m a
+ Control.Effect.Exception: throw :: Exception e => e -> a
+ Control.Effect.Exception: throwIO :: (Exception e, Has (Lift IO) sig m) => e -> m a
+ Control.Effect.Exception: throwTo :: (Exception e, Has (Lift IO) sig m) => ThreadId -> e -> m ()
+ Control.Effect.Exception: toException :: Exception e => e -> SomeException
+ Control.Effect.Exception: try :: (Exception e, Has (Lift IO) sig m) => m a -> m (Either e a)
+ Control.Effect.Exception: tryJust :: (Exception e, Has (Lift IO) sig m) => (e -> Maybe b) -> m a -> m (Either b a)
+ Control.Effect.Exception: type Has (eff :: Type -> Type -> Type -> Type) (sig :: Type -> Type -> Type -> Type) (m :: Type -> Type) = (Members eff sig, Algebra sig m)
+ Control.Effect.Exception: uninterruptibleMask :: Has (Lift IO) sig m => ((forall a. m a -> m a) -> m b) -> m b
+ Control.Effect.Exception: uninterruptibleMask_ :: Has (Lift IO) sig m => m a -> m a
Files
- ChangeLog.md +18/−0
- README.lhs +35/−0
- README.md +32/−2
- fused-effects-exceptions.cabal +49/−13
- src/Control/Carrier/State/IORef.hs +72/−0
- src/Control/Effect/Catch.hs +0/−91
- src/Control/Effect/Exception.hs +254/−0
- test/Main.hs +40/−0
+ ChangeLog.md view
@@ -0,0 +1,18 @@+# 1.0.0.0++* Port to fused-effects 1.0.+* Add `Control.Effect.Exception`, which wraps the entirety of `base`'s `Control.Exception`.+* Add `Control.Carrier.State.IORef`, a state carrier that does not drop writes.+* Remove `Catch` effect in favor of `Control.Effect.Exception`.++# 0.2.0.0++Bump lower bound of `fused-effects` to 0.5.++# 0.1.1.0++Depend on `unliftio-core` for unlifting.++# 0.1.0.0++Initial release.
+ README.lhs view
@@ -0,0 +1,35 @@+# fused-effects-exceptions++[](https://hackage.haskell.org/package/fused-effects-exceptions)+[](LICENSE)+[](https://github.com/fused-effects/fused-effects-exceptions/actions)++<!--+Setup, hidden from the rendered markdown.++```haskell+{-# LANGUAGE ScopedTypeVariables, TypeApplications #-}+module Main (module Main) where++import Control.Carrier.State.Strict+import Control.Effect.Exception++main :: IO ()+main = pure ()+```+-->+++This package provides `Control.Effect.Exception`, a module that wraps the [`Control.Exception`](http://hackage.haskell.org/package/base/docs/Control-Exception.html) API from `base` with the vocabulary provided by the [`fused-effects`](http://hackage.haskell.org/package/fused-effects) library. These functions interact with GHC's support for dynamic exceptions, including functions like `catch` for exception handling and `bracket` for resource management.++Please be aware that injudicious use of these functions may provoke surprising interactions with carriers that thread a monadic state as a parameter, à la the `Control.Carrier.State` types provided by `fused-effects`. For example, a function like `finally`, which does not thread any state from its body to its handler block, may discard state writes in cleanup handlers:++```haskell+discardsState :: IO Char+discardsState = execState 'a' ((throwIO (userError "urk") `finally` put @Char 'z')+ `catch` (\(_ :: IOException) -> pure ()))+```++Though the `put @Char 'z'` statement is evaluated, its effect is ultimately discarded; the result of executing the above is `'a'`. If this behavior is a concern, a `Control.Carrier.State.IORef` carrier is provided, which fixes this issue given access to a `MonadIO` constraint. If it is not a concern (such as if the cleanup block is only run for its effects in `IO`), then the `StateC` carriers from `fused-effects` will suffice. For more information about the issues associated with this approach, consult Alexis King's excellent [Demystifying `MonadBaseControl`](https://lexi-lambda.github.io/blog/2019/09/07/demystifying-monadbasecontrol/).++Prior versions of this package provided a `Catch` effect; this has been excised in favor of the more-general `Control.Effect.Exception`, which provides more functionality without requiring any additional carriers beyond a `Lift IO` effect.
README.md view
@@ -1,5 +1,35 @@ # fused-effects-exceptions -This package provides functionality to handle exceptions thrown in the `IO` monad. It delegates to `catch` from `Control.Exception`. An additional `catchSync` primitive is provided to handle the common case of catching only synchronous exceptions.+[](https://hackage.haskell.org/package/fused-effects-exceptions)+[](LICENSE)+[](https://github.com/fused-effects/fused-effects-exceptions/actions) -This implementation was extracted from one originally written by Josh Vera. It requires a version of `fused-effects` later than 0.3.+<!--+Setup, hidden from the rendered markdown.++```haskell+{-# LANGUAGE ScopedTypeVariables, TypeApplications #-}+module Main (module Main) where++import Control.Carrier.State.Strict+import Control.Effect.Exception++main :: IO ()+main = pure ()+```+-->+++This package provides `Control.Effect.Exception`, a module that wraps the [`Control.Exception`](http://hackage.haskell.org/package/base/docs/Control-Exception.html) API from `base` with the vocabulary provided by the [`fused-effects`](http://hackage.haskell.org/package/fused-effects) library. These functions interact with GHC's support for dynamic exceptions, including functions like `catch` for exception handling and `bracket` for resource management.++Please be aware that injudicious use of these functions may provoke surprising interactions with carriers that thread a monadic state as a parameter, à la the `Control.Carrier.State` types provided by `fused-effects`. For example, a function like `finally`, which does not thread any state from its body to its handler block, may discard state writes in cleanup handlers:++```haskell+discardsState :: IO Char+discardsState = execState 'a' ((throwIO (userError "urk") `finally` put @Char 'z')+ `catch` (\(_ :: IOException) -> pure ()))+```++Though the `put @Char 'z'` statement is evaluated, its effect is ultimately discarded; the result of executing the above is `'a'`. If this behavior is a concern, a `Control.Carrier.State.IORef` carrier is provided, which fixes this issue given access to a `MonadIO` constraint. If it is not a concern (such as if the cleanup block is only run for its effects in `IO`), then the `StateC` carriers from `fused-effects` will suffice. For more information about the issues associated with this approach, consult Alexis King's excellent [Demystifying `MonadBaseControl`](https://lexi-lambda.github.io/blog/2019/09/07/demystifying-monadbasecontrol/).++Prior versions of this package provided a `Catch` effect; this has been excised in favor of the more-general `Control.Effect.Exception`, which provides more functionality without requiring any additional carriers beyond a `Lift IO` effect.
fused-effects-exceptions.cabal view
@@ -1,29 +1,65 @@ cabal-version: 2.4 name: fused-effects-exceptions-version: 0.2.0.0+version: 1.0.0.0 synopsis: Handle exceptions thrown in IO with fused-effects.-description: Provides an effect that enables catching exceptions thrown from impure computations such as 'IO'.+description: Provides Resource and Catch effects capable of reacting to and catching GHC's dynamic exceptions. homepage: https://github.com/fused-effects/fused-effects-exceptions#readme license: BSD-3-Clause license-file: LICENSE-author: Josh Vera+author: Josh Vera, Patrick Thomson, and Rob Rix maintainer: patrickt@github.com-copyright: 2019 Josh Vera and Patrick Thomson+copyright: 2019 Josh Vera, Patrick Thomson, and Rob Rix category: Control build-type: Simple-extra-source-files: README.md+extra-source-files:+ README.md+ ChangeLog.md -tested-with: GHC == 8.6.4+tested-with:+ GHC == 8.2.2+ GHC == 8.4.4+ GHC == 8.6.5+ GHC == 8.8.1 +common common+ default-language: Haskell2010+ library- hs-source-dirs: src- default-language: Haskell2010- exposed-modules: Control.Effect.Catch- build-depends: base >= 4.7 && < 5- , fused-effects ^>= 0.5- , safe-exceptions >= 0.1 && <1- , unliftio-core >= 0.1.2 && <1+ import: common+ hs-source-dirs: src+ exposed-modules:+ Control.Carrier.State.IORef+ Control.Effect.Exception+ build-depends:+ base >= 4.7 && < 5+ , fused-effects >= 1+ , transformers >= 0.4 && < 0.6++test-suite test+ import: common+ type: exitcode-stdio-1.0+ hs-source-dirs: test+ main-is: Main.hs+ build-depends:+ base+ , fused-effects-exceptions+ , fused-effects+ , tasty ^>= 1.2+ , tasty-hunit ^>= 0.10+ , transformers++test-suite docs+ import: common+ type: exitcode-stdio-1.0+ main-is: README.lhs+ ghc-options: -pgmL markdown-unlit+ build-depends:+ base+ , fused-effects+ , fused-effects-exceptions+ build-tool-depends:+ markdown-unlit:markdown-unlit ^>= 0.5 source-repository head type: git
+ src/Control/Carrier/State/IORef.hs view
@@ -0,0 +1,72 @@+{-# LANGUAGE FlexibleInstances, GeneralizedNewtypeDeriving, MultiParamTypeClasses, RankNTypes, TypeOperators, UndecidableInstances #-}++{- | A carrier for the 'State' effect. It uses an 'IORef' internally to handle its state, and thus is safe to use with "Control.Carrier.Resource". Underlying 'IORef' operations are performed with 'readIORef' and 'writeIORef'.++Note that the parameter order in 'runState', 'evalState', and 'execState' is reversed compared the equivalent functions provided by @transformers@. This is an intentional decision made to enable the composition of effect handlers with '.' without invoking 'flip'.+-}+module Control.Carrier.State.IORef+( -- * Strict state carrier+ runState+, evalState+, execState+, StateC(..)+-- * State effect+, module Control.Effect.State+) where++import Control.Applicative (Alternative (..))+import Control.Algebra+import Control.Carrier.Reader+import Control.Effect.State+import Control.Monad (MonadPlus (..))+import qualified Control.Monad.Fail as Fail+import Control.Monad.Fix+import Control.Monad.IO.Class+import Control.Monad.Trans.Class+import Data.IORef++-- | Run a 'State' effect starting from the passed value.+--+-- prop> run (runState a (pure b)) === (a, b)+--+-- @since 1.0.0.0+runState :: MonadIO m => s -> StateC s m a -> m (s, a)+runState s x = do+ ref <- liftIO $ newIORef s+ result <- runReader ref . runStateC $ x+ final <- liftIO . readIORef $ ref+ pure (final, result)+{-# INLINE[3] runState #-}++-- | Run a 'State' effect, yielding the result value and discarding the final state.+--+-- prop> run (evalState a (pure b)) === b+--+-- @since 1.0.0.0+evalState :: forall s m a . MonadIO m => s -> StateC s m a -> m a+evalState s x = do+ ref <- liftIO $ newIORef s+ runReader ref . runStateC $ x+{-# INLINE[3] evalState #-}++-- | Run a 'State' effect, yielding the final state and discarding the return value.+--+-- prop> run (execState a (pure b)) === a+--+-- @since 1.0.0.0+execState :: forall s m a . MonadIO m => s -> StateC s m a -> m s+execState s = fmap fst . runState s+{-# INLINE[3] execState #-}++-- | @since 1.0.0.0+newtype StateC s m a = StateC { runStateC :: ReaderC (IORef s) m a }+ deriving (Alternative, Applicative, Functor, Monad, Fail.MonadFail, MonadFix, MonadIO, MonadPlus)++instance (MonadIO m, Algebra sig m, Effect sig) => Algebra (State s :+: sig) (StateC s m) where+ alg (L act) = do+ ref <- StateC ask+ case act of+ Put s k -> liftIO (writeIORef ref s) *> k+ Get k -> liftIO (readIORef ref) >>= k+ alg (R other) = StateC (alg (R (handleCoercible other)))+ {-# INLINE alg #-}
− src/Control/Effect/Catch.hs
@@ -1,91 +0,0 @@-{-# LANGUAGE DeriveFunctor, ExistentialQuantification, FlexibleContexts, FlexibleInstances, GeneralizedNewtypeDeriving,- MultiParamTypeClasses, RankNTypes, StandaloneDeriving, TypeOperators, UndecidableInstances #-}---- | An effect that enables catching exceptions thrown from--- impure computations such as 'IO'.------ Use of the 'Control.Effect.Error' effect from @Control.Effect.Error@ may lead to--- simpler code, as well as avoiding the dynamically-typed nature of--- 'Control.Exception'. This is best used when integrating with third-party--- libraries that operate in 'IO'. If you are using 'catch' for resource--- management, consider using 'Control.Effect.Resource' instead.-module Control.Effect.Catch- ( Catch (..)- , catch- , catchSync- , runCatch- , CatchC (..)- ) where--import Control.Effect.Carrier-import Control.Effect.Reader-import Control.Effect.Sum-import qualified Control.Exception as Exc-import Control.Exception.Safe (isSyncException)-import Control.Monad.IO.Class-import Control.Monad.IO.Unlift--data Catch m k- = forall output e . Exc.Exception e => CatchIO (m output) (e -> m output) (output -> m k)--deriving instance Functor m => Functor (Catch m)--instance HFunctor Catch where- hmap f (CatchIO go cleanup k) = CatchIO (f go) (f . cleanup) (f . k)--instance Effect Catch where- handle state handler (CatchIO go cleanup k)- = CatchIO (handler (go <$ state)) (\se -> handler (cleanup se <$ state)) (handler . fmap k)---- | Like 'Control.Effect.Error.catchError', but delegating to--- 'Control.Exception.catch' under the hood, which allows catching--- errors that might occur when lifting 'IO' computations.--- Unhandled errors are rethrown. Use 'Exc.SomeException' if you want--- to catch all errors.-catch :: (Member Catch sig, Carrier sig m, Exc.Exception e)- => m a- -> (e -> m a)- -> m a-catch go cleanup = send (CatchIO go cleanup pure)---- | Like 'catch', but the handler only engages on synchronous exceptions.--- Async exceptions are rethrown.-catchSync :: (Member Catch sig, Carrier sig m, Exc.Exception e, MonadIO m)- => m a- -> (e -> m a)- -> m a-catchSync f g = f `catch` \e ->- if isSyncException e- then g e- -- intentionally rethrowing an async exception synchronously,- -- since we want to preserve async behavior- else liftIO (Exc.throw e)---- | Evaluate a 'Catch' effect.-unliftCatch :: (forall x . m x -> IO x)- -> CatchC m a- -> m a-unliftCatch handler = runReader (Handler handler) . runCatchC---- | Evaluate a 'Catch' effect, using 'MonadUnliftIO' to infer a correct--- unlifting function.-runCatch :: MonadUnliftIO m => CatchC m a -> m a-runCatch c = withRunInIO (\f -> runHandler (Handler f) c)--newtype Handler m = Handler (forall x . m x -> IO x)--runHandler :: Handler m -> CatchC m a -> IO a-runHandler h@(Handler handler) = handler . runReader h . runCatchC--newtype CatchC m a = CatchC { runCatchC :: ReaderC (Handler m) m a }- deriving (Functor, Applicative, Monad, MonadIO)--instance MonadUnliftIO m => MonadUnliftIO (CatchC m) where- askUnliftIO = CatchC . ReaderC $ \(Handler h) ->- withUnliftIO $ \u -> pure (UnliftIO $ \r -> unliftIO u (unliftCatch h r))--instance (Carrier sig m, MonadIO m) => Carrier (Catch :+: sig) (CatchC m) where- eff (L (CatchIO act cleanup k)) = do- handler <- CatchC ask- liftIO (Exc.catch (runHandler handler act) (runHandler handler . cleanup)) >>= k- eff (R other) = CatchC (eff (R (handleCoercible other)))
+ src/Control/Effect/Exception.hs view
@@ -0,0 +1,254 @@+{-# LANGUAGE DeriveFunctor, ExistentialQuantification, RankNTypes, StandaloneDeriving #-}+-- | Operations from "Control.Exception" lifted into effectful contexts using 'Control.Effect.Lift.Lift'.+--+-- @since 1.0.0.0+module Control.Effect.Exception+( -- * Lifted "Control.Exception" operations+ throwIO+, ioError+, throwTo+, catch+, catches+, Handler(..)+, catchJust+, handle+, handleJust+, try+, tryJust+, evaluate+, mask+, mask_+, uninterruptibleMask+, uninterruptibleMask_+, getMaskingState+, interruptible+, allowInterrupt+, bracket+, bracket_+, bracketOnError+, finally+, onException+, module Control.Exception+ -- * Lift effect+, Lift(..)+, sendM+, liftWith+ -- * Re-exports+, Algebra+, Has+, run+) where++import Control.Concurrent (ThreadId)+import Control.Effect.Lift+import Control.Exception hiding+ ( throwIO+ , ioError+ , throwTo+ , catch+ , catches+ , Handler+ , catchJust+ , handle+ , handleJust+ , try+ , tryJust+ , evaluate+ , mask+ , mask_+ , uninterruptibleMask+ , uninterruptibleMask_+ , getMaskingState+ , interruptible+ , allowInterrupt+ , bracket+ , bracket_+ , bracketOnError+ , finally+ , onException+ )+import qualified Control.Exception as Exc+import Prelude hiding (ioError)++-- | See @"Control.Exception".'Exc.throwIO'@.+--+-- @since 1.0.0.0+throwIO :: (Exc.Exception e, Has (Lift IO) sig m) => e -> m a+throwIO = sendM . Exc.throwIO++-- | See @"Control.Exception".'Exc.ioError'@.+--+-- @since 1.0.0.0+ioError :: Has (Lift IO) sig m => IOError -> m a+ioError = sendM . Exc.ioError++-- | See @"Control.Exception".'Exc.throwTo'@.+--+-- @since 1.0.0.0+throwTo :: (Exc.Exception e, Has (Lift IO) sig m) => ThreadId -> e -> m ()+throwTo thread = sendM . Exc.throwTo thread++-- | See @"Control.Exception".'Exc.catch'@.+--+-- @since 1.0.0.0+catch :: (Exc.Exception e, Has (Lift IO) sig m) => m a -> (e -> m a) -> m a+catch m h = liftWith $ \ ctx run -> run (m <$ ctx) `Exc.catch` (run . (<$ ctx) . h)++-- | See @"Control.Exception".'Exc.catches'@.+--+-- @since 1.0.0.0+catches :: Has (Lift IO) sig m => m a -> [Handler m a] -> m a+catches m hs = liftWith $ \ ctx run ->+ Exc.catches (run (m <$ ctx)) (map (\ (Handler h) -> Exc.Handler (run . (<$ ctx) . h)) hs)++-- | See @"Control.Exception".'Exc.Handler'@.+--+-- @since 1.0.0.0+data Handler m a+ = forall e . Exc.Exception e => Handler (e -> m a)++deriving instance Functor m => Functor (Handler m)++-- | See @"Control.Exception".'Exc.catchJust'@.+--+-- @since 1.0.0.0+catchJust+ :: (Exc.Exception e, Has (Lift IO) sig m)+ => (e -> Maybe b)+ -> m a+ -> (b -> m a)+ -> m a+catchJust p m h = liftWith $ \ ctx run -> Exc.catchJust p (run (m <$ ctx)) (run . (<$ ctx) . h)++-- | See @"Control.Exception".'Exc.handle'@.+--+-- @since 1.0.0.0+handle :: (Exc.Exception e, Has (Lift IO) sig m) => (e -> m a) -> m a -> m a+handle = flip catch++-- | See @"Control.Exception".'Exc.handleJust'@.+--+-- @since 1.0.0.0+handleJust+ :: (Exc.Exception e, Has (Lift IO) sig m)+ => (e -> Maybe b)+ -> (b -> m a)+ -> m a+ -> m a+handleJust p = flip (catchJust p)++-- | See @"Control.Exception".'Exc.try'@.+--+-- @since 1.0.0.0+try :: (Exc.Exception e, Has (Lift IO) sig m) => m a -> m (Either e a)+try m = (Right <$> m) `catch` (pure . Left)++-- | See @"Control.Exception".'Exc.tryJust'@.+--+-- @since 1.0.0.0+tryJust :: (Exc.Exception e, Has (Lift IO) sig m) => (e -> Maybe b) -> m a -> m (Either b a)+tryJust p m = catchJust p (Right <$> m) (pure . Left)++-- | See @"Control.Exception".'Exc.evaluate'@.+--+-- @since 1.0.0.0+evaluate :: Has (Lift IO) sig m => a -> m a+evaluate = sendM . Exc.evaluate++-- | See @"Control.Exception".'Exc.mask'@.+--+-- @since 1.0.0.0+mask :: Has (Lift IO) sig m => ((forall a . m a -> m a) -> m b) -> m b+mask with = liftWith $ \ ctx run -> Exc.mask $ \ restore ->+ run (with (\ m -> liftWith $ \ ctx' run' -> restore (run' (m <$ ctx'))) <$ ctx)++-- | See @"Control.Exception".'Exc.mask_'@.+--+-- @since 1.0.0.0+mask_ :: Has (Lift IO) sig m => m a -> m a+mask_ m = mask $ const m++-- | See @"Control.Exception".'Exc.uninterruptibleMask'@.+--+-- @since 1.0.0.0+uninterruptibleMask :: Has (Lift IO) sig m => ((forall a . m a -> m a) -> m b) -> m b+uninterruptibleMask with = liftWith $ \ ctx run -> Exc.uninterruptibleMask $ \ restore ->+ run (with (\ m -> liftWith $ \ ctx' run' -> restore (run' (m <$ ctx'))) <$ ctx)++-- | See @"Control.Exception".'Exc.uninterruptibleMask_'@.+--+-- @since 1.0.0.0+uninterruptibleMask_ :: Has (Lift IO) sig m => m a -> m a+uninterruptibleMask_ m = uninterruptibleMask $ const m++-- | See @"Control.Exception".'Exc.getMaskingState'@.+--+-- @since 1.0.0.0+getMaskingState :: Has (Lift IO) sig m => m Exc.MaskingState+getMaskingState = sendM Exc.getMaskingState++-- | See @"Control.Exception".'Exc.interruptible'@.+--+-- @since 1.0.0.0+interruptible :: Has (Lift IO) sig m => m a -> m a+interruptible m = liftWith $ \ ctx run -> Exc.interruptible (run (m <$ ctx))++-- | See @"Control.Exception".'Exc.allowInterrupt'@.+--+-- @since 1.0.0.0+allowInterrupt :: Has (Lift IO) sig m => m ()+allowInterrupt = sendM Exc.allowInterrupt++-- | See @"Control.Exception".'Exc.bracket'@.+--+-- @since 1.0.0.0+bracket+ :: Has (Lift IO) sig m+ => m a+ -> (a -> m b)+ -> (a -> m c)+ -> m c+bracket acquire release m = mask $ \ restore -> do+ a <- acquire+ r <- restore (m a) `onException` release a+ r <$ release a++-- | See @"Control.Exception".'Exc.bracket_'@.+--+-- @since 1.0.0.0+bracket_+ :: Has (Lift IO) sig m+ => m a+ -> m b+ -> m c+ -> m c+bracket_ before after thing = bracket before (const after) (const thing)++-- | See @"Control.Exception".'Exc.bracketOnError'@.+--+-- @since 1.0.0.0+bracketOnError+ :: Has (Lift IO) sig m+ => m a+ -> (a -> m b)+ -> (a -> m c)+ -> m c+bracketOnError acquire release m = mask $ \ restore -> do+ a <- acquire+ restore (m a) `onException` release a++-- | See @"Control.Exception".'Exc.finally'@.+--+-- @since 1.0.0.0+finally+ :: Has (Lift IO) sig m+ => m a+ -> m b+ -> m a+finally m sequel = mask $ \ restore -> (restore m `onException` sequel) <* sequel++-- | See @"Control.Exception".'Exc.onException'@.+--+-- @since 1.0.0.0+onException :: Has (Lift IO) sig m => m a -> m b -> m a+onException io what = io `catch` \e -> what >> throwIO (e :: Exc.SomeException)
+ test/Main.hs view
@@ -0,0 +1,40 @@+{-# LANGUAGE ScopedTypeVariables, TypeApplications #-}++module Main where++import Prelude hiding (ioError)++import qualified Control.Carrier.State.IORef as IOState+import qualified Control.Carrier.State.Strict as State+import Control.Carrier.Lift (runM)+import Control.Effect.Exception+import Control.Effect.State+import qualified Test.Tasty as Tasty+import qualified Test.Tasty.HUnit as HUnit++problematic :: (Has (Lift IO) sig m, Has (State Char) sig m) => m ()+problematic =+ let throws = modify @Char succ *> throwIO (userError "should explode") `finally` put @Char 'x'+ in throws `catch` (\(_ :: IOException) -> pure ())++testStateDropsWrites :: Tasty.TestTree+testStateDropsWrites = HUnit.testCase "State.Strict drops writes" $ do+ result <- State.execState 'a' $ problematic+ result HUnit.@?= 'a' -- writes are lost++testIOStatePreservesWrites :: Tasty.TestTree+testIOStatePreservesWrites = HUnit.testCase "State.IORef preserves writes" $ do+ result <- IOState.execState 'a' $ problematic+ result HUnit.@?= 'x'++tests :: Tasty.TestTree+tests = Tasty.testGroup "Control.Carrier.Exception"+ [ Tasty.testGroup "finally"+ [ testStateDropsWrites+ , testIOStatePreservesWrites+ ]+ ]++main :: IO ()+main = Tasty.defaultMain tests+