diff --git a/bluefin-internal/src/Bluefin/Internal.hs b/bluefin-internal/src/Bluefin/Internal.hs index bd5df54..c9d1e8d 100644 --- a/bluefin-internal/src/Bluefin/Internal.hs +++ b/bluefin-internal/src/Bluefin/Internal.hs @@ -35,6 +35,7 @@ infixr 9 :& type (:&) = Union type role Eff nominal representational + newtype Eff (es :: Effects) a = UnsafeMkEff {unsafeUnEff :: IO a} deriving stock (Functor) deriving newtype (Applicative, Monad) @@ -237,7 +238,6 @@ type Stream a = Coroutine a () -- fmap per @->@ that appears in type of the dynamic effect. That is, -- @queryDatabase@ has type @String -> Int -> Eff e [String]@, which -- has two @->@, so there are two @fmap@s before @useImpl@. - class Handle (h :: Effects -> Type) where -- | Used to create compound effects, i.e. handles that contain -- other handles. @@ -395,13 +395,34 @@ catch f h = handle h f -- exception. This is essentially the same as -- @Control.Exception.'Control.Exception.bracket'@, whose -- documentation you can inspect for further details. +-- +-- @bracket@ has a very general type that does not require @es@ to +-- contain an exception or IO effect. The reason that this is safe is: +-- +-- * While @bracket@ does catch exceptions, this is unobservable, +-- since the exception is re-thrown; the cleanup action happens +-- unconditionally; and no part of it gets access to the thrown +-- exception. +-- +-- * 'Eff' itself is able to guarantee that any exceptions thrown +-- in the body will be actually thrown before @bracket@ +-- exits. This is inherited from the fact that @Eff@ is a wrapper +-- around 'IO'. +-- +-- While it is usually the case that the cleanup action will in fact +-- want to use @IO@ effects, this is not universally true, see the +-- @polymorphicBracket@ example for an example. bracket :: Eff es a -> (a -> Eff es ()) -> (a -> Eff es b) -> Eff es b -bracket before after body = UnsafeMkEff $ Control.Exception.bracket - (unsafeUnEff before) (unsafeUnEff . after) (unsafeUnEff . body) +bracket before after body = + UnsafeMkEff $ + Control.Exception.bracket + (unsafeUnEff before) + (unsafeUnEff . after) + (unsafeUnEff . body) -- | -- @ diff --git a/bluefin-internal/src/Bluefin/Internal/Examples.hs b/bluefin-internal/src/Bluefin/Internal/Examples.hs index c0e4a8e..2adc8c7 100644 --- a/bluefin-internal/src/Bluefin/Internal/Examples.hs +++ b/bluefin-internal/src/Bluefin/Internal/Examples.hs @@ -616,3 +616,33 @@ instance Handle Application where applicationState = mapHandle a, logger = mapHandle l } + +-- This example shows a case where we can use @bracket@ polymorphically +-- in order to perform correct cleanup if @es@ is instantiated to a +-- set of effects that includes exceptions. +polymorphicBracket :: + (st :> es) => + State (Integer, Bool) st -> + Eff es () -> + Eff es () +polymorphicBracket st act = + bracket + (pure ()) + -- Always set the boolean indicating that we have terminated + (\_ -> modify st (\(c, b) -> (c, True))) + -- Perform the given effectful action, then increment the counter + (\_ -> do act; modify st (\(c, b) -> ((c + 1), b))) + +-- Results in (1, True) +polymorphicBracketExample1 :: (Integer, Bool) +polymorphicBracketExample1 = + runPureEff $ do + (_res, st) <- runState (0, False) $ \st -> polymorphicBracket st (pure ()) + pure st + +-- Results in (0, True) +polymorphicBracketExample2 :: (Integer, Bool) +polymorphicBracketExample2 = + runPureEff $ do + (_res, st) <- runState (0, False) $ \st -> try $ \e -> polymorphicBracket st (throw e 42) + pure st