@@ -1,28 +1,49 @@
1−{- | Support for checked exceptions.
1+-- | Support for checked exceptions.
3−The 'Error' effect provided by this module is a suitable replacement for the
3+-- /Note:/ the 'Error' effect provided by this module is a suitable replacement
4−'Control.Monad.Except.ExceptT' monad transformer found in the @mtl@ library. It
4+-- for the "Control.Monad.Trans.Except" monad transformer found in the
5−is __not__ intended to be a general mechanism for catching errors, that's what
5+-- @transformers@ library. It is __not__ a general mechanism for handling
6−functions from the "Control.Monad.Catch" module are for.
6+-- exceptions, that's what functions from the "Control.Monad.Catch" module are
8−For example, if you want to catch an unchecked exception like
9+-- For example, if you want to catch an unchecked exception like
9−'Control.Exception.ErrorCall' you could use 'Control.Monad.Catch.catch':
10+-- 'Control.Exception.ErrorCall' you could use 'Control.Monad.Catch.catch':
11−>>> import qualified Control.Monad.Catch as E
12+-- >>> import qualified Control.Monad.Catch as E
12−>>> :{
13+-- >>> let boom = error "BOOM!"
13− let boom :: Eff es String
14+-- >>> runEff $ boom `E.catch` \(_::ErrorCall) -> pure "caught an error"
14− boom = error "BOOM!"
15+-- "caught an error"
16− runEff $ boom `E.catch` \(e :: ErrorCall) -> pure "caught some error"
17+-- In particular, unchecked exceptions of type @e@ are distinct from errors of
18+-- type @e@ and will __not__ be caught by functions from this module:
20+-- >>> runEff . runError @ErrorCall $ boom `catchError` \_ (_::ErrorCall) -> pure "caught an error"
21+-- *** Exception: BOOM!
24+-- On the other hand, functions for safe finalization and management of
25+-- resources such as 'Control.Monad.Catch.finally' and
26+-- 'Control.Monad.Catch.bracket' work as expected:
28+-- >>> let display = liftIO . putStrLn
30+-- runEff . fmap (first snd) . runError @String $ do
31+-- E.bracket_ (display "Beginning.")
32+-- (display "Cleaning up.")
33+-- (display "Computing." >> throwError "oops" >> display "More.")
18−"caught some error"
35+-- Beginning.
21module Effectful.Error
40module Effectful.Error
23 , runError
43 , runError
24 , throwError
44 , throwError
25 , catchError
45 , catchError
26 , tryError
47 , tryError
28 -- * Re-exports
49 -- * Re-exports
@@ -40,9 +61,11 @@ import GHC.Stack
40import Effectful.Dispatch.Static
61import Effectful.Dispatch.Static
41import Effectful.Monad
62import Effectful.Monad
64+-- | Provide the capability of handling errors of type @e@.
43newtype Error e :: Effect where
65newtype Error e :: Effect where
44 Error :: ErrorId -> Error e m r
66 Error :: ErrorId -> Error e m r
68+-- | Handle errors of type @e@.
47 :: forall e es a. Typeable e
70 :: forall e es a. Typeable e
48 => Eff (Error e : es) a
71 => Eff (Error e : es) a
@@ -60,18 +83,23 @@ runError m = unsafeEff $ \es0 -> mask $ \release -> do
60 Left ex -> tryHandler ex eid (\cs e -> Left (cs, e))
83 Left ex -> tryHandler ex eid (\cs e -> Left (cs, e))
61 $ throwIO ex
84 $ throwIO ex
86+-- | Throw an error of type @e@.
64 :: forall e es a. (HasCallStack, Typeable e, Error e :> es)
88 :: forall e es a. (HasCallStack, Typeable e, Error e :> es)
66 -> Eff es a
91 -> Eff es a
67throwError e = unsafeEff $ \es -> do
92throwError e = unsafeEff $ \es -> do
68 IdE (Error eid) <- getEnv @(Error e) es
93 IdE (Error eid) <- getEnv @(Error e) es
69 throwIO $ ErrorEx eid callStack e
94 throwIO $ ErrorEx eid callStack e
96+-- | Handle an error of type @e@.
72 :: forall e es a. (Typeable e, Error e :> es)
98 :: forall e es a. (Typeable e, Error e :> es)
73 => Eff es a
99 => Eff es a
100+ -- ^ The inner computation.
74 -> (CallStack -> e -> Eff es a)
101 -> (CallStack -> e -> Eff es a)
102+ -- ^ A handler for errors in the inner computation.
75 -> Eff es a
103 -> Eff es a
76catchError m handler = unsafeEff $ \es -> do
104catchError m handler = unsafeEff $ \es -> do
77 IdE (Error eid) <- getEnv @(Error e) es
105 IdE (Error eid) <- getEnv @(Error e) es
@@ -80,9 +108,23 @@ catchError m handler = unsafeEff $ \es -> do
80 checkSizeEnv size es
108 checkSizeEnv size es
81 unEff (handler cs e) es
109 unEff (handler cs e) es
111+-- | The same as @'flip' 'catchError'@, which is useful in situations where the
112+-- code for the handler is shorter.
114+ :: forall e es a. (Typeable e, Error e :> es)
115+ => (CallStack -> e -> Eff es a)
116+ -- ^ A handler for errors in the inner computation.
118+ -- ^ The inner computation.
120+handleError = flip catchError
122+-- | Similar to 'catchError', but returns an 'Either' result which is a 'Right'
123+-- if no error was thrown and a 'Left' otherwise.
84 :: forall e es a. (Typeable e, Error e :> es)
125 :: forall e es a. (Typeable e, Error e :> es)
85 => Eff es a
126 => Eff es a
127+ -- ^ The inner computation.
86 -> Eff es (Either (CallStack, e) a)
128 -> Eff es (Either (CallStack, e) a)
87tryError m = (Right <$> m) `catchError` \es e -> pure $ Left (es, e)
129tryError m = (Right <$> m) `catchError` \es e -> pure $ Left (es, e)
@@ -125,3 +167,6 @@ catchErrorIO eid m handler = do
125 if eid == etag
167 if eid == etag
126 then handler e cs
168 then handler e cs
127 else throwIO err
169 else throwIO err
172+-- >>> import Data.Bifunctor