{-# LANGUAGE DerivingVia #-}
module Control.Effect.Regional
  ( -- * Effects
    Regional(..)
  , Hoist

    -- * Actions
  , regionally
  , hoist

    -- * Interpretations
  , runHoist

  , hoistToFinal

    -- * Threading utilities
  , threadRegionalViaOptional

    -- * Combinators for 'Algebra's
    -- Intended to be used for custom 'Carrier' instances when
    -- defining 'algPrims'.
  , powerAlgHoist
  , powerAlgHoistFinal

    -- * Carriers
  , HoistC
  , HoistToFinalC
  ) where

import Control.Effect
import Control.Effect.Carrier

import Control.Effect.Type.Regional
import Control.Effect.Type.Optional
import Control.Effect.Internal.Regional

import Control.Monad.Trans.Control (control)

-- | Execute a computation modified in some way, providing
-- the interpreter of @'Regional' s@ a constant to indicate
-- how the computation should be modified.
regionally :: Eff (Regional s) m => s -> m a -> m a
regionally s m = send (Regionally s m)
{-# INLINE regionally #-}

-- | Lift a natural transformation of a base monad to the
-- current monad.
hoist :: Eff (Hoist b) m => (forall x. b x -> b x) -> m a -> m a
hoist n = regionally (HoistCall n)
{-# INLINE hoist #-}

type HoistToFinalC b = InterpretPrimC HoistToFinalH (Hoist b)

-- | Run a @'Hoist' m@ effect, where the base monad @m@ is the current monad.
--
-- @'Derivs' ('HoistC' m) = 'Hoist' m ': 'Derivs' m@
--
-- @'Prims'  ('HoistC' m) = 'Hoist' m ': 'Prims' m@
runHoist :: Carrier m
         => HoistC m a
         -> m a
runHoist = unHoistC
{-# INLINE runHoist #-}

-- | Run a @'Hoist' b@ effect, where the base monad @b@ is the final base monad.
--
-- @'Derivs' ('HoistToFinalC' b m) = 'Hoist' b ': 'Derivs' m@
--
-- @'Prims'  ('HoistToFinalC' b m) = 'Hoist' b ': 'Prims' m@
hoistToFinal :: ( MonadBaseControl b m
                , Carrier m
                )
             => HoistToFinalC b m a
             -> m a
hoistToFinal = interpretPrimViaHandler
{-# INLINE hoistToFinal #-}

-- | Strengthen an @'Algebra' p m@ by adding a @'Hoist' m@ handler
powerAlgHoist :: forall m p a
               . Algebra' p m a
              -> Algebra' (Hoist m ': p) m a
powerAlgHoist alg = powerAlg alg $ \(Regionally (HoistCall n) m) -> n m
{-# INLINE powerAlgHoist #-}

-- | Strengthen an @'Algebra' p m@ by adding a @'Hoist' b@ handler, where
-- @b@ is the final base monad.
powerAlgHoistFinal :: forall b m p a
                    . MonadBaseControl b m
                   => Algebra' p m a
                   -> Algebra' (Hoist b ': p) m a
powerAlgHoistFinal alg = powerAlg alg $ \case
  Regionally (HoistCall n) m -> control $ \lower -> n (lower m)
{-# INLINE powerAlgHoistFinal #-}