Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add some more docs and examples for bracket #11

Merged
merged 2 commits into from
May 1, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 24 additions & 3 deletions bluefin-internal/src/Bluefin/Internal.hs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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)

-- |
-- @
Expand Down
30 changes: 30 additions & 0 deletions bluefin-internal/src/Bluefin/Internal/Examples.hs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Loading