registry-0.1.2.0: src/Data/Registry/RIO.hs
{-# LANGUAGE DeriveFunctor #-}
{-# LANGUAGE IncoherentInstances #-}
{-# LANGUAGE MonoLocalBinds #-}
{-# LANGUAGE UndecidableInstances #-}
{- |
RIO is equivalent to @ResourceT (WriterT Warmup IO)@
It can be used to instantiate "components as records of functions"
where each component can allocate resources and have a "warmup phase"
to preload data or assess if it is working properly.
-}
module Data.Registry.RIO where
import Control.Monad.Base
import Control.Monad.Catch
import Control.Monad.Trans
import Control.Monad.Trans.Resource
import qualified Control.Monad.Trans.Resource as Resource (allocate)
import Data.Registry.Make
import Data.Registry.Registry
import Data.Registry.Solver
import Data.Registry.Warmup
import Protolude
-- | Data type encapsulating resource finalizers
newtype Stop = Stop InternalState
-- | Run all finalizers
runStop :: Stop -> IO ()
runStop (Stop is) = runResourceT $ closeInternalState is
-- | This newtype creates a monad to sequence
-- component creation actions, cumulating start/stop tasks
-- found along the way
newtype RioT m a =
RioT
{ runRioT :: Stop -> m (a, Warmup) }
deriving (Functor)
-- | Specialization of RioT to IO
type RIO = RioT IO
runRIO :: RIO a -> Stop -> IO (a, Warmup)
runRIO = runRioT
instance (Monad m) => Applicative (RioT m) where
pure a =
RioT (const (pure (a, mempty)))
RioT fab <*> RioT fa =
RioT $ \s ->
do (f, sf) <- fab s
(a, sa) <- fa s
pure (f a, sf `mappend` sa)
instance (Monad m) => Monad (RioT m) where
return = pure
RioT ma >>= f =
RioT $ \s ->
do (a, sa) <- ma s
(b, sb) <- runRioT (f a) s
pure (b, sa `mappend` sb)
instance (MonadIO m) => MonadIO (RioT m) where
liftIO io = RioT (const $ (, mempty) <$> liftIO io)
instance (MonadThrow m) => MonadThrow (RioT m) where
throwM e = RioT (const $ throwM e)
instance (MonadBase IO m, MonadIO m) => MonadBase IO (RioT m) where
liftBase = liftIO
instance MonadResource m => MonadResource (RioT m) where
liftResourceT action = RioT $ \(Stop s) -> liftIO ((, mempty) <$> runInternalState action s)
instance MonadTrans RioT where
lift :: Monad m => m a -> RioT m a
lift ma = RioT (const $ (, mempty) <$> ma)
-- * For production
-- | This function must be used to run services involving a top component
-- It creates the top component and invokes all warmup functions
--
-- The passed function 'f' is used to decide whether to continue or
-- not depending on the Result
withRegistry :: forall a b ins out m . (Typeable a, Typeable m, MonadIO m, MonadUnliftIO m, Contains (RioT m a) out, Solvable ins out) =>
Registry ins out
-> (Result -> a -> m b)
-> m b
withRegistry registry f = runResourceT $ do
(a, warmup) <- runRegistryT @a registry
result <- lift . liftIO $ runWarmup warmup
lift $ f result a
-- | This can be used if you want to insert the component creation inside
-- another action managed with 'ResourceT'. Or if you want to call 'runResourceT' yourself later
runRegistryT :: forall a ins out m . (Typeable a, Typeable m, MonadIO m, Contains (RioT m a) out, Solvable ins out) => Registry ins out -> ResourceT m (a, Warmup)
runRegistryT registry = withInternalState $ \is -> runRioT (make @(RioT m a) registry) (Stop is)
-- * For testing
-- | Instantiate the component but don't execute the warmup (it may take time)
-- and keep the Stop value to clean resources later
-- This function statically checks that the component can be instantiated
executeRegistry :: forall a ins out m . (Typeable a, Typeable m, MonadIO m, Contains (RioT m a) out, Solvable ins out) => Registry ins out -> m (a, Warmup, Stop)
executeRegistry registry = do
is <- liftIO createInternalState
(a, w) <- runRioT (make @(RioT m a) registry) (Stop is)
pure (a, w, Stop is)
-- | Instantiate the component but don't execute the warmup (it may take time) and lose a way to cleanu up resources
-- | Almost no compilation time is spent on checking that component resolution is possible
unsafeRun :: forall a ins out m . (Typeable a, Typeable m, MonadIO m, Contains (RioT m a) out) => Registry ins out -> m a
unsafeRun registry = fst <$> unsafeRunWithStop registry
-- | Same as 'unsafeRun' but keep the 'Stop' value to be able to clean resources later
unsafeRunWithStop :: forall a ins out m . (Typeable a, Typeable m, MonadIO m, Contains (RioT m a) out) => Registry ins out -> m (a, Stop)
unsafeRunWithStop registry = do
is <- createInternalState
(a, _) <- runRioT (makeUnsafe @(RioT m a) registry) (Stop is)
pure (a, Stop is)
-- | Lift a 'Warmup' action into the 'RioT m' monad
warmupWith :: (Applicative m) => Warmup -> RioT m ()
warmupWith w = RioT (const $ pure ((), w))
-- | Allocate some resource
allocate :: (MonadResource m) => IO a -> (a -> IO ()) -> RioT m a
allocate resource cleanup =
snd <$> Resource.allocate resource cleanup