packages feed

general-allocate-0.2.0.0: src/Control/Monad/Allocate.hs

{-# LANGUAGE AllowAmbiguousTypes #-}
{-# LANGUAGE ConstraintKinds #-}
{-# LANGUAGE DerivingVia #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE InstanceSigs #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE StandaloneDeriving #-}
{-# LANGUAGE StandaloneKindSignatures #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE UndecidableInstances #-}
{-# LANGUAGE UnicodeSyntax #-}

{- |
Description : Exception-safe arbitrary-lifetime resource usage with deterministic cleanup
Copyright   : Copyright 2022 Shea Levy.
License     : Apache-2.0
Maintainer  : shea@shealevy.com

This module defines interfaces for safe resource usage on top of 'GeneralAllocate', where
resources have an arbitrary lifetime and can be released manually or at the end of the
computation.

For contexts where nested scope-based allocation and release is sufficient, see
"Control.Monad.With".

This design is heavily based on 'MonadResource' from [resourcet](https://github.com/snoyberg/conduit/tree/master/resourcet).
@resourcet@ is @Copyright (c)2011, Michael Snoyman@, and licensed under the BSD 3-clause license
available at [LICENSE.resourcet](https://github.com/shlevy/general-allocate/blob/master/LICENSE.resourcet).
-}
module Control.Monad.Allocate where

import Control.Exception.Safe
import Control.Monad.Cont
import Control.Monad.Except
import Control.Monad.RWS.Lazy
import qualified Control.Monad.RWS.Strict as Strict
import Control.Monad.Reader
import Control.Monad.State.Lazy
import qualified Control.Monad.State.Strict as Strict
import Control.Monad.Trans.Maybe
import Control.Monad.Trans.Resource hiding (throwM)
import Control.Monad.Trans.Resource.Internal
import Control.Monad.Writer.Lazy
import qualified Control.Monad.Writer.Strict as Strict
import Data.Acquire
import Data.Acquire.Internal
import Data.Exceptable
import Data.GeneralAllocate
import Data.Kind

{- | A monad allowing for exception-safe resource usage with arbitrary lifetimes.

The guarantees of 'MonadAllocate' are weaker than 'MonadResource':
in some monads, it's possible for resources not to get cleaned up if the
/entire/ monadic computation is going to be aborted (e.g. an async exception
sent to a thread executing a monad with no exception catching). Of course,
'MonadResource' itself can't guarantee cleanup in the presence of @SIGKILL@...
In any case, this allows for 'MonadAllocate' to be implemented lawfully in more
monads (see t'Control.Monad.NoContinuation.Resource.NoContinuationResourceT'). This
allows for writing monad-generic exception-safe code which can be properly
instantiated in 'IO' or mocked out in 'Control.Monad.ST.ST' without changing the code.
-}
class (Monad m, Monad (AllocationContext m)) ⇒ MonadAllocate m where
  -- | The monad in which resources are allocated and released.
  type AllocationContext m ∷ Type → Type

  -- | A handle to release some resource early manually
  type GeneralReleaseKey m

  -- | The exception type of the monadic context
  type AllocationException m

  type AllocationException m = SomeException

  -- | Allocate some resource, which will be cleaned up on call to
  -- 'generalRelease' or the end of the current resource scope,
  -- whichever comes first.
  generalAllocate
    ∷ GeneralAllocate (AllocationContext m) (AllocationException m) () () a
    → m (GeneralReleaseKey m, a)

  -- | Register an action which will be guaranteed to run on call to
  -- 'generalRelease' or the end of the current resource scope,
  -- whichever comes first.
  generalRegister
    ∷ (GeneralReleaseType (AllocationException m) () → AllocationContext m ())
    → m (GeneralReleaseKey m)
  generalRegister rel = fst <$> (generalAllocate . GeneralAllocate $ alloc)
   where
    alloc ∷ (∀ x. AllocationContext m x → AllocationContext m x) → AllocationContext m (GeneralAllocated (AllocationContext m) (AllocationException m) () () ())
    alloc _ = pure $ GeneralAllocated () rel

  -- | Run a release action from a prior call to 'generalAllocate'/'generalRegister'.
  generalRelease
    ∷ GeneralReleaseKey m
    → AllocationContext m ()

-- | A 'MonadAllocate' whose exception type can be projected into the Haskell exception hierarchy
type MonadAllocateExceptable m = (MonadAllocate m, Exceptable (AllocationException m))

{- | A helper for [DerivingVia](https://ghc.gitlab.haskell.org/ghc/doc/users_guide/exts/deriving_via.html) a
'MonadAllocate' instance for any 'MonadResource'.
-}
newtype AllocateViaResource m a = AllocateViaResource (m a) deriving newtype (Functor, Applicative, Monad)

instance (MonadResource m) ⇒ MonadAllocate (AllocateViaResource m) where
  type AllocationContext (AllocateViaResource m) = IO
  type GeneralReleaseKey (AllocateViaResource m) = ReleaseKey

  generalAllocate
    ∷ ∀ a
     . GeneralAllocate (AllocationContext (AllocateViaResource m)) SomeException () () a
    → AllocateViaResource m (ReleaseKey, a)
  generalAllocate (GeneralAllocate alloc) = AllocateViaResource $ do
    let alloc' ∷ ((∀ b. IO b → IO b) → IO (Allocated a))
        alloc' restore = do
          GeneralAllocated x rel ← alloc restore
          let rel' (ReleaseExceptionWith e) = rel $ ReleaseFailure e
              rel' _ = rel $ ReleaseSuccess ()
          pure $ Allocated x rel'
    (k, x) ← allocateAcquire $ Acquire alloc'
    pure (k, x)

  generalRegister rel = AllocateViaResource . liftResourceT . withInternalState $ \st → do
    let rel' (ReleaseExceptionWith e) = rel $ ReleaseFailure e
        rel' _ = rel $ ReleaseSuccess ()
    registerType st rel'
  generalRelease = release

deriving via AllocateViaResource (ResourceT m) instance (MonadIO m) ⇒ MonadAllocate (ResourceT m)

{- | A helper for [DerivingVia](https://ghc.gitlab.haskell.org/ghc/doc/users_guide/exts/deriving_via.html) a
'MonadAllocate' instance for any 'MonadTrans'formed 'MonadAllocate'.
-}
type AllocateViaLift ∷ ((Type → Type) → (Type → Type)) → (Type → Type) → Type → Type
newtype AllocateViaLift t m a = AllocateViaLift (t m a) deriving newtype (Functor, Applicative, Monad, MonadTrans)

instance (MonadAllocate m, MonadTrans t, Monad (t m)) ⇒ MonadAllocate (AllocateViaLift t m) where
  type AllocationContext (AllocateViaLift t m) = AllocationContext m
  type GeneralReleaseKey (AllocateViaLift t m) = GeneralReleaseKey m
  type AllocationException (AllocateViaLift t m) = AllocationException m
  generalAllocate = lift . generalAllocate
  generalRegister = lift . generalRegister
  generalRelease = generalRelease @m

deriving via AllocateViaLift MaybeT m instance (MonadAllocate m) ⇒ MonadAllocate (MaybeT m)

deriving via AllocateViaLift (ExceptT e) m instance (MonadAllocate m) ⇒ MonadAllocate (ExceptT e m)

deriving via AllocateViaLift (ReaderT r) m instance (MonadAllocate m) ⇒ MonadAllocate (ReaderT r m)

deriving via AllocateViaLift (StateT s) m instance (MonadAllocate m) ⇒ MonadAllocate (StateT s m)

deriving via AllocateViaLift (Strict.StateT s) m instance (MonadAllocate m) ⇒ MonadAllocate (Strict.StateT s m)

deriving via AllocateViaLift (WriterT w) m instance (Monoid w, MonadAllocate m) ⇒ MonadAllocate (WriterT w m)

deriving via AllocateViaLift (Strict.WriterT w) m instance (Monoid w, MonadAllocate m) ⇒ MonadAllocate (Strict.WriterT w m)

deriving via AllocateViaLift (ContT r) m instance (MonadAllocate m) ⇒ MonadAllocate (ContT r m)

deriving via AllocateViaLift (RWST r w s) m instance (Monoid w, MonadAllocate m) ⇒ MonadAllocate (RWST r w s m)

deriving via AllocateViaLift (Strict.RWST r w s) m instance (Monoid w, MonadAllocate m) ⇒ MonadAllocate (Strict.RWST r w s m)