packages feed

orville-postgresql-1.1.0.0: src/Orville/PostgreSQL/Monad/HasOrvilleState.hs

{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE RankNTypes #-}

{- |
Copyright : Flipstone Technology Partners 2023
License   : MIT
Stability : Stable

@since 1.0.0.0
-}
module Orville.PostgreSQL.Monad.HasOrvilleState
  ( HasOrvilleState (askOrvilleState, localOrvilleState)
  )
where

import Control.Monad.Trans.Class (lift)
import Control.Monad.Trans.Reader (ReaderT, ask, local, mapReaderT)

import Orville.PostgreSQL.OrvilleState (OrvilleState)

{- | 'HasOrvilleState' is the typeclass that Orville uses to access and manange
  the connection pool and state tracking when it is being executed inside an
  unknown Monad. It is a specialized version of the Reader interface so that it
  can be easily implemented by application Monads that already have a Reader
  context and want to simply add 'OrvilleState' as an attribute to that
  context, like so

  @
    data MyApplicationState =
      MyApplicationState
        { appConfig :: MyAppConfig
        , appOrvilleState :: OrvilleState
        }

    newtype MyApplicationMonad a =
      MyApplicationMonad (ReaderT MyApplicationState IO) a

    instance HasOrvilleState MyApplicationMonad where
      askOrvilleState =
        MyApplicationMonad (asks appOrvilleState)

      localOrvilleState f (MyApplicationMonad reader) =
        MyApplicationMonad $
          local
            (\\state -> state { appOrvilleState = f (appOrvilleState state))
            reader
  @

  An instance for 'ReaderT OrvilleState m' is provided as a convenience in
  the case that your application has no extra context to track.

@since 1.0.0.0
-}
class HasOrvilleState m where
  -- | Fetches the current 'OrvilleState' from the host Monad context. The
  -- equivalent of 'ask' for 'ReaderT OrvilleState'.
  --
  -- @since 1.0.0.0
  askOrvilleState :: m OrvilleState

  -- | Applies a modification to the 'OrvilleState' that is local to the given
  -- monad operation. Calls to 'askOrvilleState' made within the 'm a' provided
  -- must return the modified state. The modified state must only apply to
  -- the given 'm a' and not be persisted beyond it. The equivalent of 'local'
  -- for 'ReaderT OrvilleState'.
  --
  -- @since 1.0.0.0
  localOrvilleState ::
    -- | The function to modify the 'OrvilleState'.
    (OrvilleState -> OrvilleState) ->
    -- | The monad operation to execute with the modified state.
    m a ->
    m a

-- | @since 1.0.0.0
instance Monad m => HasOrvilleState (ReaderT OrvilleState m) where
  askOrvilleState = ask
  localOrvilleState = local

-- | @since 1.0.0.0
instance {-# OVERLAPS #-} (Monad m, HasOrvilleState m) => HasOrvilleState (ReaderT r m) where
  askOrvilleState = lift askOrvilleState
  localOrvilleState f = mapReaderT (localOrvilleState f)