on master help sign in
Ditinguish between different Error handlers for the same type
commit7a1d6f91a266f6ffa7df17f63d071b59d8c64b5e authorAndrzej Rybczak <andrzej@rybczak.net> dateThu 24 Jun 2021, 16:33 (5 years ago) parentb580fd8
2 files changed+72−26
src/Effectful/Error.hs+40−19 tests/Main.hs+32−7
src/Effectful/Error.hs+40−19
@@ -12,6 +12,7 @@ module Effectful.Error
1212
1313import Control.Exception
1414import Data.Typeable
15+import Data.Unique
1516import GHC.Stack
1617
1718import Effectful.Internal.Effect
@@ -20,48 +21,68 @@ import Effectful.Internal.Monad
2021import Effectful.Internal.Utils
2122
2223data Error e :: Effect where
23− Error :: Error e m r
24+ Error :: Unique -> Error e m r
2425
26+-- | TODO: write about possibility of an error escaping the scope of 'runError'
27+-- when misused with 'AsyncE'.
2528runError
2629 :: forall e es a. Typeable e
2730 => Eff (Error e : es) a
2831 -> Eff es (Either ([String], e) a)
2932runError (Eff m) = unsafeEff $ \es0 -> mask $ \release -> do
33+ -- A unique tag is picked so that different runError handlers for the same
34+ -- type don't catch each other's exceptions.
35+ tag <- newUnique
3036 size0 <- sizeEnv es0
31− es <- unsafeConsEnv (IdE (Error @e)) noRelinker es0
32− try (release $ m es) >>= \case
33− Right a -> Right a <$ unsafeTailEnv size0 es
34− Left (WrapErr cs e) -> Left (cs, e) <$ unsafeTailEnv size0 es
37+ es <- unsafeConsEnv (IdE (Error @e tag)) noRelinker es0
38+ r <- tryErrorIO tag (release $ m es) `onException` unsafeTailEnv size0 es
39+ _ <- unsafeTailEnv size0 es
40+ pure r
3541
3642throwError
37− :: (HasCallStack, Typeable e, Error e :> es)
43+ :: forall e es a. (HasCallStack, Typeable e, Error e :> es)
3844 => e
3945 -> Eff es a
40−throwError e = unsafeEff_ $ do
41− throwIO $ WrapErr (ppCallStack <$> getCallStack callStack) e
46+throwError e = readerEffectM @(Error e) $ \(IdE (Error tag)) -> unsafeEff_ $ do
47+ throwIO $ WrapErr tag (ppCallStack <$> getCallStack callStack) e
4248
4349catchError
44− :: (Typeable e, Error e :> es)
50+ :: forall e es a. (Typeable e, Error e :> es)
4551 => Eff es a
4652 -> ([String] -> e -> Eff es a)
4753 -> Eff es a
48−catchError (Eff m) handler = unsafeEff $ \es -> do
49− size <- sizeEnv es
50− m es `catch` \(WrapErr e cs) -> do
51− checkSizeEnv size es
52− unEff (handler e cs) es
54+catchError (Eff m) handler = do
55+ readerEffectM @(Error e) $ \(IdE (Error tag)) -> unsafeEff $ \es -> do
56+ size <- sizeEnv es
57+ catchErrorIO tag (m es) $ \cs e -> do
58+ checkSizeEnv size es
59+ unEff (handler cs e) es
5360
5461tryError
55− :: (Typeable e, Error e :> es)
62+ :: forall e es a. (Typeable e, Error e :> es)
5663 => Eff es a
5764 -> Eff es (Either ([String], e) a)
58−tryError m = (Right <$> m) `catchError` \es e -> pure (Left (es, e))
65+tryError m = (Right <$> m) `catchError` \es e -> pure $ Left (es, e)
5966
6067----------------------------------------
61−-- WrapE
68+-- Helpers
6269
63−data WrapErr e = WrapErr [String] e
70+data WrapErr e = WrapErr Unique [String] e
6471
6572instance Typeable e => Show (WrapErr e) where
66− show (WrapErr _ e) = "WrapErr " ++ show (typeOf e)
73+ showsPrec p (WrapErr _ cs e)
74+ = showsPrec p "Effectful.Error.WrapErr ("
75+ . showsPrec p (typeOf e)
76+ . showsPrec p ") "
77+ . showsPrec p cs
6778instance Typeable e => Exception (WrapErr e)
79+
80+catchErrorIO :: Typeable e => Unique -> IO a -> ([String] -> e -> IO a) -> IO a
81+catchErrorIO tag m handler = do
82+ m `catch` \err@(WrapErr etag e cs) -> do
83+ if tag == etag
84+ then handler e cs
85+ else throwIO err
86+
87+tryErrorIO :: Typeable e => Unique -> IO a -> IO (Either ([String], e) a)
88+tryErrorIO tag m = catchErrorIO tag (Right <$> m) $ \es e -> pure $ Left (es, e)
tests/Main.hs+32−7
@@ -1,6 +1,7 @@
11module Main (main) where
22
33import Control.Monad.IO.Class
4+import Data.List (isPrefixOf)
45import Test.Tasty
56import Test.Tasty.HUnit
67import qualified Control.Monad.Catch as E
@@ -8,6 +9,7 @@ import qualified Control.Exception.Lifted as LE
89import qualified UnliftIO.Exception as UE
910
1011import Effectful.Async
12+import Effectful.Error
1113import Effectful.Interpreter
1214import Effectful.State.Dynamic
1315import Effectful.Monad
@@ -22,6 +24,7 @@ main = defaultMain $ testGroup "effectful"
2224 , testCase "exceptions" test_exceptions
2325 , testCase "concurrency" test_concurrentState
2426 , testCase "local effects" test_localEffects
27+ , testCase "error from interpret" test_errorFromInterpret
2528 ]
2629 ]
2730
@@ -118,12 +121,28 @@ test_localEffects = runIOE $ do
118121 getInt
119122 assertEqual_ "correct x" 4 x
120123
121−runHasInt :: Int -> Eff (HasInt : es) a -> Eff es a
122−runHasInt n =
123− -- reinterpret with redundant local effects
124− reinterpret (evalState () . evalState n . evalState True) $ \case
125− GetInt -> get
126− PutInt i -> put i
124+test_errorFromInterpret :: Assertion
125+test_errorFromInterpret = runIOE $ do
126+ result <- runError @String . runNestedErr $ do
127+ runError @String nestedErr
128+ liftIO $ case result of
129+ Left (cs, _) -> assertBool "stack trace points to the correct action" $
130+ "nestedErr" `isPrefixOf` last cs
131+ Right _ -> assertFailure "error caught by the wrong (inner) handler"
132+
133+----------------------------------------
134+-- Helpers
135+
136+data NestedErr :: Effect where
137+ NestedErr :: NestedErr m ()
138+
139+nestedErr :: (HasCallStack, NestedErr :> es) => Eff es ()
140+nestedErr = send NestedErr
141+
142+runNestedErr :: Error String :> es => Eff (NestedErr : es) a -> Eff es a
143+runNestedErr = interpret $ \NestedErr -> throwError "nested error"
144+
145+----------------------------------------
127146
128147data HasInt :: Effect where
129148 GetInt :: HasInt m Int
@@ -135,8 +154,14 @@ getInt = send GetInt
135154putInt :: HasInt :> es => Int -> Eff es ()
136155putInt = send . PutInt
137156
157+runHasInt :: Int -> Eff (HasInt : es) a -> Eff es a
158+runHasInt n =
159+ -- reinterpret with redundant local effects
160+ reinterpret (evalState () . evalState n . evalState True) $ \case
161+ GetInt -> get
162+ PutInt i -> put i
163+
138164----------------------------------------
139−-- Helpers
140165
141166data Ex = Ex deriving (Eq, Show)
142167instance E.Exception Ex