Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add the Effectful.Exception module with appropriate re-exports #252

Merged
merged 1 commit into from
Oct 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions effectful-core/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
* Add a `SeqForkUnlift` strategy to support running unlifting functions outside
of the scope of effects they capture.
* Ensure that a `LocalEnv` is only used in a thread it belongs to.
* Add the `Effectful.Exception` module with appropriate re-exports.
* **Breaking changes**:
- `localSeqLend`, `localLend`, `localSeqBorrow` and `localBorrow` now take a
list of effects instead of a single one.
Expand Down
3 changes: 3 additions & 0 deletions effectful-core/effectful-core.cabal
Original file line number Diff line number Diff line change
Expand Up @@ -64,9 +64,11 @@ library

build-depends: base >= 4.14 && < 5
, containers >= 0.6
, deepseq >= 1.2
, exceptions >= 0.10.4
, monad-control >= 1.0.3
, primitive >= 0.7.3.0
, safe-exceptions >= 0.1.7.2
, strict-mutable-base >= 1.1.0.0
, transformers-base >= 0.4.6
, unliftio-core >= 0.2.0.1
Expand All @@ -83,6 +85,7 @@ library
Effectful.Dispatch.Static.Unsafe
Effectful.Error.Dynamic
Effectful.Error.Static
Effectful.Exception
Effectful.Fail
Effectful.Internal.Effect
Effectful.Internal.Env
Expand Down
2 changes: 1 addition & 1 deletion effectful-core/src/Effectful.hs
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,7 @@ import Effectful.Internal.Monad
--
-- These libraries can trivially be used with the 'Eff' monad since it provides
-- typical instances that these libraries require the underlying monad to have,
-- such as t'Control.Monad.Catch.MonadMask' or 'MonadUnliftIO'.
-- such as t'Effectful.Exception.MonadMask' or 'MonadUnliftIO'.
--
-- In case the 'Eff' monad doesn't provide a specific instance out of the box,
-- it can be supplied via an effect. As an example see how the instance of
Expand Down
7 changes: 2 additions & 5 deletions effectful-core/src/Effectful/Dispatch/Dynamic.hs
Original file line number Diff line number Diff line change
Expand Up @@ -153,12 +153,10 @@ import Effectful.Internal.Utils
-- The following defines an 'EffectHandler' that reads and writes files from the
-- drive:
--
-- >>> import Control.Exception (IOException)
-- >>> import Control.Monad.Catch (catch)
-- >>> import Control.Monad.IO.Class
-- >>> import qualified System.IO as IO
--
-- >>> import Effectful.Error.Static
-- >>> import Effectful.Exception
--
-- >>> newtype FsError = FsError String deriving Show
--
Expand Down Expand Up @@ -249,7 +247,6 @@ import Effectful.Internal.Utils
--
-- If we naively try to interpret it, we will run into trouble:
--
-- >>> import Control.Monad.IO.Class
-- >>> import GHC.Clock (getMonotonicTime)
--
-- >>> :{
Expand Down Expand Up @@ -491,7 +488,6 @@ reinterpretWith runHandlerEs m handler = reinterpret runHandlerEs handler m
-- type instance DispatchOf E = Dynamic
-- :}
--
-- >>> import Control.Monad.IO.Class
-- >>> :{
-- runE :: IOE :> es => Eff (E : es) a -> Eff es a
-- runE = interpret_ $ \case
Expand Down Expand Up @@ -1199,4 +1195,5 @@ instance

-- $setup
-- >>> import Control.Concurrent (ThreadId, forkIOWithUnmask)
-- >>> import Control.Monad.IO.Class
-- >>> import Effectful.Reader.Static
14 changes: 7 additions & 7 deletions effectful-core/src/Effectful/Error/Static.hs
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
-- | Support for handling errors of a particular type, i.e. checked exceptions.
--
-- The 'Error' effect is __not__ a general mechanism for handling regular
-- exceptions, that's what functions from the @exceptions@ library are for (see
-- "Control.Monad.Catch" for more information).
-- exceptions, that's what functions from the "Effectful.Exception" module are
-- for.
--
-- In particular, regular exceptions of type @e@ are distinct from errors of
-- type @e@ and will __not__ be caught by functions from this module:
--
-- >>> import qualified Control.Monad.Catch as E
-- >>> import qualified Effectful.Exception as E
--
-- >>> boom = error "BOOM!"
--
Expand All @@ -16,14 +16,14 @@
-- ...
--
-- If you want to catch regular exceptions, you should use
-- 'Control.Monad.Catch.catch' (or a similar function):
-- 'Effectful.Exception.catch' (or a similar function):
--
-- >>> runEff $ boom `E.catch` \(_::ErrorCall) -> pure "caught"
-- "caught"
--
-- On the other hand, functions for safe finalization and management of
-- resources such as 'Control.Monad.Catch.finally' and
-- 'Control.Monad.Catch.bracket' work as expected:
-- resources such as 'Effectful.Exception.finally' and
-- 'Effectful.Exception.bracket' work as expected:
--
-- >>> msg = liftIO . putStrLn
--
Expand Down Expand Up @@ -74,7 +74,7 @@
--
-- /Hint:/ if you'd like to reproduce the transactional behavior with the
-- t'Effectful.State.Static.Local.State' effect, appropriate usage of
-- 'Control.Monad.Catch.bracketOnError' will do the trick.
-- 'Effectful.Exception.bracketOnError' will do the trick.
module Effectful.Error.Static
( -- * Effect
Error
Expand Down
130 changes: 130 additions & 0 deletions effectful-core/src/Effectful/Exception.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
-- | The 'Eff' monad comes with instances of 'MonadThrow', 'MonadCatch' and
-- 'MonadMask' from the
-- [@exceptions@](https://hackage.haskell.org/package/exceptions) library, so
-- this module simply re-exports the interface of the
-- [@safe-exceptions@](https://hackage.haskell.org/package/safe-exceptions)
-- library.
--
-- Why @safe-exceptions@ and not @exceptions@? Because the former makes it much
-- easier to correctly deal with asynchronous exceptions (for more information
-- see its [README](https://github.com/fpco/safe-exceptions#readme)) and
-- provides more convenience functions.
module Effectful.Exception
( -- * Throwing
C.MonadThrow(..)
, Safe.throwString
, Safe.StringException(..)

-- * Catching (with recovery)
, C.MonadCatch(..)
, Safe.catchIO
, Safe.catchIOError
, Safe.catchAny
, Safe.catchDeep
, Safe.catchAnyDeep
, Safe.catchAsync
, Safe.catchJust

, Safe.handle
, Safe.handleIO
, Safe.handleIOError
, Safe.handleAny
, Safe.handleDeep
, Safe.handleAnyDeep
, Safe.handleAsync
, Safe.handleJust

, Safe.try
, Safe.tryIO
, Safe.tryAny
, Safe.tryDeep
, Safe.tryAnyDeep
, Safe.tryAsync
, Safe.tryJust

, Safe.Handler(..)
, Safe.catches
, Safe.catchesDeep
, Safe.catchesAsync

-- * Cleanup (no recovery)
, C.MonadMask(..)
, C.ExitCase(..)
, Safe.onException
, Safe.bracket
, Safe.bracket_
, Safe.finally
, Safe.withException
, Safe.bracketOnError
, Safe.bracketOnError_
, Safe.bracketWithError

-- * Utilities

-- ** Coercion to sync and async
, Safe.SyncExceptionWrapper(..)
, Safe.toSyncException
, Safe.AsyncExceptionWrapper(..)
, Safe.toAsyncException

-- ** Check exception type
, Safe.isSyncException
, Safe.isAsyncException

-- ** Evaluation
, evaluate
, evaluateDeep

-- * Re-exports from "Control.Exception"

-- ** The 'SomeException' type
, E.SomeException(..)

-- ** The 'Exception' class
, E.Exception(..)

-- ** Concrete exception types
, E.IOException
, E.ArithException(..)
, E.ArrayException(..)
, E.AssertionFailed(..)
, E.NoMethodError(..)
, E.PatternMatchFail(..)
, E.RecConError(..)
, E.RecSelError(..)
, E.RecUpdError(..)
, E.ErrorCall(..)
, E.TypeError(..)

-- ** Asynchronous exceptions
, E.SomeAsyncException(..)
, E.AsyncException(..)
, E.asyncExceptionToException
, E.asyncExceptionFromException
, E.NonTermination(..)
, E.NestedAtomically(..)
, E.BlockedIndefinitelyOnMVar(..)
, E.BlockedIndefinitelyOnSTM(..)
, E.AllocationLimitExceeded(..)
, E.CompactionFailed(..)
, E.Deadlock(..)

-- ** Assertions
, E.assert
) where

import Control.DeepSeq
import Control.Exception qualified as E
import Control.Exception.Safe qualified as Safe
import Control.Monad.Catch qualified as C

import Effectful
import Effectful.Dispatch.Static

-- | Lifted version of 'E.evaluate'.
evaluate :: a -> Eff es a
evaluate = unsafeEff_ . E.evaluate

-- | Deeply evaluate a value using 'evaluate' and 'NFData'.
evaluateDeep :: NFData a => a -> Eff es a
evaluateDeep = unsafeEff_ . E.evaluate . force
3 changes: 1 addition & 2 deletions effectful-core/src/Effectful/State/Static/Local.hs
Original file line number Diff line number Diff line change
Expand Up @@ -137,5 +137,4 @@ modifyM
modifyM f = stateM (\s -> ((), ) <$> f s)

-- $setup
-- >>> import Control.Exception (ErrorCall)
-- >>> import Control.Monad.Catch
-- >>> import Effectful.Exception
3 changes: 1 addition & 2 deletions effectful-core/src/Effectful/State/Static/Shared.hs
Original file line number Diff line number Diff line change
Expand Up @@ -153,5 +153,4 @@ modifyM :: (HasCallStack, State s :> es) => (s -> Eff es s) -> Eff es ()
modifyM f = stateM (\s -> ((), ) <$> f s)

-- $setup
-- >>> import Control.Exception (ErrorCall)
-- >>> import Control.Monad.Catch
-- >>> import Effectful.Exception
2 changes: 1 addition & 1 deletion effectful-core/src/Effectful/Writer/Static/Local.hs
Original file line number Diff line number Diff line change
Expand Up @@ -102,4 +102,4 @@ listens f m = do

-- $setup
-- >>> import Control.Exception (ErrorCall)
-- >>> import Control.Monad.Catch
-- >>> import Effectful.Exception
2 changes: 1 addition & 1 deletion effectful-core/src/Effectful/Writer/Static/Shared.hs
Original file line number Diff line number Diff line change
Expand Up @@ -116,4 +116,4 @@ listens f m = do

-- $setup
-- >>> import Control.Exception (ErrorCall)
-- >>> import Control.Monad.Catch
-- >>> import Effectful.Exception
1 change: 1 addition & 0 deletions effectful/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
* Add a `SeqForkUnlift` strategy to support running unlifting functions outside
of the scope of effects they capture.
* Ensure that a `LocalEnv` is only used in a thread it belongs to.
* Add the `Effectful.Exception` module with appropriate re-exports.
* **Breaking changes**:
- `localSeqLend`, `localLend`, `localSeqBorrow` and `localBorrow` now take a
list of effects instead of a single one.
Expand Down
2 changes: 2 additions & 0 deletions effectful/effectful.cabal
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,7 @@ library
, Effectful.Dispatch.Static
, Effectful.Error.Static
, Effectful.Error.Dynamic
, Effectful.Exception
, Effectful.Fail
, Effectful.Labeled
, Effectful.Labeled.Error
Expand Down Expand Up @@ -144,6 +145,7 @@ test-suite test
, exceptions
, lifted-base
, primitive
, safe-exceptions
, strict-mutable-base
, tasty
, tasty-hunit
Expand Down
19 changes: 11 additions & 8 deletions effectful/tests/StateTests.hs
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
module StateTests (stateTests) where

import Control.Exception.Lifted qualified as LE
import Control.Exception.Safe qualified as Safe
import Control.Monad
import Control.Monad.Catch qualified as E
import Control.Monad.Catch qualified as C
import Data.IORef.Strict
import Test.Tasty
import Test.Tasty.HUnit
Expand Down Expand Up @@ -66,12 +67,14 @@ test_deepStack = runEff $ do

test_exceptions :: Assertion
test_exceptions = runEff $ do
testTry "exceptions" E.try
testCatch "exceptions" E.catch
testTry "lifted-base" LE.try
testCatch "lifted-base" LE.catch
testTry "unliftio" UE.try
testCatch "unliftio" UE.catch
testTry "exceptions" C.try
testCatch "exceptions" C.catch
testTry "safe-exceptions" Safe.try
testCatch "safe-exceptions" Safe.catch
testTry "lifted-base" LE.try
testCatch "lifted-base" LE.catch
testTry "unliftio" UE.try
testCatch "unliftio" UE.catch
where
testTry
:: String
Expand All @@ -96,7 +99,7 @@ test_exceptions = runEff $ do
action :: State Int :> es => Eff es ()
action = do
modify @Int (+1)
_ <- E.throwM U.Ex
_ <- C.throwM U.Ex
modify @Int (+2)

test_localEffects :: Assertion
Expand Down
3 changes: 1 addition & 2 deletions effectful/tests/Utils.hs
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,11 @@ module Utils
, Ex(..)
) where

import Control.Exception (ErrorCall(..))
import Control.Monad.Catch
import GHC.Stack
import Test.Tasty.HUnit qualified as T

import Effectful
import Effectful.Exception

assertBool :: (HasCallStack, IOE :> es) => String -> Bool -> Eff es ()
assertBool msg p = liftIO $ T.assertBool msg p
Expand Down
Loading