
On Wed, Dec 28, 2022 at 08:38:04PM +0000, Tom Ellis wrote:
I'd like to implement "scoped exceptions"[1], that is, a combinator of type
withScopedException :: ((forall a. e -> IO a) -> IO r) -> IO (Either e r) [...] Here's a hacky way of doing it, based on tagging each exception with a unique value, and then filtering when handling:
data MyException where MyException :: e -> Data.Unique.Unique -> MyException
instance Show MyException where show _ = "<MyException>"
instance Exception MyException
withScopedException :: ((forall a. e -> IO a) -> IO r) -> IO (Either e r) wiathScopedException f = do fresh <- Data.Unique.newUnique flip tryJust (f (\e -> throwIO (MyException e fresh))) $ \case MyException e tag -> -- unsafeCoerce is very unpleasant if tag == fresh then Just (unsafeCoerce e) else Nothing
This is the approach taken by the effectful library[2]. But is there a better way? Can I persuade GHC's RTS to work like this directly?
I found a better way, or more accurately LSLeary suggested it to me, now implemented in Bluefin.Internal.Exception.Scoped: https://hackage-content.haskell.org/package/bluefin-internal-0.1.0.0/docs/Bl... The innovation is to have a `Key` type that supports: newKey :: IO (Key a) eqKey :: forall a b. Key a -> Key b -> Maybe (a :~~: b) This allows us to tag thrown values with a `Key` and unwrap them only when we have another copy of the same key, which allows us to obtain type equality. (`Key` is also available in Apfelmus's `vault` package.) Tom