@@ -1,8 +1,6 @@
11-- | Support for handling errors of a particular type, i.e. checked exceptions.
22--
3−-- /Note:/ the 'Error' effect is a suitable replacement for the
4−-- 'Control.Monad.Trans.Except.ExceptT' monad transformer from the
5−-- @transformers@ library. It is __not__ a general mechanism for handling
3+-- The 'Error' effect is __not__ a general mechanism for handling regular
64-- exceptions, that's what functions from the @exceptions@ library are for (see
75-- "Control.Monad.Catch" for more information).
86--
@@ -37,6 +35,38 @@
3735-- Cleaning up.
3836-- Left "oops"
3937--
38+-- /Note:/ unlike the 'Control.Monad.Trans.Except.ExceptT' monad transformer
39+-- from the @transformers@ library, the order in which you handle the 'Error'
40+-- effect with regard to other stateful effects does not matter. Consider the
41+-- following:
42+--
43+-- >>> import qualified Control.Monad.State.Strict as T
44+-- >>> import qualified Control.Monad.Except as T
45+--
46+-- >>> let m1 = (T.modify (++ " there!") >> T.throwError "oops") `T.catchError` \_ -> pure ()
47+--
48+-- >>> (`T.runStateT` "Hi") . T.runExceptT $ m1
49+-- (Right (),"Hi there!")
50+--
51+-- >>> T.runExceptT . (`T.runStateT` "Hi") $ m1
52+-- Right ((),"Hi")
53+--
54+-- Here, whether state modifications within the @catchError@ block are lost or
55+-- not depends on the shape of the monad transformer stack, which is surprising
56+-- and can be a source of subtle bugs. On the other hand:
57+--
58+-- >>> import Effectful.State.Local
59+--
60+-- >>> let m2 = (modify (++ " there!") >> throwError "oops") `catchError` \_ (_::String) -> pure ()
61+--
62+-- >>> runEff . runState "Hi" . runError @String $ m2
63+-- (Right (),"Hi there!")
64+--
65+-- >>> runEff . runError @String . runState "Hi" $ m2
66+-- Right ((),"Hi there!")
67+--
68+-- Here, no matter the order of effects, state modifications within the
69+-- @catchError@ block always persist, giving predictable behavior.
4070module Effectful.Error
4171 ( Error
4272 , runError