packages feed

fx-0.11: library/Fx/Scope.hs

-- |
-- Scope abstraction for resource management
module Fx.Scope
  ( -- * Scope
    Scope (..),
    acquire,
    releasing,
    registerRelease,
    scoping,
  )
where

import Fx.Fx
import Fx.Prelude

-- |
-- Instructions of how to acquire and release a resource of type `env` and which may fail with `err`.
newtype Scope err env = Scope (Fx () err (env, Fx () err ()))

instance Functor (Scope err) where
  fmap f (Scope m) = Scope $ do
    (env, release) <- m
    return (f env, release)

instance Applicative (Scope err) where
  pure env = Scope (pure (env, pure ()))
  Scope m1 <*> Scope m2 =
    Scope $
      liftA2 (\(env1, release1) (env2, release2) -> (env1 env2, release2 *> release1)) m1 m2

instance Monad (Scope err) where
  return = pure
  Scope m >>= f =
    Scope do
      (env, release) <- m
      let Scope m2 = f env
      (env2, release2) <- m2
      return (env2, release2 *> release)

instance Bifunctor Scope where
  bimap lf rf (Scope m) = Scope (bimap lf (bimap rf (first lf)) m)
  second = fmap

-- |
-- Create a resource provider from an acquiring effect.
--
-- To add a release action, use 'releasing'.
acquire :: Fx () err env -> Scope err env
acquire acquireFx = Scope do
  env <- acquireFx
  return (env, pure ())

-- |
-- Add a release action to an existing resource provider.
--
-- Example:
--
-- > fileInWriteMode :: FilePath -> Scope IOError Handle
-- > fileInWriteMode path =
-- >   releasing hClose (acquire (openFile path WriteMode))
releasing :: Fx env err () -> Scope err env -> Scope err env
releasing release (Scope m) = Scope $ do
  (env, existingRelease) <- m
  return (env, existingRelease >> closeEnv env release)

-- |
-- Schedule an action to be run after the scope is exited.
registerRelease :: Fx () err () -> Scope err ()
registerRelease release =
  Scope $ pure ((), release)

-- |
-- Execute Fx in the scope of a provided environment.
scoping :: Scope err env -> Fx env err res -> Fx env' err res
scoping (Scope (Fx acquire)) (Fx fx) =
  Fx $
    ReaderT $
      \(FxEnv unmask crash _) -> ExceptT $ do
        let providerFxEnv = FxEnv unmask crash ()
        acquisition <- runExceptT (runReaderT acquire providerFxEnv)
        case acquisition of
          Left err -> return (Left err)
          Right (env, (Fx release)) -> do
            resOrErr <- runExceptT (runReaderT fx (FxEnv unmask crash env))
            releasing <- runExceptT (runReaderT release providerFxEnv)
            return (resOrErr <* releasing)

closeEnv :: env -> Fx env err res -> Fx env' err res
closeEnv env (Fx fx) =
  Fx $
    ReaderT $
      \(FxEnv unmask crash _) ->
        ExceptT $
          runExceptT (runReaderT fx (FxEnv unmask crash env))

instance RunsFx () err (Scope err) where
  runFx fx = Scope (fmap (\env -> (env, pure ())) fx)