{-# LANGUAGE UndecidableInstances #-}
{-# OPTIONS_GHC -Wno-orphans #-}
-- | Resource management via 'R.MonadResource'.
module Effectful.Resource
( -- * Effect
Resource
-- ** Handlers
, runResource
-- * Registering and releasing resources
, allocateEff
, allocateEff_
, registerEff
, releaseEff
, R.allocate
, R.allocate_
, R.register
, R.release
, R.unprotect
, ReleaseAction(..)
, unprotectEff
-- * Internal state
, R.InternalState
, getInternalState
, runInternalState
, R.createInternalState
, R.closeInternalState
-- * Re-exports
, R.ReleaseKey
, R.ResourceCleanupException(..)
) where
import Control.Exception
import qualified Control.Monad.Trans.Resource as R
import qualified Control.Monad.Trans.Resource.Internal as RI
import Effectful
import Effectful.Dispatch.Static
import Effectful.Dispatch.Static.Primitive
-- | Provide the ability to use the 'R.MonadResource' instance of 'Eff'.
data Resource :: Effect
type instance DispatchOf Resource = Static WithSideEffects
newtype instance StaticRep Resource = Resource R.InternalState
-- | Run the resource effect.
runResource :: IOE :> es => Eff (Resource : es) a -> Eff es a
runResource m = unsafeEff $ \es0 -> do
istate <- R.createInternalState
mask $ \unmask -> do
es <- consEnv (Resource istate) dummyRelinker es0
a <- unmask (unEff m es) `catch` \e -> do
unconsEnv es
RI.stateCleanupChecked (Just e) istate
throwIO e
unconsEnv es
RI.stateCleanupChecked Nothing istate
pure a
----------------------------------------
-- Registering and releasing resources
-- | A variant of 'R.allocate` adjusted to work in the 'Eff' monad.
--
-- /Note:/ the @release@ action will run a cloned environment, so any changes it
-- makes to thread local data will not be visible outside of it.
allocateEff
:: Resource :> es
=> Eff es a -- ^ allocate
-> (a -> Eff es ()) -- ^ free resource
-> Eff es (R.ReleaseKey, a)
allocateEff acquire release = do
istate <- getInternalState
unsafeEff $ \es0 -> mask_ $ do
a <- unEff acquire es0
-- we need to clone original env for release action
-- because it will be called when original env already unconsed
es1 <- cloneEnv es0
key <- RI.register' istate $ unEff (release a) es1
pure (key, a)
-- | A variant of 'R.allocate_' adjusted to work in the 'Eff' monad.
--
-- /Note:/ the @release@ action will run a cloned environment, so any changes it
-- makes to thread local data will not be visible outside of it.
allocateEff_
:: Resource :> es
=> Eff es a -- ^ allocate
-> Eff es () -- ^ free resource
-> Eff es R.ReleaseKey
allocateEff_ a = fmap fst . allocateEff a . const
-- | A variant of 'R.register' adjusted to work in the 'Eff' monad.
--
-- /Note:/ the @release@ action will run a cloned environment, so any changes it
-- makes to thread local data will not be visible outside of it.
registerEff :: Resource :> es => Eff es () -> Eff es R.ReleaseKey
registerEff release = do
istate <- getInternalState
unsafeEff $ \es0 -> do
-- we need to clone original env for release action
-- because it will be called when original env already unconsed
es1 <- cloneEnv es0
RI.register' istate $ unEff release es1
-- | A variant of 'R.release' adjusted to work in the 'Eff' monad.
releaseEff :: Resource :> es => R.ReleaseKey -> Eff es ()
releaseEff = unsafeEff_ . R.release
-- | Action for releasing a resource.
newtype ReleaseAction = ReleaseAction
{ runReleaseAction :: forall es. Resource :> es => Eff es ()
}
-- | A variant of 'R.unprotect' adjusted to work in the 'Eff' monad.
--
-- /Note:/ if the resource was acquired using 'allocateEff', 'allocateEff_' or
-- 'registerEff' then the returned 'ReleaseAction' will run in a clone of the
-- environment it was registered in.
--
-- See the documentation of the aforementioned functions for more information.
unprotectEff :: Resource :> es => R.ReleaseKey -> Eff es (Maybe ReleaseAction)
unprotectEff key = unsafeEff_ $ do
fmap (\m -> ReleaseAction $ unsafeEff_ m) <$> R.unprotect key
----------------------------------------
-- Internal state
-- | Get the 'R.InternalState' of the current 'Resource' effect.
getInternalState :: Resource :> es => Eff es R.InternalState
getInternalState = do
Resource istate <- getStaticRep
pure istate
-- | Run the 'Resource' effect with existing 'R.InternalState'.
--
-- /Note:/ the 'R.InternalState' will not be closed at the end.
runInternalState :: IOE :> es => R.InternalState -> Eff (Resource : es) a -> Eff es a
runInternalState istate = evalStaticRep (Resource istate)
----------------------------------------
-- Orphan instance
instance (IOE :> es, Resource :> es) => R.MonadResource (Eff es) where
liftResourceT (RI.ResourceT m) = unsafeEff $ \es -> do
getEnv es >>= \(Resource istate) -> m istate