registry-0.6.0.0: src/Data/Registry/Rio.hs
{-# LANGUAGE AllowAmbiguousTypes #-}
{-# LANGUAGE DataKinds #-}
-- | Utilities for working with resources
module Data.Registry.Rio
( module Data.Registry.Rio,
singleton,
cacheAt
)
where
import Control.Monad.Morph
import Control.Monad.Trans.Resource
import Data.Dynamic
import Data.Registry.Internal.Cache
import Data.Registry.Make (make)
import Data.Registry.Registry
import Protolude
-- | This data type provides some support for creating effectful components with resources
-- You can use the regular MonadResource functions like allocate to make sure that resources are cleaned up
-- You can also use the 'cacheAt' function
newtype Rio a = Rio {rioRun :: ReaderT Cache (ResourceT IO) a}
deriving (Functor, Applicative, Monad, MonadReader Cache, MonadIO, MonadResource, MonadUnliftIO)
-- | Run a Rio action by providing an empty cache and allocating / destroying resources
runRio :: MonadIO m => Rio a -> m a
runRio = liftIO . runResourceT . runCache
-- | Run a Rio action by providing an empty cache and allocating / destroying resources
execRio :: MonadIO m => Rio a -> m (a, Cache)
execRio = liftIO . runResourceT . execCache
-- | Use the value created by a Rio action so that resources are properly allocated and cached
withRio :: MonadIO m => Rio a -> (a -> IO b) -> m b
withRio action f = liftIO . runResourceT $ do
a <- runCache action
liftIO $ f a
-- | Use the value created by a Rio action so that resources are properly allocated and cached
-- inside a monad transformer
withRioM :: (MonadResource (m (ResourceT IO)), MFunctor m) => Rio a -> (a -> m IO b) -> m IO b
withRioM action f = hoist runResourceT $ do
a <- liftResourceT (runCache action)
hoist lift (f a)
-- | Run a Rio action by providing an empty cache
runCache :: Rio a -> ResourceT IO a
runCache (Rio action) = do
cache <- liftIO newCache
runReaderT action cache
-- | Run a Rio action by providing an empty cache, and return the final cache
-- for inspection
execCache :: Rio a -> ResourceT IO (a, Cache)
execCache (Rio action) = do
cache <- liftIO newCache
(,cache) <$> runReaderT action cache
-- | Lift a resourceful value into Rio
liftRio :: ResourceT IO a -> Rio a
liftRio = Rio . lift
-- | This function must be used to run services involving resources
-- The value a is created using the registry, used with the function 'f'
-- and all resources are freed at the end
withRegistry :: forall a b ins out m. (Typeable a, MonadIO m, MakeSingletons out) => Registry ins out -> (a -> IO b) -> m b
withRegistry registry f =
liftIO $ runResourceT (runRegistryT @a registry >>= liftIO . f)
-- | This function works like 'withRegistry' for a higher-order monad, typically `PropertyT IO` when
-- writing property tests with Hedgehog
withRegistryM ::
forall a b ins out m.
(Typeable a, MonadResource (m (ResourceT IO)), MFunctor m, MakeSingletons out) =>
Registry ins out ->
(a -> m IO b) ->
m IO b
withRegistryM = withRioM . make @(Rio a) . singletons
-- | Create a function of type a with a given registry
-- Return a ResourceT value to control resource allocation
runRegistryT :: forall a ins out. (Typeable a, MakeSingletons out) => Registry ins out -> ResourceT IO a
runRegistryT = runCache . make @(Rio a) . singletons
-- | Make singletons for all the output types of a registry
-- but only if they not specialized values
singletons :: forall ins out. (MakeSingletons out) => Registry ins out -> Registry ins out
singletons r = _singletonsRegistry $ makeSingletons (startSingletonsRegistry r)
-- | Registry where all Rio values are singletons
newtype SingletonsRegistry (todo :: [Type]) (ins :: [Type]) (out :: [Type]) = SingletonsRegistry {_singletonsRegistry :: Registry ins out}
-- | Prepare a Registry for making singletons
startSingletonsRegistry :: Registry ins out -> SingletonsRegistry out ins out
startSingletonsRegistry = SingletonsRegistry
-- | Prepare a Registry for making singletons on a specific list of types
makeSingletonsRegistry :: forall todo ins out. Registry ins out -> SingletonsRegistry todo ins out
makeSingletonsRegistry = SingletonsRegistry @todo
-- | This typeclass take an existing registry and makes a singleton for each Rio output type
class MakeSingletons ls where
makeSingletons :: SingletonsRegistry ls ins out -> SingletonsRegistry '[] ins out
-- | If the list of types is empty there is nothing to do
instance MakeSingletons '[] where
makeSingletons = identity
-- | If the type represents an effectful value, make a singleton for it and recurse on the rest
instance {-# OVERLAPPING #-} (Typeable a, MakeSingletons rest) => MakeSingletons (Rio a : rest) where
makeSingletons (SingletonsRegistry r) =
makeSingletons $ SingletonsRegistry @rest (tweakUnspecialized @(Rio a) singleton r)
-- | If the type represents a pure value, make singletons for the rest
instance (MakeSingletons rest) => MakeSingletons (a : rest) where
makeSingletons (SingletonsRegistry r) = makeSingletons (makeSingletonsRegistry @rest r)