on master help sign in
Add haddock to Effectful.Error
commitd3541483a539f73a8fe3355f3c482387925cbe16 authorAndrzej Rybczak <andrzej@rybczak.net> dateFri 17 Dec 2021, 18:19 (5 years ago) parent7600133
1 file changed+66−21
effectful-core/src/Effectful/Error.hs+66−21
effectful-core/src/Effectful/Error.hs+66−21
@@ -1,28 +1,49 @@
1−{- | Support for checked exceptions.
2−
3−The 'Error' effect provided by this module is a suitable replacement for the
4−'Control.Monad.Except.ExceptT' monad transformer found in the @mtl@ library. It
5−is __not__ intended to be a general mechanism for catching errors, that's what
6−functions from the "Control.Monad.Catch" module are for.
7−
8−For example, if you want to catch an unchecked exception like
9−'Control.Exception.ErrorCall' you could use 'Control.Monad.Catch.catch':
10−
11−>>> import qualified Control.Monad.Catch as E
12−>>> :{
13− let boom :: Eff es String
14− boom = error "BOOM!"
15− in
16− runEff $ boom `E.catch` \(e :: ErrorCall) -> pure "caught some error"
17−:}
18−"caught some error"
19−
20−-}
1+-- | Support for checked exceptions.
2+--
3+-- /Note:/ the 'Error' effect provided by this module is a suitable replacement
4+-- for the "Control.Monad.Trans.Except" monad transformer found in the
5+-- @transformers@ library. It is __not__ a general mechanism for handling
6+-- exceptions, that's what functions from the "Control.Monad.Catch" module are
7+-- for.
8+--
9+-- For example, if you want to catch an unchecked exception like
10+-- 'Control.Exception.ErrorCall' you could use 'Control.Monad.Catch.catch':
11+--
12+-- >>> import qualified Control.Monad.Catch as E
13+-- >>> let boom = error "BOOM!"
14+-- >>> runEff $ boom `E.catch` \(_::ErrorCall) -> pure "caught an error"
15+-- "caught an error"
16+--
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:
19+--
20+-- >>> runEff . runError @ErrorCall $ boom `catchError` \_ (_::ErrorCall) -> pure "caught an error"
21+-- *** Exception: BOOM!
22+-- ...
23+--
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:
27+--
28+-- >>> let display = liftIO . putStrLn
29+-- >>> :{
30+-- runEff . fmap (first snd) . runError @String $ do
31+-- E.bracket_ (display "Beginning.")
32+-- (display "Cleaning up.")
33+-- (display "Computing." >> throwError "oops" >> display "More.")
34+-- :}
35+-- Beginning.
36+-- Computing.
37+-- Cleaning up.
38+-- Left "oops"
39+--
2140module Effectful.Error
22− ( Error
41+ ( -- * The effect
42+ Error
2343 , runError
2444 , throwError
2545 , catchError
46+ , handleError
2647 , tryError
2748
2849 -- * Re-exports
@@ -40,9 +61,11 @@ import GHC.Stack
4061import Effectful.Dispatch.Static
4162import Effectful.Monad
4263
64+-- | Provide the capability of handling errors of type @e@.
4365newtype Error e :: Effect where
4466 Error :: ErrorId -> Error e m r
4567
68+-- | Handle errors of type @e@.
4669runError
4770 :: forall e es a. Typeable e
4871 => Eff (Error e : es) a
@@ -60,18 +83,23 @@ runError m = unsafeEff $ \es0 -> mask $ \release -> do
6083 Left ex -> tryHandler ex eid (\cs e -> Left (cs, e))
6184 $ throwIO ex
6285
86+-- | Throw an error of type @e@.
6387throwError
6488 :: forall e es a. (HasCallStack, Typeable e, Error e :> es)
6589 => e
90+ -- ^ The error.
6691 -> Eff es a
6792throwError e = unsafeEff $ \es -> do
6893 IdE (Error eid) <- getEnv @(Error e) es
6994 throwIO $ ErrorEx eid callStack e
7095
96+-- | Handle an error of type @e@.
7197catchError
7298 :: forall e es a. (Typeable e, Error e :> es)
7399 => Eff es a
100+ -- ^ The inner computation.
74101 -> (CallStack -> e -> Eff es a)
102+ -- ^ A handler for errors in the inner computation.
75103 -> Eff es a
76104catchError m handler = unsafeEff $ \es -> do
77105 IdE (Error eid) <- getEnv @(Error e) es
@@ -80,9 +108,23 @@ catchError m handler = unsafeEff $ \es -> do
80108 checkSizeEnv size es
81109 unEff (handler cs e) es
82110
111+-- | The same as @'flip' 'catchError'@, which is useful in situations where the
112+-- code for the handler is shorter.
113+handleError
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.
117+ -> Eff es a
118+ -- ^ The inner computation.
119+ -> Eff es a
120+handleError = flip catchError
121+
122+-- | Similar to 'catchError', but returns an 'Either' result which is a 'Right'
123+-- if no error was thrown and a 'Left' otherwise.
83124tryError
84125 :: forall e es a. (Typeable e, Error e :> es)
85126 => Eff es a
127+ -- ^ The inner computation.
86128 -> Eff es (Either (CallStack, e) a)
87129tryError m = (Right <$> m) `catchError` \es e -> pure $ Left (es, e)
88130
@@ -125,3 +167,6 @@ catchErrorIO eid m handler = do
125167 if eid == etag
126168 then handler e cs
127169 else throwIO err
170+
171+-- $setup
172+-- >>> import Data.Bifunctor