registry-0.1.1.0: src/Data/Registry/Registry.hs
{-# LANGUAGE AllowAmbiguousTypes #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE KindSignatures #-}
{-# LANGUAGE MonoLocalBinds #-}
{-# LANGUAGE UndecidableInstances #-}
{- |
A registry supports the creation of values out of existing values and
functions.
It contains 4 parts:
* values: they are available for building anything else and have their exact value can be shown
* functions: they are used to build other values. Only their type can be shown
* specializations: description of specific values to use while trying to build another value of a given type
* modifiers: function to apply to a newly built value before storing it for future use
A registry is created by using the `+:` operator, adding functions or values to the empty `end` registry:
> registry =
> val (Config 1)
> +: val "hello"
> +: fun add1
> +: fun show1
> +: end
At the type level a list of all the function inputs and all the outputs is being kept to
allow some checks to be made when we want to build a value out of the registry.
Registries have a `Monoid` instance so they can be created incrementally:
> config =
> val (Config 1)
> +: val "hello"
> +: end
>
> constructors =
> +: fun add1
> +: fun show1
> +: end
>
> registry =
> config <> constructors
It is also possible to use the `<+>` operator to "override" some configurations:
> mocks =
> fun noLogging
> +: fun inMemoryDb
> +: end
>
> mocks <+> registry
-}
module Data.Registry.Registry where
import Data.Registry.Internal.Cache
import Data.Registry.Internal.Types
import Data.Registry.Lift
import Data.Registry.Solver
import Data.Dynamic
import Data.Semigroup ((<>))
import Data.Text as T (unlines)
import Data.Typeable (Typeable)
import qualified Prelude (show)
import Protolude as P hiding ((<>))
import Type.Reflection
-- | Container for a list of functions or values
-- Internally all functions and values are stored as 'Dynamic' values
-- so that we can access their representation
data Registry (inputs :: [*]) (outputs :: [*]) =
Registry {
_values :: Values
, _functions :: Functions
, _specializations :: Specializations
, _modifiers :: Modifiers
}
instance Show (Registry inputs outputs) where
show (Registry (Values vs) (Functions fs) _ _) =
let describeValues =
if null vs then ""
else unlines (valDescriptionToText . valDescription <$> vs)
describeFunctions =
if null fs then ""
else unlines (funDescriptionToText . funDescription <$> fs)
in
toS $ unlines [describeValues, describeFunctions]
instance Semigroup (Registry inputs outputs) where
(<>) (Registry (Values vs1) (Functions fs1) (Specializations ss1) (Modifiers ms1))
(Registry (Values vs2) (Functions fs2) (Specializations ss2) (Modifiers ms2)) =
(Registry (Values (vs1 <> vs2)) (Functions (fs1 <> fs2)) (Specializations (ss1 <> ss2)) (Modifiers (ms1 <> ms2)))
instance Semigroup (Registry inputs outputs) => Monoid (Registry inputs outputs) where
mempty = Registry (Values []) (Functions []) (Specializations []) (Modifiers [])
mappend = (<>)
-- | Append 2 registries together
(<+>) :: Registry is1 os1 -> Registry is2 os2 -> Registry (is1 :++ is2) (os1 :++ os2)
(<+>) (Registry (Values vs1) (Functions fs1) (Specializations ss1) (Modifiers ms1))
(Registry (Values vs2) (Functions fs2) (Specializations ss2) (Modifiers ms2)) =
Registry (Values (vs1 <> vs2)) (Functions (fs1 <> fs2)) (Specializations (ss1 <> ss2)) (Modifiers (ms1 <> ms2))
-- | Store an element in the registry
-- Internally elements are stored as 'Dynamic' values
register :: (Typeable a)
=> Typed a
-> Registry ins out
-> Registry (Inputs a :++ ins) (Output a ': out)
register (TypedValue v) (Registry (Values vs) functions specializations modifiers) =
Registry (Values (v : vs)) functions specializations modifiers
register (TypedFunction f) (Registry (Values vs) (Functions fs) specializations modifiers) =
Registry (Values vs) (Functions (f : fs)) specializations modifiers
-- | Add an element to the Registry - Alternative to register where the parentheses can be ommitted
infixr 5 +:
(+:) :: (Typeable a) => Typed a -> Registry ins out -> Registry (Inputs a :++ ins) (Output a ': out)
(+:) = register
-- | The empty Registry
end :: Registry '[] '[]
end = Registry (Values []) (Functions []) (Specializations []) (Modifiers [])
-- | Create a value which can be added to the 'Registry'
val :: (Typeable a, Show a) => a -> Typed a
val a = TypedValue (ProvidedValue (toDyn a) (describeValue a))
-- | Create a value which can be added to the 'Registry' and "lift" it to an 'Applicative' context
valTo :: forall m a . (Applicative m, Typeable a, Typeable (m a), Show a) => a -> Typed (m a)
valTo a = TypedValue (liftProvidedValue @m a)
-- | Create a "lifted" a Value
liftProvidedValue :: forall m a . (Applicative m, Typeable a, Typeable (m a), Show a) => a -> Value
liftProvidedValue a = ProvidedValue (toDyn (pure a :: m a)) (describeValue a)
-- | Create a function which can be added to the 'Registry'
fun :: (Typeable a) => a -> Typed a
fun a = TypedFunction (createFunction a)
-- | This is a shortcut to @fun . allTo@ where @allTo@ lifts all the inputs and output
-- to an 'Applicative' context
funTo :: forall m a b . (ApplyVariadic m a b, Typeable a, Typeable b) => a -> Typed b
funTo a = fun (allTo @m a)
-- | This is a shortcut to @fun . argsTo@ where @allTo@ lifts all the inputs
-- to an Applicative context
funAs :: forall m a b . (ApplyVariadic1 m a b, Typeable a, Typeable b) => a -> Typed b
funAs a = fun (argsTo @m a)
-- | For a given type @a@ being currently built
-- when a value of type @b@ is required pass a specific value
specialize :: forall a b ins out . (Typeable a, Contains a out, Typeable b)
=> b
-> Registry ins out
-> Registry ins out
specialize = specializeUnsafe @a @b @ins @out
-- | This is similar to specialize but additionally uses the 'Show' instance of @b@
-- to display more information when printing the registry out
specializeVal :: forall a b ins out . (Typeable a, Contains a out, Typeable b, Show b)
=> b
-> Registry ins out
-> Registry ins out
specializeVal = specializeUnsafeVal @a @b @ins @out
specializeValTo :: forall m a b ins out . (Applicative m, Typeable a, Contains a out, Typeable (m b), Typeable b, Show b)
=> b
-> Registry ins out
-> Registry ins out
specializeValTo = specializeUnsafeValTo @m @a @b @ins @out
-- | For a given type `a` being currently built
-- when a value of type `b` is required pass a specific
-- value
specializeUnsafe :: forall a b ins out . (Typeable a, Typeable b)
=> b
-> Registry ins out
-> Registry ins out
specializeUnsafe b (Registry values functions (Specializations c) modifiers) = Registry
values
functions
(Specializations ((someTypeRep (Proxy :: Proxy a), createTypeableValue b) : c))
modifiers
specializeUnsafeVal :: forall a b ins out . (Typeable a, Contains a out, Typeable b, Show b)
=> b
-> Registry ins out
-> Registry ins out
specializeUnsafeVal b (Registry values functions (Specializations c) modifiers) = Registry
values
functions
(Specializations ((someTypeRep (Proxy :: Proxy a), createValue b) : c))
modifiers
specializeUnsafeValTo :: forall m a b ins out . (Applicative m, Typeable a, Typeable (m b), Typeable b, Show b)
=> b
-> Registry ins out
-> Registry ins out
specializeUnsafeValTo b (Registry values functions (Specializations c) modifiers) = Registry
values
functions
(Specializations ((someTypeRep (Proxy :: Proxy a), liftProvidedValue @m b) : c))
modifiers
-- | Once a value has been computed allow to modify it before storing it
tweak :: forall a ins out . (Typeable a, Contains a out)
=> (a -> a)
-> Registry ins out
-> Registry ins out
tweak = tweakUnsafe
-- | Once a value has been computed allow to modify it before storing
-- it
tweakUnsafe :: forall a ins out . (Typeable a)
=> (a -> a)
-> Registry ins out
-> Registry ins out
tweakUnsafe f (Registry values functions specializations (Modifiers mf)) = Registry values functions specializations
(Modifiers ((someTypeRep (Proxy :: Proxy a), createFunction f) : mf))
-- | Return singleton values for a monadic type
-- Note that the returned Registry is in 'IO' because we are caching a value
-- and this is a side-effect!
singleton :: forall m a ins out . (MonadIO m, Typeable a, Typeable (m a), Contains (m a) out)
=> Registry ins out
-> IO (Registry ins out)
singleton = singletonUnsafe @m @a @ins @out
singletonUnsafe :: forall m a ins out . (MonadIO m, Typeable a, Typeable (m a))
=> Registry ins out
-> IO (Registry ins out)
singletonUnsafe r = do
cache <- newCache @a
pure $ tweakUnsafe @(m a) (fetch cache) r