packages feed

registry 0.1.2.2 → 0.1.2.3

raw patch · 30 files changed

+1377/−522 lines, 30 filesdep +containersdep +hashabledep +semigroupoidsdep ~basePVP: major bump suggested

API removals or changes: PVP suggests a major version bump

Dependencies added: containers, hashable, semigroupoids, semigroups

Dependency ranges changed: base

API changes (from Hackage documentation)

- Data.Registry.Dot: makeOperationsEither :: forall a ins out. Typeable a => Registry ins out -> Either Text Operations
- Data.Registry.Dot: makeOperationsUnsafe :: forall a ins out. Typeable a => Registry ins out -> Operations
- Data.Registry.Internal.Operations: AppliedFunction :: Value -> [Value] -> AppliedFunction
- Data.Registry.Internal.Operations: Dot :: Text -> Dot
- Data.Registry.Internal.Operations: [_inputValues] :: AppliedFunction -> [Value]
- Data.Registry.Internal.Operations: [_outputValue] :: AppliedFunction -> Value
- Data.Registry.Internal.Operations: [unDot] :: Dot -> Text
- Data.Registry.Internal.Operations: adjust :: Text -> Text
- Data.Registry.Internal.Operations: data AppliedFunction
- Data.Registry.Internal.Operations: escapeNewlines :: Text -> Text
- Data.Registry.Internal.Operations: instance GHC.Classes.Eq Data.Registry.Internal.Operations.Dot
- Data.Registry.Internal.Operations: instance GHC.Show.Show Data.Registry.Internal.Operations.AppliedFunction
- Data.Registry.Internal.Operations: instance GHC.Show.Show Data.Registry.Internal.Operations.Dot
- Data.Registry.Internal.Operations: makeEdges :: Operations -> [(Value, Value)]
- Data.Registry.Internal.Operations: newtype Dot
- Data.Registry.Internal.Operations: nodeDescription :: ValueDescription -> Text
- Data.Registry.Internal.Operations: removeQuotes :: Text -> Text
- Data.Registry.Internal.Operations: toDot :: Operations -> Dot
- Data.Registry.Internal.Operations: toDotEdge :: (Value, Value) -> Text
- Data.Registry.Internal.Operations: type Operations = [AppliedFunction]
- Data.Registry.Internal.Stack: getOperation :: Stack Operations
- Data.Registry.Internal.Types: [_context] :: Context -> [SomeTypeRep]
- Data.Registry.Internal.Types: getValueDynamic :: Value -> Dynamic
+ Data.Registry.Internal.Dot: Dot :: Text -> Dot
+ Data.Registry.Internal.Dot: [unDot] :: Dot -> Text
+ Data.Registry.Internal.Dot: adjust :: Text -> Text
+ Data.Registry.Internal.Dot: countValueTypes :: Value -> DotState ()
+ Data.Registry.Internal.Dot: escapeNewlines :: Text -> Text
+ Data.Registry.Internal.Dot: hashOf :: Value -> Int
+ Data.Registry.Internal.Dot: instance GHC.Classes.Eq Data.Registry.Internal.Dot.Dot
+ Data.Registry.Internal.Dot: instance GHC.Show.Show Data.Registry.Internal.Dot.Dot
+ Data.Registry.Internal.Dot: makeEdges :: Operations -> [(Value, Value)]
+ Data.Registry.Internal.Dot: newtype Dot
+ Data.Registry.Internal.Dot: nodeDescription :: ValueDescription -> ValueCounter -> Text
+ Data.Registry.Internal.Dot: removeQuotes :: Text -> Text
+ Data.Registry.Internal.Dot: showValueCounter :: ValueCounter -> Text
+ Data.Registry.Internal.Dot: toDot :: Operations -> Dot
+ Data.Registry.Internal.Dot: toDotEdge :: ValuesByType -> (Value, Value) -> Text
+ Data.Registry.Internal.Dot: toDotVertex :: ValuesByType -> Value -> Text
+ Data.Registry.Internal.Dot: type DotState = State ValuesByType
+ Data.Registry.Internal.Dot: type Edge = (Value, Value)
+ Data.Registry.Internal.Dot: type Edges = [Edge]
+ Data.Registry.Internal.Dot: type Hash = Int
+ Data.Registry.Internal.Dot: type ValueCounter = Maybe Int
+ Data.Registry.Internal.Dot: type ValueHashes = [Hash]
+ Data.Registry.Internal.Dot: type ValueId = Int
+ Data.Registry.Internal.Dot: type ValuesByType = Map SomeTypeRep ValueHashes
+ Data.Registry.Internal.Registry: findBestSpecializedValue :: SomeTypeRep -> Context -> Specializations -> Maybe Value
+ Data.Registry.Internal.Registry: findCompatibleCreatedValue :: SomeTypeRep -> Specializations -> Values -> Maybe Value
+ Data.Registry.Internal.Stack: evalStackWithValues :: Values -> Stack a -> Either Text Statistics
+ Data.Registry.Internal.Stack: execStackWithValues :: Values -> Stack a -> Either Text Values
+ Data.Registry.Internal.Stack: getOperations :: Stack Operations
+ Data.Registry.Internal.Stack: modifyStatistics :: (Statistics -> Statistics) -> Stack ()
+ Data.Registry.Internal.Stack: runStackWithValues :: Values -> Stack a -> Either Text a
+ Data.Registry.Internal.Statistics: AppliedFunction :: Value -> [Value] -> AppliedFunction
+ Data.Registry.Internal.Statistics: Statistics :: Operations -> Values -> Statistics
+ Data.Registry.Internal.Statistics: [_inputValues] :: AppliedFunction -> [Value]
+ Data.Registry.Internal.Statistics: [_outputValue] :: AppliedFunction -> Value
+ Data.Registry.Internal.Statistics: [operations] :: Statistics -> Operations
+ Data.Registry.Internal.Statistics: [values] :: Statistics -> Values
+ Data.Registry.Internal.Statistics: allValuesPaths :: Statistics -> Paths
+ Data.Registry.Internal.Statistics: data AppliedFunction
+ Data.Registry.Internal.Statistics: data Statistics
+ Data.Registry.Internal.Statistics: initStatistics :: Values -> Statistics
+ Data.Registry.Internal.Statistics: instance GHC.Base.Monoid Data.Registry.Internal.Statistics.Statistics
+ Data.Registry.Internal.Statistics: instance GHC.Base.Semigroup Data.Registry.Internal.Statistics.Statistics
+ Data.Registry.Internal.Statistics: instance GHC.Show.Show Data.Registry.Internal.Statistics.AppliedFunction
+ Data.Registry.Internal.Statistics: instance GHC.Show.Show Data.Registry.Internal.Statistics.Statistics
+ Data.Registry.Internal.Statistics: type Operations = [AppliedFunction]
+ Data.Registry.Internal.Statistics: type Paths = [[Value]]
+ Data.Registry.Internal.Statistics: usedSpecializations :: Statistics -> [Specialization]
+ Data.Registry.Internal.Statistics: valuePaths :: Value -> Paths
+ Data.Registry.Internal.Types: Dependencies :: [Value] -> Dependencies
+ Data.Registry.Internal.Types: DependenciesTypes :: [SomeTypeRep] -> DependenciesTypes
+ Data.Registry.Internal.Types: Specialization :: NonEmpty SomeTypeRep -> Value -> Specialization
+ Data.Registry.Internal.Types: SpecializedContext :: Maybe Int -> Maybe Int -> SpecializedContext
+ Data.Registry.Internal.Types: [_contextStack] :: Context -> [SomeTypeRep]
+ Data.Registry.Internal.Types: [_endRange] :: SpecializedContext -> Maybe Int
+ Data.Registry.Internal.Types: [_specializationPath] :: Specialization -> NonEmpty SomeTypeRep
+ Data.Registry.Internal.Types: [_specializationValue] :: Specialization -> Value
+ Data.Registry.Internal.Types: [_startRange] :: SpecializedContext -> Maybe Int
+ Data.Registry.Internal.Types: [unDependenciesTypes] :: DependenciesTypes -> [SomeTypeRep]
+ Data.Registry.Internal.Types: [unDependencies] :: Dependencies -> [Value]
+ Data.Registry.Internal.Types: [unSpecializations] :: Specializations -> [Specialization]
+ Data.Registry.Internal.Types: [unValues] :: Values -> [Value]
+ Data.Registry.Internal.Types: applicableTo :: Specializations -> Context -> Specializations
+ Data.Registry.Internal.Types: createValueFromSpecialization :: Context -> Specialization -> Value
+ Data.Registry.Internal.Types: data Specialization
+ Data.Registry.Internal.Types: data SpecializedContext
+ Data.Registry.Internal.Types: dependenciesOn :: Value -> Dependencies
+ Data.Registry.Internal.Types: dependenciesTypes :: Dependencies -> DependenciesTypes
+ Data.Registry.Internal.Types: hasSpecializedDependencies :: Specializations -> Value -> Bool
+ Data.Registry.Internal.Types: instance Data.Hashable.Class.Hashable Data.Registry.Internal.Types.Context
+ Data.Registry.Internal.Types: instance Data.Hashable.Class.Hashable Data.Registry.Internal.Types.Dependencies
+ Data.Registry.Internal.Types: instance Data.Hashable.Class.Hashable Data.Registry.Internal.Types.Value
+ Data.Registry.Internal.Types: instance Data.Hashable.Class.Hashable Data.Registry.Internal.Types.ValueDescription
+ Data.Registry.Internal.Types: instance GHC.Base.Monoid Data.Registry.Internal.Types.Dependencies
+ Data.Registry.Internal.Types: instance GHC.Base.Monoid Data.Registry.Internal.Types.DependenciesTypes
+ Data.Registry.Internal.Types: instance GHC.Base.Semigroup Data.Registry.Internal.Types.Dependencies
+ Data.Registry.Internal.Types: instance GHC.Base.Semigroup Data.Registry.Internal.Types.DependenciesTypes
+ Data.Registry.Internal.Types: instance GHC.Classes.Eq Data.Registry.Internal.Types.Context
+ Data.Registry.Internal.Types: instance GHC.Classes.Eq Data.Registry.Internal.Types.DependenciesTypes
+ Data.Registry.Internal.Types: instance GHC.Classes.Eq Data.Registry.Internal.Types.SpecializedContext
+ Data.Registry.Internal.Types: instance GHC.Classes.Ord Data.Registry.Internal.Types.SpecializedContext
+ Data.Registry.Internal.Types: instance GHC.Show.Show Data.Registry.Internal.Types.Dependencies
+ Data.Registry.Internal.Types: instance GHC.Show.Show Data.Registry.Internal.Types.DependenciesTypes
+ Data.Registry.Internal.Types: instance GHC.Show.Show Data.Registry.Internal.Types.Specialization
+ Data.Registry.Internal.Types: instance GHC.Show.Show Data.Registry.Internal.Types.SpecializedContext
+ Data.Registry.Internal.Types: isContextApplicable :: Context -> Specialization -> Bool
+ Data.Registry.Internal.Types: isInSpecializationContext :: SomeTypeRep -> Value -> Bool
+ Data.Registry.Internal.Types: makeCreatedValue :: Dynamic -> ValueDescription -> Dependencies -> Value
+ Data.Registry.Internal.Types: makeProvidedValue :: Dynamic -> ValueDescription -> Value
+ Data.Registry.Internal.Types: newtype Dependencies
+ Data.Registry.Internal.Types: newtype DependenciesTypes
+ Data.Registry.Internal.Types: specializationContext :: Value -> Maybe Context
+ Data.Registry.Internal.Types: specializationEnd :: Specialization -> SomeTypeRep
+ Data.Registry.Internal.Types: specializationStart :: Specialization -> SomeTypeRep
+ Data.Registry.Internal.Types: specializationTargetType :: Specialization -> SomeTypeRep
+ Data.Registry.Internal.Types: specializedContext :: Context -> Specialization -> SpecializedContext
+ Data.Registry.Internal.Types: usedSpecialization :: Value -> Maybe Specialization
+ Data.Registry.Internal.Types: valDependencies :: Value -> Dependencies
+ Data.Registry.RIO: instance Data.Functor.Alt.Alt Data.Registry.RIO.RIO
+ Data.Registry.RIO: instance GHC.Base.Alternative Data.Registry.RIO.RIO
+ Data.Registry.RIO: withRIOAndWarmupResult :: MonadIO m => (Result -> IO ()) -> RIO a -> (a -> IO b) -> m b
+ Data.Registry.RIO: withRIOIgnoreWarmupResult :: MonadIO m => RIO a -> (a -> IO b) -> m b
+ Data.Registry.Registry: MemoizeRegistry :: Registry ins out -> MemoizeRegistry
+ Data.Registry.Registry: [_unMemoizeRegistry] :: MemoizeRegistry -> Registry ins out
+ Data.Registry.Registry: class MemoizedActions ls
+ Data.Registry.Registry: class PathToTypeReps (path :: [*])
+ Data.Registry.Registry: instance (Control.Monad.IO.Class.MonadIO m, Data.Typeable.Internal.Typeable a, Data.Typeable.Internal.Typeable (m a), Data.Registry.Registry.MemoizedActions rest) => Data.Registry.Registry.MemoizedActions (m a : rest)
+ Data.Registry.Registry: instance (Data.Typeable.Internal.Typeable a, Data.Registry.Registry.PathToTypeReps rest) => Data.Registry.Registry.PathToTypeReps (a : rest)
+ Data.Registry.Registry: instance Data.Registry.Registry.MemoizedActions '[]
+ Data.Registry.Registry: instance Data.Registry.Registry.MemoizedActions rest => Data.Registry.Registry.MemoizedActions (a : rest)
+ Data.Registry.Registry: instance Data.Typeable.Internal.Typeable a => Data.Registry.Registry.PathToTypeReps '[a]
+ Data.Registry.Registry: makeMemoizeRegistry :: forall todo ins out. Registry ins out -> MemoizeRegistry todo ins out
+ Data.Registry.Registry: memoize :: forall m a ins out. (MonadIO m, Typeable a, Typeable (m a), Contains (m a) out) => Registry ins out -> IO (Registry ins out)
+ Data.Registry.Registry: memoizeActions :: MemoizedActions ls => MemoizeRegistry ls ins out -> IO (MemoizeRegistry '[] ins out)
+ Data.Registry.Registry: memoizeAll :: forall m ins out. (MonadIO m, MemoizedActions out) => Registry ins out -> IO (Registry ins out)
+ Data.Registry.Registry: memoizeUnsafe :: forall m a ins out. (MonadIO m, Typeable a, Typeable (m a)) => Registry ins out -> IO (Registry ins out)
+ Data.Registry.Registry: newtype MemoizeRegistry (todo :: [*]) (ins :: [*]) (out :: [*])
+ Data.Registry.Registry: someTypeReps :: PathToTypeReps path => Proxy path -> NonEmpty SomeTypeRep
+ Data.Registry.Registry: specializePath :: forall path b ins out. (PathToTypeReps path, IsSubset path out, Typeable b) => b -> Registry ins out -> Registry ins out
+ Data.Registry.Registry: specializePathUnsafe :: forall path b ins out. (PathToTypeReps path, Typeable b) => b -> Registry ins out -> Registry ins out
+ Data.Registry.Registry: specializePathUnsafeVal :: forall path b ins out. (PathToTypeReps path, Typeable b, Show b) => b -> Registry ins out -> Registry ins out
+ Data.Registry.Registry: specializePathUnsafeValTo :: forall m path b ins out. (Applicative m, PathToTypeReps path, Typeable (m b), Typeable b, Show b) => b -> Registry ins out -> Registry ins out
+ Data.Registry.Registry: specializePathVal :: forall path b ins out. (PathToTypeReps path, IsSubset path out, Typeable b, Show b) => b -> Registry ins out -> Registry ins out
+ Data.Registry.Registry: specializePathValTo :: forall m path b ins out. (Applicative m, PathToTypeReps path, IsSubset path out, Typeable (m b), Typeable b, Show b) => b -> Registry ins out -> Registry ins out
+ Data.Registry.Registry: startMemoizeRegistry :: Registry ins out -> MemoizeRegistry out ins out
+ Data.Registry.Statistics: makeStatistics :: forall a ins out. (Typeable a, Contains a out, Solvable ins out) => Registry ins out -> Statistics
+ Data.Registry.Statistics: makeStatisticsEither :: forall a ins out. Typeable a => Registry ins out -> Either Text Statistics
+ Data.Registry.Statistics: makeStatisticsFast :: forall a ins out. (Typeable a, Contains a out) => Registry ins out -> Statistics
+ Data.Registry.Statistics: makeStatisticsUnsafe :: forall a ins out. Typeable a => Registry ins out -> Statistics
- Data.Registry.Internal.Stack: evalStack :: Stack a -> Values -> Either Text Operations
+ Data.Registry.Internal.Stack: evalStack :: Stack a -> Either Text Statistics
- Data.Registry.Internal.Stack: execStack :: Stack a -> Values -> Either Text Values
+ Data.Registry.Internal.Stack: execStack :: Stack a -> Either Text Values
- Data.Registry.Internal.Stack: runStack :: Stack a -> Values -> Either Text a
+ Data.Registry.Internal.Stack: runStack :: Stack a -> Either Text a
- Data.Registry.Internal.Stack: type Stack a = StateT (Values, Operations) (Either Text) a
+ Data.Registry.Internal.Stack: type Stack a = StateT Statistics (Either Text) a
- Data.Registry.Internal.Types: CreatedValue :: Dynamic -> ValueDescription -> Value
+ Data.Registry.Internal.Types: CreatedValue :: Dynamic -> ValueDescription -> Maybe Context -> Maybe Specialization -> Dependencies -> Value
- Data.Registry.Internal.Types: Specializations :: [(SomeTypeRep, Value)] -> Specializations
+ Data.Registry.Internal.Types: Specializations :: [Specialization] -> Specializations
- Data.Registry.Internal.Types: createValue :: (Show a, Typeable a) => a -> Value
+ Data.Registry.Internal.Types: createValue :: (Typeable a, Show a) => a -> Value
- Data.Registry.RIO: executeRegistry :: forall a ins out. (Typeable a, Contains (RIO a) out, Solvable ins out) => Registry ins out -> IO (a, Warmup, Stop)
+ Data.Registry.RIO: executeRegistry :: forall a ins out m. (Typeable a, Contains (RIO a) out, Solvable ins out, MonadIO m) => Registry ins out -> m (a, Warmup, Stop)
- Data.Registry.RIO: runRegistryT :: forall a ins out. (Typeable a, Contains (RIO a) out, Solvable ins out) => Registry ins out -> ResourceT IO (a, Warmup)
+ Data.Registry.RIO: runRegistryT :: forall a ins out m. (Typeable a, Contains (RIO a) out, Solvable ins out, MonadIO m, MemoizedActions out) => Registry ins out -> ResourceT m (a, Warmup)
- Data.Registry.RIO: unsafeRun :: forall a ins out. (Typeable a, Contains (RIO a) out) => Registry ins out -> IO a
+ Data.Registry.RIO: unsafeRun :: forall a ins out m. (Typeable a, Contains (RIO a) out, MonadIO m) => Registry ins out -> m a
- Data.Registry.RIO: unsafeRunDynamic :: forall a ins out. Typeable a => Registry ins out -> IO a
+ Data.Registry.RIO: unsafeRunDynamic :: forall a ins out m. (Typeable a, MonadIO m) => Registry ins out -> m a
- Data.Registry.RIO: unsafeRunDynamicWithStop :: forall a ins out. Typeable a => Registry ins out -> IO (a, Stop)
+ Data.Registry.RIO: unsafeRunDynamicWithStop :: forall a ins out m. (Typeable a, MonadIO m) => Registry ins out -> m (a, Stop)
- Data.Registry.RIO: unsafeRunWithStop :: forall a ins out. (Typeable a, Contains (RIO a) out) => Registry ins out -> IO (a, Stop)
+ Data.Registry.RIO: unsafeRunWithStop :: forall a ins out m. (Typeable a, Contains (RIO a) out, MonadIO m) => Registry ins out -> m (a, Stop)
- Data.Registry.RIO: withNoWarmupRIO :: RIO a -> (a -> IO b) -> IO b
+ Data.Registry.RIO: withNoWarmupRIO :: MonadIO m => RIO a -> (a -> IO b) -> m b
- Data.Registry.RIO: withRIO :: RIO a -> (a -> IO ()) -> IO Result
+ Data.Registry.RIO: withRIO :: MonadIO m => RIO a -> (a -> IO ()) -> m Result
- Data.Registry.RIO: withRegistry :: forall a b ins out. (Typeable a, Contains (RIO a) out, Solvable ins out) => Registry ins out -> (Result -> a -> IO b) -> IO b
+ Data.Registry.RIO: withRegistry :: forall a b ins out m. (Typeable a, Contains (RIO a) out, Solvable ins out, MonadIO m, MemoizedActions out) => Registry ins out -> (Result -> a -> IO b) -> m b

Files

registry.cabal view
@@ -4,10 +4,10 @@ -- -- see: https://github.com/sol/hpack ----- hash: 7566c8a11593eb9d930102b802543fdd53f6664c7b6d041c3fe6d67c48386775+-- hash: e570c9eb12f1d7452ae6ba23b2fd2fde606bc45719f4bc981ddea25dc2b18704  name:           registry-version:        0.1.2.2+version:        0.1.2.3 synopsis:       data structure for assembling components description:    This library provides a "Registry" which is a data structure containing a list of functions and values representing dependencies in a directed acyclic graph. A `make` function can then be used to create a value of a specific type out of the registry.                 You can start with the [README](https://github.com/etorreborre/registry/blob/master/README.md) for a full description of the library.@@ -26,18 +26,20 @@       Data.Registry       Data.Registry.Dot       Data.Registry.Internal.Cache+      Data.Registry.Internal.Dot       Data.Registry.Internal.Dynamic       Data.Registry.Internal.Make-      Data.Registry.Internal.Operations       Data.Registry.Internal.Reflection       Data.Registry.Internal.Registry       Data.Registry.Internal.Stack+      Data.Registry.Internal.Statistics       Data.Registry.Internal.Types       Data.Registry.Lift       Data.Registry.Make       Data.Registry.Registry       Data.Registry.RIO       Data.Registry.Solver+      Data.Registry.Statistics       Data.Registry.Warmup   other-modules:       Paths_registry@@ -47,10 +49,14 @@   ghc-options: -Wall -Wcompat -Wincomplete-record-updates -fhide-source-paths -fprint-potential-instances -optP-Wno-nonportable-include-path -Wincomplete-uni-patterns   build-depends:       base >=4.7 && <5+    , containers <0.7     , exceptions <0.11+    , hashable <1.3     , mtl <3     , protolude <0.3     , resourcet <1.3+    , semigroupoids <5.4+    , semigroups <0.19     , text <2     , transformers-base <0.5   default-language: Haskell2010@@ -67,10 +73,15 @@       Test.Data.Registry.Internal.MakeSpec       Test.Data.Registry.Internal.ReflectionSpec       Test.Data.Registry.Internal.RegistrySpec-      Test.Data.Registry.Make+      Test.Data.Registry.Internal.TypesSpec+      Test.Data.Registry.Make.MakeSpec+      Test.Data.Registry.Make.MemoizeSpec+      Test.Data.Registry.Make.SpecializationSpec+      Test.Data.Registry.Make.TweakingSpec       Test.Data.Registry.MonadRandomSpec       Test.Data.Registry.RegistrySpec       Test.Data.Registry.RIOSpec+      Test.Data.Registry.SimpleExamples       Test.Data.Registry.SmallExample       Test.Data.Registry.WarmupSpec       Test.Tasty.Extensions@@ -83,7 +94,9 @@       MonadRandom <0.6     , async <2.3     , base >=4.7 && <5+    , containers <0.7     , exceptions <0.11+    , hashable <1.3     , hedgehog <0.7     , hedgehog-corpus <0.2     , io-memoize <1.2@@ -92,6 +105,8 @@     , random <2.0     , registry     , resourcet <1.3+    , semigroupoids <5.4+    , semigroups <0.19     , tasty <1.3     , tasty-discover <4.3     , tasty-hedgehog <0.3
src/Data/Registry.hs view
@@ -8,10 +8,11 @@   module M ) where -import           Data.Registry.Dot      as M -- Produce a graph out of a registry-import           Data.Registry.Lift     as M -- Lift functions into a monadic context-import           Data.Registry.Make     as M -- Various "make" functions to create components from a registry-import           Data.Registry.Registry as M -- The Registry data structure-import           Data.Registry.RIO      as M -- A monad for instantiating components (managing resources, handling startup)-import           Data.Registry.Solver   as M -- Type-level constraints to check if we can make a component from a registry-import           Data.Registry.Warmup   as M -- A small DSL for describing the warmup actions of a component+import           Data.Registry.Dot        as M -- Produce a graph out of a registry+import           Data.Registry.Statistics as M -- Provide statistics about the execution of a registry+import           Data.Registry.Lift       as M -- Lift functions into a monadic context+import           Data.Registry.Make       as M -- Various "make" functions to create components from a registry+import           Data.Registry.Registry   as M -- The Registry data structure+import           Data.Registry.RIO        as M -- A monad for instantiating components (managing resources, handling startup)+import           Data.Registry.Solver     as M -- Type-level constraints to check if we can make a component from a registry+import           Data.Registry.Warmup     as M -- A small DSL for describing the warmup actions of a component
src/Data/Registry/Dot.hs view
@@ -7,24 +7,18 @@   out of a 'Registry'. -} module Data.Registry.Dot (-  module O+  module D , makeDot , makeDotEither , makeDotFast , makeDotUnsafe-, makeOperationsEither-, makeOperationsUnsafe ) where -import           Data.Registry.Internal.Make-import           Data.Registry.Internal.Operations as O-import           Data.Registry.Internal.Stack-import           Data.Registry.Internal.Types+import           Data.Registry.Internal.Dot as D+import           Data.Registry.Statistics import           Data.Registry.Registry import           Data.Registry.Solver-import           Prelude                           (error) import           Protolude-import           Type.Reflection  -- | Make a DOT graph for a specific value `a` built from the 'Registry' --   `a` is at the root of the graph and its children are values@@ -44,41 +38,9 @@ -- | Similar to `make` but does not check if `a` can be made out of the 'Regisry' --   It returns a Left value if that's not the case makeDotEither :: forall a ins out . (Typeable a) => Registry ins out -> Either Text Dot-makeDotEither r = toDot <$> makeOperationsEither @a r+makeDotEither r = toDot . operations <$> makeStatisticsEither @a r  -- | Similar to `make` but does not check if `a` can be made out of the 'Regisry' --   and throws an exception if that's not the case makeDotUnsafe :: forall a ins out . (Typeable a) => Registry ins out -> Dot-makeDotUnsafe = toDot . makeOperationsUnsafe @a---- | Return an `Operations` value listing all the function applications necessary to---   create a value of a given type-makeOperationsEither :: forall a ins out . (Typeable a) => Registry ins out -> Either Text Operations-makeOperationsEither registry =-  let values          = _values registry-      functions       = _functions registry-      specializations = _specializations registry-      modifiers       = _modifiers registry-      targetType      = someTypeRep (Proxy :: Proxy a)-  in-      -- use the makeUntyped function to create an element of the target type from a list of values and functions-      -- the list of values is kept as some State so that newly created values can be added to the current state-      case-        (flip evalStack) values-          (makeUntyped targetType (Context [targetType]) functions specializations modifiers)--      of-        Left e ->-          Left $ "could not create a " <> show targetType <> " out of the registry because " <> e <> "\nThe registry is\n" <>-                 show registry--        other ->-          other---- | Return an `Operations` value listing all the function applications necessary to---   create a value of a given type (and throws an exception if the value cannot be created)-makeOperationsUnsafe  :: forall a ins out . (Typeable a) => Registry ins out -> Operations-makeOperationsUnsafe registry =-  case makeOperationsEither @a registry of-    Right a -> a-    Left  e -> Prelude.error (toS e)+makeDotUnsafe = toDot . operations . makeStatisticsUnsafe @a
src/Data/Registry/Internal/Cache.hs view
@@ -1,6 +1,6 @@ {- | - Cache for individual IO values when we wish to make singletons+ Cache for individual IO values when we wish to memoize actions  for database connection pools for example   This is inspired by https://hackage.haskell.org/package/io-memoize
+ src/Data/Registry/Internal/Dot.hs view
@@ -0,0 +1,135 @@+{- |+  Nested datatype to track the resolution algorithm++  From this data type we can draw a graph of the full+  instantation of a value+-}+module Data.Registry.Internal.Dot where++import           Data.Hashable+import           Data.List                         (elemIndex)+import           Data.Map.Strict                   hiding (adjust)+import           Data.Registry.Internal.Statistics+import           Data.Registry.Internal.Types+import           Data.Text                         as T+import           Protolude                         as P+import           Type.Reflection++-- | Make a list of graph edges from the list of function applications+makeEdges :: Operations -> [(Value, Value)]+makeEdges []                               = []+makeEdges (AppliedFunction out ins : rest) = ((out,) <$> ins) <> makeEdges rest++-- * DOT GRAPH++-- | A DOT graph+newtype Dot = Dot {+  unDot :: Text+} deriving (Eq, Show)++-- Use a State type to get the current index of a value+-- when there are values of the same type and different+-- hash values+type DotState = State ValuesByType+type ValuesByType = Map SomeTypeRep ValueHashes+type Hash = Int+type ValueId = Int+type ValueHashes = [Hash]+type Edge = (Value, Value)+type Edges = [Edge]+type ValueCounter = Maybe Int++-- | Make a DOT graph out of all the function applications+toDot :: Operations -> Dot+toDot op =+  let edges = makeEdges op+      allValues = join $ (\(v1, v2) -> [v1, v2]) <$> edges+      valueTypes = execState (traverse countValueTypes allValues) mempty+  in Dot $+       T.unlines $+       [ "strict digraph {"+       ,  "  node [shape=record]"+       ]+       <> (toDotEdge valueTypes <$> edges)+       <> ["}"]++-- | Update a map classifying values by type+countValueTypes :: Value -> DotState ()+countValueTypes value = do+  maps <- get+  let key = valueDynTypeRep value+  let valueHash = hashOf value++  case lookup key maps of+    -- there were no values for that type, create a list with the value hash+    Nothing -> put $ insert key [valueHash] maps++    -- there is a list of hashes for that type+    Just hashes ->+      case elemIndex valueHash hashes of+        -- that value hasn't been seen before+        Nothing -> do+          let newHashes = hashes <> [valueHash]+          put $ insert key newHashes maps++        -- the value has been seen before+        Just _ -> pure ()++-- | A DOT edge representing the dependency between 2 values+toDotEdge :: ValuesByType -> (Value, Value) -> Text+toDotEdge valuesByType (value1, value2) =+  let v1 = toDotVertex valuesByType value1+      v2 = toDotVertex valuesByType value2+  in  v1 <> " -> "  <> v2 <> ";"++-- | Represent a value as a vertex in a dot graph+--   we use some state to keep track of values of the+--   same type+--   The values are numbered starting from 1 when there are+--   several of them for the same type+toDotVertex :: ValuesByType -> Value -> Text+toDotVertex valuesByType value =+  let key = valueDynTypeRep value+      valueHash = hashOf value++      valueCounter =+        case lookup key valuesByType of+          Nothing -> Nothing -- this case should not happen given how the map is built+          Just hashes ->+            if P.length hashes == 1 then Nothing+            else (+1) <$> elemIndex valueHash hashes++  in adjust (nodeDescription (valDescription value) valueCounter)++-- | Return the hash of a value based on its dependencies+hashOf :: Value -> Int+hashOf value = hash+  (unDependencies . valDependencies $ value, valDescription value)++-- | Description of a Value in the DOT graph+nodeDescription :: ValueDescription -> ValueCounter -> Text+nodeDescription (ValueDescription t Nothing) n =+  t <> showValueCounter n+nodeDescription (ValueDescription t (Just v)) n =+  nodeDescription (ValueDescription t Nothing) n <> "\n" <> v++-- | Don't show the counter if there+showValueCounter :: ValueCounter -> Text+showValueCounter Nothing  = ""+showValueCounter (Just n) = "-" <> show n++-- | We need to process the node descriptions+--     - we add quotes arountd the text+--     - we remove quotes (") inside the text+--     - we escape newlines+adjust :: Text -> Text+adjust node = "\"" <> (escapeNewlines . removeQuotes) node <> "\""++-- | Remove quotes from a textual description to avoid breaking the DOT format+removeQuotes :: Text -> Text+removeQuotes = T.replace "\"" ""++-- | Replace \n with \\n so that newlines are kept in+--   node descriptions+escapeNewlines :: Text -> Text+escapeNewlines = T.replace "\n" "\\n"
src/Data/Registry/Internal/Dynamic.hs view
@@ -18,7 +18,7 @@   -> Either Text Value  -- ^ result applyFunction function [] =   if P.null (collectInputTypes function) then-    pure $ CreatedValue (funDyn function) (ValueDescription (_outputType . funDescription $ function) Nothing)+    pure $ makeCreatedValue (funDyn function) (ValueDescription (_outputType . funDescription $ function) Nothing) mempty   else     Left $  "the function "     <> show (dynTypeRep (funDyn function))@@ -26,7 +26,10 @@  applyFunction function values =   do created <- applyFunctionDyn (funDyn function) (valueDyn <$> values)-     pure $ CreatedValue created (ValueDescription (_outputType . funDescription $ function) Nothing)+     let description  = ValueDescription (_outputType . funDescription $ function) Nothing+     let dependencies = foldMap dependenciesOn values++     pure $ makeCreatedValue created description dependencies  -- | Apply a Dynamic function to a list of Dynamic values applyFunctionDyn ::
src/Data/Registry/Internal/Make.hs view
@@ -41,9 +41,10 @@   -> Stack (Maybe Value) makeUntyped targetType context functions specializations modifiers = do   values <- getValues-   -- is there already a value with the desired type?-  case findValue targetType context specializations values of+  let foundValue = findValue targetType context specializations values++  case foundValue of     Nothing ->       -- if not, is there a way to build such value?       case findConstructor targetType functions of@@ -67,8 +68,8 @@                 <> fmap show missingInputTypes             else do               -- else apply the function and store the output value in the registry-              v <- lift $ applyFunction function inputs-              modified <- storeValue modifiers v+              value <- lift $ applyFunction function inputs+              modified <- storeValue modifiers value                functionApplied modified inputs               pure (Just modified)
− src/Data/Registry/Internal/Operations.hs
@@ -1,73 +0,0 @@-{- |-  Nested datatype to track the resolution algorithm--  From this data type we can draw a graph of the full-  instantation of a value--}-module Data.Registry.Internal.Operations where--import           Data.Registry.Internal.Types-import           Data.Text as T-import           Protolude---- | A list of function applications created---   when creating a value out of the Registry-type Operations = [AppliedFunction]---- | A function application with an output value and a list of input values-data AppliedFunction = AppliedFunction {-    _outputValue :: Value-  , _inputValues ::[Value]-  } deriving (Show)---- | Make a list of graph edges from the list of function applications-makeEdges :: Operations -> [(Value, Value)]-makeEdges [] = []-makeEdges (AppliedFunction out ins : rest) =-  ((out,) <$> ins) <>-  makeEdges rest---- * DOT GRAPH---- | A DOT graph-newtype Dot = Dot {-  unDot :: Text-  } deriving (Eq, Show)---- | Make a DOT graph out of all the function applications-toDot :: Operations -> Dot-toDot op = Dot $ T.unlines $-  [ "strict digraph {"-  ,  "  node [shape=record]"-  ]-  <> (toDotEdge <$> makeEdges op)-  <> ["}"]---- | A DOT edge representing the dependency between 2 values-toDotEdge :: (Value, Value) -> Text-toDotEdge (v1, v2) =-     adjust (nodeDescription . valDescription $ v1)-  <> " -> "-  <> adjust (nodeDescription . valDescription $ v2)-  <> ";"---- | Description of a Value in the DOT graph-nodeDescription :: ValueDescription -> Text-nodeDescription (ValueDescription t Nothing) = t-nodeDescription (ValueDescription t (Just v)) = t <> "\n" <> v---- | We need to process the node descriptions---     - we add quotes arountd the text---     - we remove quotes (") inside the text---     - we escape newlines-adjust :: Text -> Text-adjust t = "\"" <> (escapeNewlines . removeQuotes) t <> "\""---- | Remove quotes from a textual description to avoid breaking the DOT format-removeQuotes :: Text -> Text-removeQuotes = T.replace "\"" ""---- | Replace \n with \\n so that newlines are kept in---   node descriptions-escapeNewlines :: Text -> Text-escapeNewlines = T.replace "\n" "\\n"
src/Data/Registry/Internal/Registry.hs view
@@ -1,7 +1,7 @@-{-# LANGUAGE AllowAmbiguousTypes        #-}-{-# LANGUAGE DataKinds                  #-}-{-# LANGUAGE MonoLocalBinds             #-}-{-# LANGUAGE UndecidableInstances       #-}+{-# LANGUAGE AllowAmbiguousTypes  #-}+{-# LANGUAGE DataKinds            #-}+{-# LANGUAGE MonoLocalBinds       #-}+{-# LANGUAGE UndecidableInstances #-}  {- |   Internal structure of a 'Registry' and@@ -10,9 +10,9 @@ module Data.Registry.Internal.Registry where  import           Data.Registry.Internal.Dynamic-import           Data.Registry.Internal.Types import           Data.Registry.Internal.Stack-import           Protolude                       as P+import           Data.Registry.Internal.Types+import           Protolude                      as P import           Type.Reflection  -- | Find a value having a target type from:@@ -20,32 +20,63 @@ --        to find the targe in a specific context (Context). Context describes --       the types of values we are currently trying to (recursively) make -----     - a list of dynamic values (Values)+--     - a list of already created values (Values)+--+--  3 subtleties:+--    1. if there are specialized values we need to find the most specialized for+--      the current context, that is the one having its "targetType" the "lowest" in the+--      values graph+--+--    2. if an already created value has the right type but if it is a specialization+--       and the type we are looking for is not in the specialization context+--       then we cannot use that value, we need to recreate a brand new one+--+--    3. if an already created value has the right type and is not specialized+--       but if there is an incompatible specialization for one of its dependencies+--       then it cannot be used+-- findValue ::      SomeTypeRep   -> Context   -> Specializations   -> Values   -> Maybe Value--- no specializations or values to choose from-findValue _ _ (Specializations []) (Values []) = Nothing+findValue target context specializations values =+  let -- 1. first try to find the target value in the list of specializations+      -- those all are all the specializations which make sense in this context+      applicableSpecializations = (specializations `applicableTo` context)+      bestSpecializedValue = findBestSpecializedValue target context applicableSpecializations --- recurse on the specializations first-findValue target (Context context) (Specializations ((t, v) : rest)) values =-  -- if there is an override which value matches the current target-  -- and if that override is in the current context then return the value-  if target == valueDynTypeRep v && t `elem` context then-    Just v-  else-    findValue target (Context context) (Specializations rest) values+      compatibleValue = findCompatibleCreatedValue target specializations values --- otherwise recurse on the list of constructors until a value--- with the target type is found-findValue target context specializations (Values (v : rest)) =-  if valueDynTypeRep v == target then-    Just v-  else-    findValue target context specializations (Values rest)+  in bestSpecializedValue <|> compatibleValue++-- | Among all the applicable specializations take the most specific one+--   if there exists any+findBestSpecializedValue :: SomeTypeRep -> Context -> Specializations -> Maybe Value+findBestSpecializedValue target context (Specializations sp) =+  let -- the candidates must have the required type+      specializationCandidates = filter (\s -> target == specializationTargetType s) sp+      -- the best specialization is the one having its last context type the deepest in the current context+      bestSpecializations = sortOn (specializedContext context) specializationCandidates+      bestSpecializedValue = head bestSpecializations++  in  createValueFromSpecialization context <$> bestSpecializedValue++-- | Among all the created values, take a compatible one+--+--    - if that value is a specialized value or has specialized+--      dependencies it must be compatible with the current context+findCompatibleCreatedValue :: SomeTypeRep -> Specializations -> Values -> Maybe Value+findCompatibleCreatedValue target specializations (Values vs) =+  let isApplicableValue value = valueDynTypeRep value == target+      isNotSpecializedForAnotherContext value =+        not (hasSpecializedDependencies specializations value) &&+        not (isInSpecializationContext target value)++      applicableValues = filter ((&&) <$> isApplicableValue <*> isNotSpecializedForAnotherContext) vs++  in  head applicableValues  -- | Find a constructor function returning a target type --   from a list of constructors
src/Data/Registry/Internal/Stack.hs view
@@ -8,41 +8,53 @@ -} module Data.Registry.Internal.Stack where -import           Data.Registry.Internal.Operations+import           Data.Registry.Internal.Statistics import           Data.Registry.Internal.Types import           Protolude  -- | Monadic stack for the resolution algorithm-type Stack a = StateT (Values, Operations) (Either Text) a+type Stack a = StateT Statistics (Either Text) a  -- | Return a value from the Stack if possible-runStack :: Stack a -> Values -> Either Text a-runStack sa vs = evalStateT sa (vs, [])+runStack :: Stack a -> Either Text a+runStack = runStackWithValues mempty +-- | Return a value from the Stack if possible+runStackWithValues :: Values -> Stack a -> Either Text a+runStackWithValues vs sa = evalStateT sa (initStatistics vs)++execStack :: Stack a -> Either Text Values+execStack = execStackWithValues mempty+ -- | Return the state of the stack after executing the action --   This returns the list of built values-execStack :: Stack a -> Values -> Either Text Values-execStack sa vs = fst <$> execStateT sa (vs, [])+execStackWithValues :: Values -> Stack a -> Either Text Values+execStackWithValues vs sa = values <$> execStateT sa (initStatistics vs)  -- | Return the list of applied functions after resolution-evalStack :: Stack a -> Values -> Either Text Operations-evalStack sa vs = snd <$> execStateT sa (vs, [])+evalStack :: Stack a -> Either Text Statistics+evalStack = evalStackWithValues mempty +evalStackWithValues :: Values -> Stack a -> Either Text Statistics+evalStackWithValues vs sa = execStateT sa (initStatistics vs)+ -- | Get the current list of values getValues :: Stack Values-getValues = fst <$> get+getValues = values <$> get  -- | Get the current list of operations-getOperation :: Stack Operations-getOperation = snd <$> get+getOperations :: Stack Operations+getOperations = operations <$> get  -- | Modify the current list of values modifyValues :: (Values -> Values) -> Stack ()-modifyValues f = modify (\(vs, ops) -> (f vs, ops))+modifyValues f = modifyStatistics (\s -> s { values = f (values s) }) --- | Get the current list of values modifyOperations :: (Operations -> Operations) -> Stack ()-modifyOperations f = modify (\(vs, ops) -> (vs, f ops))+modifyOperations f = modifyStatistics (\s -> s { operations = f (operations s) })++modifyStatistics :: (Statistics -> Statistics) -> Stack ()+modifyStatistics = modify  -- | Store a function application in the list of operations functionApplied :: Value -> [Value] -> Stack ()
+ src/Data/Registry/Internal/Statistics.hs view
@@ -0,0 +1,71 @@+{-+  This module provides a set of statistics over the execution+  of the registry. This allows to get better insights over the execution+  or test that the registry is well configured+-}+module Data.Registry.Internal.Statistics where++import           Data.Registry.Internal.Types+import           Protolude++-- * DATA TYPES++-- | This datatype records:+--    - the created values+--    - the applied functions+--    - the specializations used to create values+data Statistics = Statistics {+  operations :: Operations+, values     :: Values+} deriving (Show)++instance Semigroup Statistics where+  Statistics ops1 vs1 <> Statistics ops2 vs2 =+    Statistics (ops1 <> ops2) (vs1 <> vs2)++instance Monoid Statistics where+  mempty = Statistics mempty mempty+  mappend = (<>)++-- | A list of function applications created+--   when creating a value out of the Registry+type Operations = [AppliedFunction]++-- | List of distinct paths from the root of the value graph to a leaf+type Paths = [[Value]]++-- | A function application with an output value and a list of input values+data AppliedFunction = AppliedFunction {+  _outputValue :: Value+, _inputValues ::[Value]+} deriving (Show)++initStatistics :: Values -> Statistics+initStatistics vs = mempty { values = vs }++-- | Return the specializations used during the creation of values+usedSpecializations :: Statistics -> [Specialization]+usedSpecializations stats =+  case values stats of+    Values [] -> []+    Values (v : vs) ->+      case usedSpecialization v of+        Just s  -> s : usedSpecializations stats { values = Values vs }+        Nothing -> usedSpecializations stats { values = Values vs }++-- | Return the list of distinct paths from the root of a value graph to leaves+--   of that graph.+--   This can be used to check if a given value was indeed used according to a given+--   specialization+allValuesPaths :: Statistics -> Paths+allValuesPaths stats = do+  v <- unValues $ values stats+  valuePaths v++-- | Return all the paths from a given value to all its dependencies+valuePaths :: Value -> Paths+valuePaths v@(CreatedValue _ _ _ _ (Dependencies ds)) = do+  d <- ds+  (v :) <$> valuePaths d++valuePaths _ = []
src/Data/Registry/Internal/Types.hs view
@@ -1,39 +1,52 @@ {-# LANGUAGE GeneralizedNewtypeDeriving #-}- {- |   List of types used inside the Registry -} module Data.Registry.Internal.Types where  import           Data.Dynamic+import           Data.Hashable+import           Data.List                         (elemIndex, intersect)+import           Data.List.NonEmpty+import           Data.List.NonEmpty                as NonEmpty (head, last) import           Data.Registry.Internal.Reflection-import           Data.Text                         as T+import           Data.Text                         as T hiding (last) import           Prelude                           (show) import           Protolude                         as P hiding (show) import qualified Protolude                         as P import           Type.Reflection + -- | A 'Value' is the 'Dynamic' representation of a Haskell value + its description --   It is either provided by the user of the Registry or created as part of the --   resolution algorithm+--   If a `Context` is present for a a created value this means that this value+--   has been written as the result of a specialization. The first type of the+--   list of types in the context is the types under which the specialization must+--   apply and the other types are "parents" of the current value in the value+--   graph data Value =-    CreatedValue  Dynamic ValueDescription+    CreatedValue  Dynamic ValueDescription (Maybe Context) (Maybe Specialization) Dependencies   | ProvidedValue Dynamic ValueDescription   deriving (Show) --- | Return the dynamic part of a value-getValueDynamic :: Value -> Dynamic-getValueDynamic (CreatedValue d _)  = d-getValueDynamic (ProvidedValue d _) = d+instance Hashable Value where+  hash value = hash (valDescription value)+  hashWithSalt n value = hashWithSalt n (valDescription value) +-- | This registers the specific context in which a valu -- | Description of a value. It might just have --   a description for its type when it is a value --   created by the resolution algorithm data ValueDescription = ValueDescription {     _valueType  :: Text   , _valueValue :: Maybe Text-  } deriving (Eq, Show)+ } deriving (Eq, Show) +instance Hashable ValueDescription where+  hash (ValueDescription d v) = hash (d, v)+  hashWithSalt n (ValueDescription d v) = hashWithSalt n (d, v)+ -- | Describe a value with its type and actual content describeValue :: (Typeable a, Show a) => a -> ValueDescription describeValue a = ValueDescription (showFullValueType a) (Just . toS $ show a)@@ -46,10 +59,18 @@ showValue :: Value -> Text showValue = valDescriptionToText . valDescription --- | Create a Value from a Haskell value, with its 'Show' description-createValue :: (Show a, Typeable a) => a -> Value-createValue a = ProvidedValue (toDyn a) (describeValue a)+-- | Create a Value from a Haskell value, using its Show instance for its description+createValue :: (Typeable a, Show a) => a -> Value+createValue a = makeProvidedValue (toDyn a) (describeValue a) +-- | Make a ProvidedValue+makeProvidedValue :: Dynamic -> ValueDescription -> Value+makeProvidedValue = ProvidedValue++-- | make a CreatedValue in no particular context+makeCreatedValue :: Dynamic -> ValueDescription -> Dependencies -> Value+makeCreatedValue d desc = CreatedValue d desc Nothing Nothing+ -- | Create a Value from a Haskell value, with only its 'Typeable' description createTypeableValue :: Typeable a => a -> Value createTypeableValue a = ProvidedValue (toDyn a) (describeTypeableValue a)@@ -60,25 +81,56 @@  -- | Type representation of a 'Value' valueDynTypeRep :: Value -> SomeTypeRep-valueDynTypeRep (CreatedValue  d _) = dynTypeRep d-valueDynTypeRep (ProvidedValue d _) = dynTypeRep d+valueDynTypeRep = dynTypeRep . valueDyn  -- | Dynamic representation of a 'Value' valueDyn :: Value -> Dynamic-valueDyn (CreatedValue  d _) = d-valueDyn (ProvidedValue d _) = d+valueDyn (CreatedValue  d _ _ _ _) = d+valueDyn (ProvidedValue d _)     = d  -- | The description for a 'Value' valDescription :: Value -> ValueDescription-valDescription (CreatedValue  _ d) = d-valDescription (ProvidedValue _ d) = d+valDescription (CreatedValue  _ d _ _ _ ) = d+valDescription (ProvidedValue _ d)      = d +-- | The dependencies for a 'Value'+valDependencies :: Value -> Dependencies+valDependencies (CreatedValue  _ _ _ _ ds) = ds+valDependencies (ProvidedValue _ _)      = mempty+ -- | A ValueDescription as 'Text'. If the actual content of the 'Value' --   is provided display the type first then the content valDescriptionToText :: ValueDescription -> Text valDescriptionToText (ValueDescription t Nothing)  = t valDescriptionToText (ValueDescription t (Just v)) = t <> ": " <> v +-- | Return the creation context for a given value when it was created+--   as the result of a "specialization"+specializationContext :: Value -> Maybe Context+specializationContext (CreatedValue _ _ context _ _) = context+specializationContext _                            = Nothing++-- | Return the specialization used to create a specific values+usedSpecialization :: Value -> Maybe Specialization+usedSpecialization (CreatedValue _ _ _ specialization _) = specialization+usedSpecialization _                                     = Nothing++-- | Return True if a type is part of the specialization context of a Value+isInSpecializationContext :: SomeTypeRep -> Value -> Bool+isInSpecializationContext target value =+  case specializationContext value of+    Just (Context cs) -> target `elem` cs+    Nothing           -> False++-- | Return True if a value has transitives dependencies which are+--   specialized values+hasSpecializedDependencies :: Specializations -> Value -> Bool+hasSpecializedDependencies (Specializations ss) v =+  let DependenciesTypes ds = dependenciesTypes $ valDependencies v+      targetTypes = specializationTargetType <$> ss++  in  not . P.null $ targetTypes `intersect` ds+ -- | A Function is the 'Dynamic' representation of a Haskell function + its description data Function = Function Dynamic FunctionDescription deriving (Show) @@ -147,7 +199,7 @@  -- | List of values available which can be used as parameters to --   constructors for building other values-newtype Values = Values [Value] deriving (Show, Semigroup, Monoid)+newtype Values = Values { unValues :: [Value] } deriving (Show, Semigroup, Monoid)  -- | Display a list of values describeValues :: Values -> Text@@ -162,12 +214,125 @@ addValue v (Values vs) = Values (v : vs)  -- | The types of values that we are trying to build at a given moment---   of the resolution algorithm-newtype Context = Context { _context :: [SomeTypeRep] } deriving (Show, Semigroup, Monoid)+--   of the resolution algorithm.+--   IMPORTANT: this is a *stack*, the deepest elements in the value+--   graph are first in the list+newtype Context = Context {+  _contextStack :: [SomeTypeRep]+} deriving (Eq, Hashable, Show, Semigroup, Monoid) +-- | The values that a value depends on+newtype Dependencies = Dependencies {+  unDependencies :: [Value]+} deriving (Show, Hashable, Semigroup, Monoid)++-- | The values types that a value depends on+newtype DependenciesTypes = DependenciesTypes {+  unDependenciesTypes :: [SomeTypeRep]+} deriving (Eq, Show, Semigroup, Monoid)++dependenciesTypes :: Dependencies -> DependenciesTypes+dependenciesTypes (Dependencies ds) = DependenciesTypes (valueDynTypeRep <$> ds)++-- | The dependencies of a value + the value itself+dependenciesOn :: Value -> Dependencies+dependenciesOn value = Dependencies $+  value : (unDependencies . valDependencies $ value)+ -- | Specification of values which become available for --   construction when a corresponding type comes in context-newtype Specializations = Specializations [(SomeTypeRep, Value)] deriving (Show, Semigroup, Monoid)+newtype Specializations = Specializations {+  unSpecializations :: [Specialization]+} deriving (Show, Semigroup, Monoid)++-- | A specialization is defined by+--   a path of types, from top to bottom in the+--    value graph and target value, which is the+--   value to use when we need a value on that type+--   on that path.+--   For example:+--      specializationPath = [App, PaymentEngine, TransactionRepository]+--      specializationValue = DatabaseConfig "localhost" 5432+--   This means that need to use this `DatabaseConfig` whenever+--   trying to find inputs needed to create a TransactionRepository+--   if that repository is necessary to create a PaymentEngine, itself+--   involved in the creation of the App+data Specialization = Specialization {+  _specializationPath  :: NonEmpty SomeTypeRep+, _specializationValue :: Value+} deriving (Show)++-- | First type of a specialization+specializationStart :: Specialization -> SomeTypeRep+specializationStart = NonEmpty.head . _specializationPath++-- | Last type of a specialization+specializationEnd :: Specialization -> SomeTypeRep+specializationEnd = NonEmpty.last . _specializationPath++-- | Return the type of the replaced value in a specialization+specializationTargetType :: Specialization -> SomeTypeRep+specializationTargetType = valueDynTypeRep . _specializationValue++-- | A specialization is applicable to a context if all its types+--   are part of that context, in the right order+isContextApplicable :: Context -> Specialization -> Bool+isContextApplicable (Context contextPath) (Specialization specializationPath _)  =+  P.all (`elem` contextPath) specializationPath++-- | Return the specifications valid in a given context+applicableTo :: Specializations -> Context -> Specializations+applicableTo (Specializations ss) context =+  Specializations (P.filter (isContextApplicable context) ss)++-- | The depth of a specialization in a context is the+--   the index of the 'deepest' type of that specialization+--   in the stack of types of that context+--   is the one having its "deepest" type (in the value graph)+--     the "deepest" in the current context+--   If there is a tie we take the "highest" highest type of each+specializedContext :: Context -> Specialization -> SpecializedContext+specializedContext (Context cs) specialization =+  SpecializedContext+    (specializationStart specialization `elemIndex` cs)+    (specializationEnd   specialization `elemIndex` cs)++-- | For a given context this represents the position of a specialization path+--   in that context. startRange is the index of the start type of the specialization+--   endRange is the index of the last type.+data SpecializedContext = SpecializedContext {+  _startRange :: Maybe Int+, _endRange   :: Maybe Int+} deriving (Eq, Show)++-- | A specialization range is preferrable to another one if its types+--   are more specific (or "deepest" in the value graph) than the other+--   If a path is limited to just one type then a path ending with the same+--   type but specifying other types will take precedence+--   See TypesSpec for some concrete examples.+instance Ord SpecializedContext where+  SpecializedContext s1 e1 <= SpecializedContext s2 e2+    | e1 /= s1 && e2 /= s2 = e1 <= e2 || (e1 == e2 && s1 <= s2)+    | e1 == s1 && e2 /= s2 = e1 < e2+    | otherwise            = e1 <= e2++-- | Restrict a given context to the types of a specialization+-- specializedContext :: Context -> Specialization -> Context+-- specializedContext (Context cs) specialization = Context $+--   P.dropWhile    (/= specializationEnd specialization) .+--   dropWhileEnd (/= specializationStart specialization) $ cs++-- | In a given context, create a value as specified by a specialization+--   the full context is necessary since the specificationPath is+--   only a subpath of a given creation context+createValueFromSpecialization :: Context -> Specialization -> Value+createValueFromSpecialization context specialization@(Specialization _ (ProvidedValue d desc)) =+  -- the creation context for that value+  CreatedValue d desc (Just context) (Just specialization) mempty++-- this is not supposed to happen since specialization are always+-- using ProvidedValues+createValueFromSpecialization _ v = _specializationValue v  -- | Display a list of specializations for the Registry, just showing the --   context (a type) in which a value must be selected
src/Data/Registry/Make.hs view
@@ -69,7 +69,7 @@       --  use the makeUntyped function to create an element of the target type from a list of values and functions       --  the list of values is kept as some State so that newly created values can be added to the current state       case-        flip runStack values+        runStackWithValues values           (makeUntyped targetType (Context [targetType]) functions specializations modifiers)        of
src/Data/Registry/RIO.hs view
@@ -1,7 +1,8 @@-{-# LANGUAGE DeriveFunctor         #-}-{-# LANGUAGE IncoherentInstances   #-}-{-# LANGUAGE MonoLocalBinds        #-}-{-# LANGUAGE UndecidableInstances  #-}+{-# LANGUAGE DataKinds            #-}+{-# LANGUAGE DeriveFunctor        #-}+{-# LANGUAGE IncoherentInstances  #-}+{-# LANGUAGE MonoLocalBinds       #-}+{-# LANGUAGE UndecidableInstances #-} {- |    RIO is equivalent to @ResourceT (WriterT Warmup IO)@@@ -18,11 +19,13 @@ import           Control.Monad.Trans.Resource import qualified Control.Monad.Trans.Resource as Resource (allocate) +import           Control.Applicative+import           Data.Functor.Alt import           Data.Registry.Make import           Data.Registry.Registry import           Data.Registry.Solver import           Data.Registry.Warmup-import           Protolude+import           Protolude                    hiding (Alt, try)  -- | Data type encapsulating resource finalizers newtype Stop = Stop InternalState@@ -39,7 +42,7 @@  instance Applicative RIO where   pure a =-    RIO(const (pure (a, mempty)))+    RIO (const (pure (a, mempty)))    RIO fab <*> RIO fa =     RIO $ \s ->@@ -68,12 +71,25 @@ instance MonadResource RIO where   liftResourceT action = RIO $ \(Stop s) -> liftIO ((, mempty) <$> runInternalState action s) +-- We cannot piggy-back on the IO Alternative instance+-- because it only catches IOErrors+instance Alternative RIO where+  empty = RIO (const empty)+  (RIO runA) <|> (RIO runB) = RIO $ \s -> do+    res <- try (runA s)+    case res of+      Left (_::SomeException) -> runB s+      Right r                 -> pure r++instance Alt RIO where+  (<!>) = (<|>)+ -- * For production  -- | Use a RIO value and make sure that resources are closed --   Only run the action if the warmup is successful-withRIO :: RIO a -> (a -> IO ()) -> IO Result-withRIO rio f = runResourceT $ withInternalState $ \is ->+withRIO :: (MonadIO m) => RIO a -> (a -> IO ()) -> m Result+withRIO rio f = liftIO $ runResourceT $ withInternalState $ \is ->   do  (a, warmup) <- runRIO rio (Stop is)       result      <- liftIO $ runWarmup warmup       if isSuccess result then f a else pure ()@@ -84,55 +100,76 @@ -- --   The passed function 'f' is used to decide whether to continue or --   not depending on the Result-withRegistry :: forall a b ins out . (Typeable a, Contains (RIO a) out, Solvable ins out) =>+--+--   We also make sure that all effects are memoized by calling `memoizeAll` on the Registry here!+withRegistry :: forall a b ins out m . (Typeable a, Contains (RIO a) out, Solvable ins out, MonadIO m, MemoizedActions out) =>      Registry ins out   -> (Result -> a -> IO b)-  -> IO b-withRegistry registry f = runResourceT $ do+  -> m b+withRegistry registry f = liftIO $ 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 . (Typeable a, Contains (RIO a) out, Solvable ins out) => Registry ins out -> ResourceT IO (a, Warmup)-runRegistryT registry = withInternalState $ \is ->-  runRIO (make @(RIO a) registry) (Stop is)+runRegistryT :: forall a ins out m . (Typeable a, Contains (RIO a) out, Solvable ins out, MonadIO m, MemoizedActions out)+  => Registry ins out+  -> ResourceT m (a, Warmup)+runRegistryT registry = withInternalState $ \is -> do+  r <- liftIO $ memoizeAll @RIO registry+  liftIO $ runRIO (make @(RIO a) r) (Stop is)  -- * For testing  -- | Use a RIO value and make sure that resources are closed --   Don't run the warmup-withNoWarmupRIO :: RIO a -> (a -> IO b) -> IO b-withNoWarmupRIO rio f =+withNoWarmupRIO :: (MonadIO m) => RIO a -> (a -> IO b) -> m b+withNoWarmupRIO rio f = liftIO $   runResourceT $ withInternalState $ \is ->   f . fst =<< runRIO rio (Stop is) +-- | Use a RIO value and make sure that resources are closed+--   Run the warmup but ignore the result+withRIOIgnoreWarmupResult :: (MonadIO m) => RIO a -> (a -> IO b) -> m b+withRIOIgnoreWarmupResult = withRIOAndWarmupResult (const $ pure ())++-- | Use a RIO value and make sure that resources are closed+--   Run a unit function with the warmup result (print or throw exception)+withRIOAndWarmupResult :: (MonadIO m) => (Result -> IO ()) -> RIO a -> (a -> IO b) -> m b+withRIOAndWarmupResult withResult rio f = liftIO $+  runResourceT $ withInternalState $ \is -> do+    (a, warmup) <- runRIO rio (Stop is)+    warmupResult <- liftIO $ runWarmup warmup+    withResult warmupResult+    liftIO (f a)+ -- | 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 . (Typeable a, Contains (RIO a) out, Solvable ins out) => Registry ins out -> IO (a, Warmup, Stop)-executeRegistry registry = do+executeRegistry :: forall a ins out m . (Typeable a, Contains (RIO a) out, Solvable ins out, MonadIO m) => Registry ins out -> m (a, Warmup, Stop)+executeRegistry registry = liftIO $ do   is <- liftIO createInternalState   (a, w) <- runRIO (make @(RIO 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 . (Typeable a, Contains (RIO a) out) => Registry ins out -> IO a+unsafeRun :: forall a ins out m . (Typeable a, Contains (RIO a) out, MonadIO m) => Registry ins out -> m a unsafeRun = unsafeRunDynamic  -- | Instantiate the component but don't execute the warmup (it may take time) and lose a way to cleanu up resources --   Don't even check that a component can be built out of the registry-unsafeRunDynamic :: forall a ins out . (Typeable a) => Registry ins out -> IO a-unsafeRunDynamic registry = fst <$> unsafeRunDynamicWithStop registry+unsafeRunDynamic :: forall a ins out m . (Typeable a, MonadIO m) => Registry ins out -> m a+unsafeRunDynamic registry = liftIO $ fst <$> unsafeRunDynamicWithStop registry  -- | Same as 'unsafeRun' but keep the 'Stop' value to be able to clean resources later-unsafeRunWithStop :: forall a ins out . (Typeable a, Contains (RIO a) out) => Registry ins out -> IO (a, Stop)+unsafeRunWithStop :: forall a ins out m . (Typeable a, Contains (RIO a) out, MonadIO m) => Registry ins out -> m (a, Stop) unsafeRunWithStop = unsafeRunDynamicWithStop -unsafeRunDynamicWithStop :: forall a ins out . (Typeable a) => Registry ins out -> IO (a, Stop)-unsafeRunDynamicWithStop registry = do+unsafeRunDynamicWithStop :: forall a ins out m . (Typeable a, MonadIO m) => Registry ins out -> m (a, Stop)+unsafeRunDynamicWithStop registry = liftIO $ do   is <- createInternalState   (a, _) <- runRIO (makeUnsafe @(RIO a) registry) (Stop is)   pure (a, Stop is)
src/Data/Registry/Registry.hs view
@@ -141,6 +141,12 @@   -> Registry ins out specialize = specializeUnsafe @a @b @ins @out +specializePath :: forall path b ins out . (PathToTypeReps path, IsSubset path out, Typeable b)+  => b+  -> Registry ins out+  -> Registry ins out+specializePath = specializePathUnsafe @path @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)@@ -149,12 +155,24 @@   -> Registry ins out specializeVal = specializeUnsafeVal @a @b @ins @out +specializePathVal :: forall path b ins out . (PathToTypeReps path, IsSubset path out, Typeable b, Show b)+  => b+  -> Registry ins out+  -> Registry ins out+specializePathVal = specializePathUnsafeVal @path @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 +specializePathValTo :: forall m path b ins out . (Applicative m, PathToTypeReps path, IsSubset path out, Typeable (m b), Typeable b, Show b)+  => b+  -> Registry ins out+  -> Registry ins out+specializePathValTo = specializePathUnsafeValTo @m @path @b @ins @out+ -- | For a given type `a` being currently built --   when a value of type `b` is required pass a specific --   value@@ -165,9 +183,19 @@ specializeUnsafe b (Registry values functions (Specializations c) modifiers) = Registry   values   functions-  (Specializations ((someTypeRep (Proxy :: Proxy a), createTypeableValue b) : c))+  (Specializations (Specialization (pure $ someTypeRep (Proxy :: Proxy a)) (createTypeableValue b) : c))   modifiers +specializePathUnsafe :: forall path b ins out . (PathToTypeReps path, Typeable b)+  => b+  -> Registry ins out+  -> Registry ins out+specializePathUnsafe b (Registry values functions (Specializations c) modifiers) = Registry+  values+  functions+  (Specializations (Specialization (someTypeReps (Proxy :: Proxy path)) (createTypeableValue b) : c))+  modifiers+ specializeUnsafeVal :: forall a b ins out . (Typeable a, Contains a out, Typeable b, Show b)   => b   -> Registry ins out@@ -175,9 +203,19 @@ specializeUnsafeVal b (Registry values functions (Specializations c) modifiers) = Registry   values   functions-  (Specializations ((someTypeRep (Proxy :: Proxy a), createValue b) : c))+  (Specializations (Specialization (pure $ someTypeRep (Proxy :: Proxy a)) (createValue b) : c))   modifiers +specializePathUnsafeVal :: forall path b ins out . (PathToTypeReps path, Typeable b, Show b)+  => b+  -> Registry ins out+  -> Registry ins out+specializePathUnsafeVal b (Registry values functions (Specializations c) modifiers) = Registry+  values+  functions+  (Specializations (Specialization (someTypeReps (Proxy :: Proxy path)) (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@@ -185,9 +223,29 @@ specializeUnsafeValTo b (Registry values functions (Specializations c) modifiers) = Registry   values   functions-  (Specializations ((someTypeRep (Proxy :: Proxy a), liftProvidedValue @m b) : c))+  (Specializations (Specialization (pure $ someTypeRep (Proxy :: Proxy a)) (liftProvidedValue @m b) : c))   modifiers +specializePathUnsafeValTo :: forall m path b ins out . (Applicative m, PathToTypeReps path, Typeable (m b), Typeable b, Show b)+  => b+  -> Registry ins out+  -> Registry ins out+specializePathUnsafeValTo b (Registry values functions (Specializations c) modifiers) = Registry+  values+  functions+  (Specializations (Specialization (someTypeReps (Proxy :: Proxy path)) (liftProvidedValue @m b) : c))+  modifiers++-- | Typeclass for extracting type representations out of a list of types+class PathToTypeReps (path :: [*]) where+  someTypeReps :: Proxy path -> NonEmpty SomeTypeRep++instance {-# OVERLAPPING #-} (Typeable a) => PathToTypeReps '[a] where+  someTypeReps = const $ pure (someTypeRep (Proxy :: Proxy a))++instance (Typeable a, PathToTypeReps rest) => PathToTypeReps (a : rest) where+  someTypeReps = const $ someTypeRep (Proxy :: Proxy a) :| toList (someTypeReps (Proxy :: Proxy rest))+ -- | Once a value has been computed allow to modify it before storing it --   This keeps the same registry type tweak :: forall a ins out . (Typeable a, Contains a out)@@ -205,17 +263,66 @@ 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+-- * Memoization++-- | Instantiating components can trigger side-effects+--   The way the resolution algorithm works a component of type `m a` will be+--   re-executed *everytime* it is needed as a given dependency+--   This section adds support for memoizing those actions (component creation + optional warmup)++-- | Return memoized 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)+memoize :: 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+memoize = memoizeUnsafe @m @a @ins @out -singletonUnsafe :: forall m a ins out . (MonadIO m, Typeable a, Typeable (m a))+-- | Memoize an action for a given type but don't check if the value is part of the registry outputs+memoizeUnsafe :: forall m a ins out . (MonadIO m, Typeable a, Typeable (m a))   => Registry ins out   -> IO (Registry ins out)-singletonUnsafe r = do+memoizeUnsafe r = do   cache <- newCache @a   pure $ tweakUnsafe @(m a) (fetch cache) r++-- | Memoize *all* the output actions of a Registry when they are creating effectful components+--   This relies on a helper data structure `MemoizeRegistry` tracking the types already+--   memoized and a typeclass MemoizedActions going through the list of `out` types to process them+--   one by one. Note that a type of the form `a` will not be memoized (only `m a`)+memoizeAll :: forall m ins out . (MonadIO m, MemoizedActions out) => Registry ins out -> IO (Registry ins out)+memoizeAll r = _unMemoizeRegistry <$>+  memoizeActions (startMemoizeRegistry r)++newtype MemoizeRegistry (todo :: [*]) (ins :: [*]) (out :: [*]) = MemoizeRegistry { _unMemoizeRegistry :: Registry ins out }++startMemoizeRegistry :: Registry ins out -> MemoizeRegistry out ins out+startMemoizeRegistry = MemoizeRegistry++makeMemoizeRegistry :: forall todo ins out . Registry ins out -> MemoizeRegistry todo ins out+makeMemoizeRegistry = MemoizeRegistry @todo++class MemoizedActions ls where+  memoizeActions :: MemoizeRegistry ls ins out -> IO (MemoizeRegistry '[] ins out)++instance MemoizedActions '[] where+  memoizeActions = pure++instance {-# OVERLAPPING #-} (MonadIO m, Typeable a, Typeable (m a), MemoizedActions rest) => MemoizedActions (m a : rest) where+  memoizeActions (MemoizeRegistry r) = do+    r' <- memoizeUnsafe @m @a r+    memoizeActions (makeMemoizeRegistry @rest r')++instance (MemoizedActions rest) => MemoizedActions (a : rest) where+  memoizeActions (MemoizeRegistry r) =+    memoizeActions (makeMemoizeRegistry @rest r)++-- * DEPRECATIONS++{-# DEPRECATED singleton "use memoize instead" #-}+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 = memoize @m @a @ins @out++{-# DEPRECATED singletonUnsafe "use memoizeUnsafe instead" #-}+singletonUnsafe :: forall m a ins out . (MonadIO m, Typeable a, Typeable (m a)) => Registry ins out -> IO (Registry ins out)+singletonUnsafe = memoizeUnsafe @m @a @ins @out
+ src/Data/Registry/Statistics.hs view
@@ -0,0 +1,58 @@+{-# LANGUAGE AllowAmbiguousTypes #-}+{-# LANGUAGE MonoLocalBinds      #-}++module Data.Registry.Statistics (+  module S+, makeStatistics+, makeStatisticsFast+, makeStatisticsEither+, makeStatisticsUnsafe+) where++import           Data.Registry.Internal.Make+import           Data.Registry.Internal.Statistics as S+import           Data.Registry.Internal.Stack+import           Data.Registry.Internal.Types+import           Data.Registry.Solver+import           Data.Registry.Registry+import           Prelude                           (error)+import           Protolude+import           Type.Reflection++makeStatistics :: forall a ins out . (Typeable a, Contains a out, Solvable ins out) => Registry ins out -> Statistics+makeStatistics = makeStatisticsUnsafe @a++makeStatisticsFast :: forall a ins out .  (Typeable a, Contains a out) => Registry ins out -> Statistics+makeStatisticsFast = makeStatisticsUnsafe @a++-- | Return `Statistics` as the result of the creation of a value+--   of a given type+makeStatisticsEither :: forall a ins out . (Typeable a) => Registry ins out -> Either Text Statistics+makeStatisticsEither registry =+  let values          = _values registry+      functions       = _functions registry+      specializations = _specializations registry+      modifiers       = _modifiers registry+      targetType      = someTypeRep (Proxy :: Proxy a)+  in+      -- use the makeUntyped function to create an element of the target type from a list of values and functions+      -- the list of values is kept as some State so that newly created values can be added to the current state+      case+        evalStackWithValues values+          (makeUntyped targetType (Context [targetType]) functions specializations modifiers)++      of+        Left e ->+          Left $ "could not create a " <> show targetType <> " out of the registry because " <> e <> "\nThe registry is\n" <>+                 show registry++        other ->+          other++-- | Return `Statistics` as the result of the creation of a value+--   of a given type (and throws an exception if the value cannot be created)+makeStatisticsUnsafe  :: forall a ins out . (Typeable a) => Registry ins out -> Statistics+makeStatisticsUnsafe registry =+  case makeStatisticsEither @a registry of+    Right a -> a+    Left  e -> Prelude.error (toS e)
test/Test/Data/Registry/DotSpec.hs view
@@ -1,59 +1,43 @@-{-# LANGUAGE TemplateHaskell #-}+{-# LANGUAGE TemplateHaskell       #-} {-# OPTIONS_GHC -fno-warn-missing-signatures #-}  module Test.Data.Registry.DotSpec where -import           Data.Registry.Dot import           Data.Registry+import           Data.Text                                  as T import           Protolude-import Data.Text as T+import           Test.Data.Registry.Make.SpecializationSpec import           Test.Tasty.Extensions  test_dot =   prop "a dot graph can be generated from a registry" $ do-    let dot = makeDot @(IO Listener) registry+    let dot = makeDot @App appRegistry++    annotate "the graph does not contain redundant edges"+    annotate "the graph does not contain redundant edges"     unDot dot === T.unlines [-       "strict digraph {"-      ,"  node [shape=record]"-      ,"\"IO Test.Data.Registry.DotSpec.Listener\" -> \"Test.Data.Registry.DotSpec.ListenerConfig\\nListenerConfig nyc\";"-      ,"\"IO Test.Data.Registry.DotSpec.Listener\" -> \"IO Test.Data.Registry.DotSpec.Logging\";"-      ,"}"+        "strict digraph {"+      , "  node [shape=record]"+      , "\"Test.Data.Registry.Make.SpecializationSpec.App\" -> \"Test.Data.Registry.Make.SpecializationSpec.Sql-1\";"+      , "\"Test.Data.Registry.Make.SpecializationSpec.App\" -> \"Test.Data.Registry.Make.SpecializationSpec.TwitterClient\";"+      , "\"Test.Data.Registry.Make.SpecializationSpec.App\" -> \"Test.Data.Registry.Make.SpecializationSpec.Supervisor-1\";"+      , "\"Test.Data.Registry.Make.SpecializationSpec.App\" -> \"Test.Data.Registry.Make.SpecializationSpec.StatsStore\";"+      , "\"Test.Data.Registry.Make.SpecializationSpec.StatsStore\" -> \"Test.Data.Registry.Make.SpecializationSpec.TwitterClient\";"+      , "\"Test.Data.Registry.Make.SpecializationSpec.StatsStore\" -> \"Test.Data.Registry.Make.SpecializationSpec.Sql-2\";"+      , "\"Test.Data.Registry.Make.SpecializationSpec.StatsStore\" -> \"Test.Data.Registry.Make.SpecializationSpec.Supervisor-1\";"+      , "\"Test.Data.Registry.Make.SpecializationSpec.Supervisor-1\" -> \"Test.Data.Registry.Make.SpecializationSpec.SupervisorConfig-1\\nSupervisorConfig default\";"+      , "\"Test.Data.Registry.Make.SpecializationSpec.Sql-2\" -> \"Test.Data.Registry.Make.SpecializationSpec.Supervisor-2\";"+      , "\"Test.Data.Registry.Make.SpecializationSpec.Supervisor-2\" -> \"Test.Data.Registry.Make.SpecializationSpec.SupervisorConfig-2\\nSupervisorConfig for sql under the stats store\";"+      , "\"Test.Data.Registry.Make.SpecializationSpec.TwitterClient\" -> \"Test.Data.Registry.Make.SpecializationSpec.Supervisor-3\";"+      , "\"Test.Data.Registry.Make.SpecializationSpec.Supervisor-3\" -> \"Test.Data.Registry.Make.SpecializationSpec.SupervisorConfig-3\\nSupervisorConfig for the twitter client\";"+      , "\"Test.Data.Registry.Make.SpecializationSpec.Supervisor-1\" -> \"Test.Data.Registry.Make.SpecializationSpec.SupervisorConfig-1\\nSupervisorConfig default\";"+      , "\"Test.Data.Registry.Make.SpecializationSpec.TwitterClient\" -> \"Test.Data.Registry.Make.SpecializationSpec.Supervisor-3\";"+      , "\"Test.Data.Registry.Make.SpecializationSpec.Supervisor-3\" -> \"Test.Data.Registry.Make.SpecializationSpec.SupervisorConfig-3\\nSupervisorConfig for the twitter client\";"+      , "\"Test.Data.Registry.Make.SpecializationSpec.Sql-1\" -> \"Test.Data.Registry.Make.SpecializationSpec.Supervisor-4\";"+      , "\"Test.Data.Registry.Make.SpecializationSpec.Supervisor-4\" -> \"Test.Data.Registry.Make.SpecializationSpec.SupervisorConfig-4\\nSupervisorConfig for sql in general\";"+      , "}"       ] ---- * Helpers---- a registry--config =-     valTo @IO (AuthConfig "auth")-  +: valTo @IO (ListenerConfig "nyc")-  +: end--registry =-     fun       newLogging-  +: funTo @IO newAuth-  +: funAs @IO newListener-  +: config---- A small graph of components--newtype Logging = Logging { info :: Text -> IO () }--newLogging :: IO Logging-newLogging = pure (Logging print)--newtype Auth = Auth { auth :: Text -> IO Bool }-newtype AuthConfig = AuthConfig Text deriving (Eq, Show)--newAuth :: AuthConfig -> Logging -> Auth-newAuth _config _logging = Auth (\t -> print t >> pure True)--newtype Listener = Listener { listen :: Text -> IO () }-newtype ListenerConfig = ListenerConfig Text deriving (Eq, Show)--newListener :: ListenerConfig -> Logging -> IO Listener-newListener _config _logging = pure (Listener print)  ---- tests = $(testGroupGenerator)
test/Test/Data/Registry/Internal/CacheSpec.hs view
@@ -4,7 +4,6 @@ module Test.Data.Registry.Internal.CacheSpec where  import           Control.Concurrent.Async-import           Data.IORef import           Data.Registry.Internal.Cache import           Protolude                    as P import           Test.Tasty.Extensions@@ -12,13 +11,13 @@ test_cache = test "caching an IO action must always return the same value" $ do   cached <- liftIO $ do     -- create an action which will increment an Int everytime it is called-    ref <- newIORef (0 :: Int)-    let action = modifyIORef ref (+1) >> readIORef ref+    ref <- newMVar (0 :: Int)+    let action = modifyMVar_ ref (pure . (+1)) >> readMVar ref     cache <- newCache      -- when the action is cached it will always return the same value     let cachedAction = fetch cache action-    _ <- replicateConcurrently_ 100 cachedAction -- with concurrent accesses+    void $ replicateConcurrently_ 100 cachedAction -- with concurrent accesses     cachedAction    cached === 1
test/Test/Data/Registry/Internal/Gens.hs view
@@ -13,6 +13,7 @@ import           Prelude                           (show) import           Protolude import           Type.Reflection+import           Data.List.NonEmpty  -- Hedgehog generators for the internal types registry =@@ -26,10 +27,12 @@   +: funTo @Gen ProvidedValue   +: funTo @Gen ValueDescription   +: funTo @Gen FunctionDescription+  +: funTo @Gen Specialization+  +: fun   (genNonEmpty @SomeTypeRep)   +: fun   (genList @(SomeTypeRep, Function))-  +: fun   (genList @(SomeTypeRep, Value))+  +: fun   (genList @Specialization)   +: fun   (genPair @SomeTypeRep @Function)-  +: fun   (genPair @SomeTypeRep @Value)+  +: fun   (genPair @(NonEmpty SomeTypeRep) @Value)   +: fun   (genList @Function)   +: fun   (genList @SomeTypeRep)   +: fun   (genList @Value)@@ -65,7 +68,7 @@   pure (value, values)  genSomeTypeRep :: Gen Value -> Gen SomeTypeRep-genSomeTypeRep = fmap (dynTypeRep . getValueDynamic)+genSomeTypeRep = fmap valueDynTypeRep  genDynamic :: Gen Dynamic genDynamic = Gen.element [toDyn (1 :: Int), toDyn (2 :: Int), toDyn ("1" :: Text)]@@ -75,6 +78,14 @@  genList :: forall a . Gen a -> Gen [a] genList = Gen.list (Range.linear 1 3)++genNonEmpty :: forall a . Gen a -> Gen (NonEmpty a)+genNonEmpty genA = do+  ls <- Gen.list (Range.linear 1 3) genA+  case ls of+    -- this case can not happen+    [] -> pure <$> genA+    as -> pure (fromList as)  genMaybe :: forall a . Gen a -> Gen (Maybe a) genMaybe = Gen.maybe
test/Test/Data/Registry/Internal/MakeSpec.hs view
@@ -1,6 +1,4 @@-{-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE TemplateHaskell     #-}-{-# LANGUAGE TypeApplications    #-} {-# OPTIONS_GHC -fno-warn-missing-signatures #-} {-# OPTIONS_GHC -fno-warn-deprecations #-} @@ -25,9 +23,9 @@    -- put one of the input types to build already in the list of   -- types being built-  let context = Context (target : _context context')+  let context = Context (target : _contextStack context') -  let result = runStack (makeInputs [target] context  functions specializations modifiers) values+  let result = runStackWithValues values (makeInputs [target] context  functions specializations modifiers)   case result of     Left e  -> annotateShow e >> "cycle detected!" `T.isPrefixOf` e === True     Right _ -> failure
test/Test/Data/Registry/Internal/RegistrySpec.hs view
@@ -30,7 +30,7 @@   values <- forAll $ gen @Values   let listTypeRep = dynTypeRep . toDyn $ [value]   let context = Context [listTypeRep] -- when trying to build a [Int]-  let specializations = Specializations [(listTypeRep, createValue value)]+  let specializations = Specializations [Specialization (pure listTypeRep) (createValue value)]    (fromValueDyn <$> findValue (valueDynTypeRep (createValue value)) context specializations values) === Just (Just value) @@ -52,7 +52,7 @@   (value, values) <- forAll genValues    let createdValue = createValue value-  let (Right stored) = execStack (storeValue mempty createdValue) values+  let (Right stored) = execStackWithValues values (storeValue mempty createdValue)    let found = findValue (dynTypeRep . toDyn $ value) mempty mempty stored   (fromValueDyn <$> found) === Just (Just value)@@ -63,7 +63,7 @@   let valueType = dynTypeRep . toDyn $ value   let modifiers = Modifiers [(valueType, createFunction (\(i:: Int) -> i + 1))]   let createdValue = createValue value-  let (Right stored) = execStack (storeValue modifiers createdValue) values+  let (Right stored) = execStackWithValues values (storeValue modifiers createdValue)    let found = findValue valueType mempty mempty stored   (fromValueDyn <$> found) === Just (Just (value + 1))@@ -77,7 +77,7 @@        , (valueType, createFunction (\(i:: Int) -> i + 1))        ]   let createdValue = createValue value-  let (Right stored) = execStack (storeValue modifiers createdValue) values+  let (Right stored) = execStackWithValues values (storeValue modifiers createdValue)    let found = findValue valueType mempty mempty stored   (fromValueDyn <$> found) === Just (Just ((value * 2) + 1))
+ test/Test/Data/Registry/Internal/TypesSpec.hs view
@@ -0,0 +1,45 @@+{-# LANGUAGE TemplateHaskell #-}+{-# OPTIONS_GHC -fno-warn-missing-signatures #-}++module Test.Data.Registry.Internal.TypesSpec where++import           Data.List.NonEmpty+import           Data.Registry.Internal.Types+import           Protolude                    as P+import           Test.Tasty.Extensions+import           Type.Reflection++test_specialized_context_order = prop "there are preferrable specializations than other in a given context" $ do+  let c1 = Context [f, e, d, c, b, a]+  let s1 = specializedContext c1 (Specialization (a :| [c]) (createValue A))+  let s2 = specializedContext c1 (Specialization (a :| [e]) (createValue A))+  let s3 = specializedContext c1 (Specialization (c :| [f]) (createValue A))+  let s4 = specializedContext c1 (Specialization (b :| [f]) (createValue A))+  let s5 = specializedContext c1 (Specialization (pure c)   (createValue A))+  let s6 = specializedContext c1 (Specialization (pure f)   (createValue A))++  (s2 < s1) === True+  (s3 < s1) === True+  (s4 < s1) === True+  (s3 < s2) === True+  (s4 < s2) === True+  (s4 < s3) === True+  (s1 < s5) === True+  (s6 < s5) === True++data A = A deriving (Eq, Show)+data B = B deriving (Eq, Show)+data C = C deriving (Eq, Show)+data D = D deriving (Eq, Show)+data E = E deriving (Eq, Show)+data F = F deriving (Eq, Show)++a = someTypeRep $ typeOf A+b = someTypeRep $ typeOf B+c = someTypeRep $ typeOf C+d = someTypeRep $ typeOf D+e = someTypeRep $ typeOf E+f = someTypeRep $ typeOf F++----+tests = $(testGroupGenerator)
− test/Test/Data/Registry/Make.hs
@@ -1,235 +0,0 @@-{-# LANGUAGE DataKinds        #-}-{-# LANGUAGE TemplateHaskell  #-}-{-# OPTIONS_GHC -fno-warn-missing-signatures #-}--{--  This module tests the construction of some simple values-  using a registry--}-module Test.Data.Registry.Make where--import           Data.Registry-import           Data.Text     as T (length)-import           Data.IORef-import           Protolude hiding (C1)-import           Test.Tasty.Extensions-import           System.IO.Memoize----- | Contextual setting of different values for a given type-test_contextual = test "values can use some values depending on some context" $ do-  (c1, c2) <- liftIO $-    do let r =    val (Config 3)-               +: fun newUseConfig1-               +: fun newUseConfig2-               +: end-       let r' = specialize @UseConfig1 (Config 1) $-                specialize @UseConfig2 (Config 2) r-       pure (printConfig1 (make @UseConfig1 r'), printConfig2 (make @UseConfig2 r'))--  c1 === Config 1-  c2 === Config 2--newtype Config = Config Int deriving (Eq, Show)--newtype UseConfig1 = UseConfig1 { printConfig1 :: Config }-newUseConfig1 config = UseConfig1 { printConfig1 = config }--newtype UseConfig2 = UseConfig2 { printConfig2 :: Config }-newUseConfig2 config = UseConfig2 { printConfig2 = config }---- | Modification of stored values-test_tweak = test "created values can be modified prior to being stored" $ do-  c1 <- liftIO $-    do let r =    val (Config 1)-               +: fun newUseConfig1-               +: fun newAppUsingConfig1-               +: end-       let r' = tweak (\(UseConfig1 _) -> UseConfig1 (Config 10)) r-       pure (printConfig (make @AppUsingConfig1 r'))--  c1 === Config 10--newtype AppUsingConfig1 = AppUsingConfig1  { printConfig :: Config }-newAppUsingConfig1 config1 = AppUsingConfig1  { printConfig = printConfig1 config1 }---- | Creation of singletons with memoization-test_singleton = test "effectful values can be made as singletons with System.IO.Memoize" $ do-  (c1, c2) <- liftIO $-    do -- create a counter for the number of instantiations-       counter <- newIORef 0--       newSingOnce <- once (newSing counter)-       let r =    fun (argsTo @IO newC1)-               +: fun (argsTo @IO newC2)-               +: fun (argsTo @IO newSingOnce)-               +: end-       c1 <- make @(IO C1) r-       c2 <- make @(IO C2) r-       pure (c1, c2)--  c1 === C1 (Sing 1)-  c2 === C2 (Sing 1)--test_singleton_proper = test "effectful values can be made as singletons" $ do-  (c1, c2) <- liftIO $-    do -- create a counter for the number of instantiations-       counter <- newIORef 0--       let r =    fun (argsTo @IO newC1)-               +: fun (argsTo @IO newC2)-               +: fun (argsTo @IO (newSing counter))-               +: end-       r' <- singleton @IO @Sing r-       c1 <- make @(IO C1) r'-       c2 <- make @(IO C2) r'-       pure (c1, c2)--  c1 === C1 (Sing 1)-  c2 === C2 (Sing 1)--newtype C1 = C1 Sing deriving (Eq, Show)-newC1 :: Sing -> IO C1-newC1 = pure . C1--newtype C2 = C2 Sing deriving (Eq, Show)-newC2 :: Sing -> IO C2-newC2 = pure . C2--newtype Sing = Sing Int deriving (Eq, Show)-newSing :: IORef Int -> IO Sing-newSing counter = do-  _ <- modifyIORef counter (+1)-  i <- readIORef counter-  pure (Sing i)---- | Effectful creation with lifting-test_lifted = test "functions can be lifted in order to participate in building instances" $ do-  f1 <- liftIO $-    do let r =    fun (argsTo @IO newF1)-               +: valTo @IO (1::Int)-               +: valTo @IO ("hey"::Text)-               +: end-       make @(IO F1) r--  f1 === F1 1 "hey"--data F1 = F1 Int Text deriving (Eq, Show)--newF1 :: Int -> Text -> IO F1-newF1 i t = pure (F1 i t)--test_cycle = test "cycle can be detected" $ do-  -- a registry with 2 functions inverse of each other-  let explosive = makeUnsafe @Text (fun add1 +: fun dda1 +: end)-  r <- liftIO $ try (print explosive)-  case r of-    Left (_ :: SomeException) -> assert True-    Right _ -> assert False---- | No typeclass instance is necessary for a "record of functions" to be a Registry component-data Logging = Logging {-  info  :: Text -> IO ()-, debug :: Text -> IO ()-}--logging = make @Logging (fun Logging { info = print, debug = print } +: end)---- | Simple datatypes which can be used in a registry-newtype Text1 = Text1 Text deriving (Eq, Show)-newtype Text2 = Text2 Text deriving (Eq, Show)-newtype Int1 = Int1 Int deriving (Eq, Show)---- | values and functions-int1 :: Int-int1 = 1--add1 :: Int -> Text-add1 i = show (i + 1)--add2 :: Int -> Text -> Text1-add2 i j = Text1 (show (i+1) <> j)--text1 :: Text-text1 = "text1"--toText2 :: Text1 -> Text2-toText2 (Text1 t) = Text2 t--registry1 :: Registry (Inputs Int :++ '[Int, Int, Text, Text1])-                      '[Output Int, Text, Text1, Text2]-registry1 =-     val int1-  +: fun add1-  +: fun add2-  +: fun toText2-  +: end--countSize :: Text -> Maybe Int-countSize t = Just (T.length t)--m = make @Text $ fun (\(t::Text) -> t) +: end--made1 :: Text-made1 = make @Text registry1--made2 :: Text1-made2 = make @Text1 registry1--made3 :: Text2-made3 = make @Text2 registry1-----countSize1 :: Text -> Int1-countSize1 t = Int1 (T.length t)--registry2 :: Registry (Inputs Int :++ '[Int, Text]) '[Output Int, Text, Int1]-registry2 =-     fun int1-  +: fun add1-  +: fun countSize1-  +: end--made4 :: Int1-made4 = make @Int1 registry2---- | This does *not* compile because Double in not in the---   list of outputs for registry2-{--wrong :: Double-wrong = make @Double registry2--}---- | This does *not* compile because the list of inputs---   in registry2 is not included in the list of outputs-unknown :: Double -> Text1-unknown _ = Text1 "text1"--registry3 :: Registry (Inputs Int :++ '[Double, Int, Text])-                      '[Output Int, Text1, Text, Int1]-registry3 =-     val int1-  +: fun unknown-  +: fun add1-  +: fun countSize1-  +: end---- | This does not compile because we need a double---   to make Text1 and it is not in the list of outputs-{--wrong :: Text1-wrong = make @Text1 registry3--}---- | This version compiles but throws an exception at runtime-dangerous :: Text1-dangerous = makeUnsafe @Text1 registry3---- | This test shows that we can detect a cycle at runtime---- inverse of add1-dda1 :: Text -> Int-dda1 = T.length-------tests = $(testGroupGenerator)
+ test/Test/Data/Registry/Make/MakeSpec.hs view
@@ -0,0 +1,46 @@+{-# LANGUAGE DataKinds       #-}+{-# LANGUAGE TemplateHaskell #-}+{-# OPTIONS_GHC -fno-warn-missing-signatures #-}++module Test.Data.Registry.Make.MakeSpec where++import           Data.Registry+import           Data.Text             as T (length)+import           Protolude+import           Test.Tasty.Extensions++-- | Effectful creation with lifting+test_lifted = test "functions can be lifted in order to participate in building instances" $ do+  f1 <- liftIO $+    do let r =    fun (argsTo @IO newF1)+               +: valTo @IO (1::Int)+               +: valTo @IO ("hey"::Text)+               +: end+       make @(IO F1) r++  f1 === F1 1 "hey"++data F1 = F1 Int Text deriving (Eq, Show)++newF1 :: Int -> Text -> IO F1+newF1 i t = pure (F1 i t)++----++test_cycle = test "cycle can be detected" $ do+  -- a registry with 2 functions inverse of each other+  let explosive = makeUnsafe @Text (fun add1 +: fun dda1 +: end)+  r <- liftIO $ try (print explosive)+  case r of+    Left (_ :: SomeException) -> assert True+    Right _                   -> assert False++add1 :: Int -> Text+add1 i = show (i + 1)++-- inverse of add1 (in terms of type signature)+dda1 :: Text -> Int+dda1 = T.length++----+tests = $(testGroupGenerator)
+ test/Test/Data/Registry/Make/MemoizeSpec.hs view
@@ -0,0 +1,104 @@+{-# LANGUAGE DataKinds        #-}+{-# LANGUAGE TemplateHaskell  #-}+{-# OPTIONS_GHC -fno-warn-missing-signatures #-}++module Test.Data.Registry.Make.MemoizeSpec where++import           Data.Registry+import           Data.IORef+import           Protolude hiding (C1)+import           Test.Tasty.Extensions+import           System.IO.Memoize++-- | Creation of values with memoization+test_memoize = test "effectful values can be memoized with System.IO.Memoize" $ do+  (c1, c2) <- liftIO $+    do -- create a counter for the number of instantiations+       counter <- newIORef 0++       newSingOnce <- once (newSing counter)+       let r =    fun (argsTo @IO newC1)+               +: fun (argsTo @IO newC2)+               +: fun (argsTo @IO newSingOnce)+               +: end+       c1 <- make @(IO C1) r+       c2 <- make @(IO C2) r+       pure (c1, c2)++  c1 === C1 (Sing 1)+  c2 === C2 (Sing 1)++test_memoize_proper = test "effectful values can memoized" $ do+  (c1, c2) <- liftIO $+    do -- create a counter for the number of instantiations+       counter <- newIORef 0++       let r =    fun (argsTo @IO newC1)+               +: fun (argsTo @IO newC2)+               +: fun (argsTo @IO (newSing counter))+               +: end+       r' <- memoize @IO @Sing r+       c1 <- make @(IO C1) r'+       c2 <- make @(IO C2) r'+       pure (c1, c2)++  c1 === C1 (Sing 1)+  c2 === C2 (Sing 1)++newtype C1 = C1 Sing deriving (Eq, Show)+newC1 :: Sing -> IO C1+newC1 = pure . C1++newtype C2 = C2 Sing deriving (Eq, Show)+newC2 :: Sing -> IO C2+newC2 = pure . C2++newtype Sing = Sing Int deriving (Eq, Show)+newSing :: IORef Int -> IO Sing+newSing counter = do+  _ <- modifyIORef counter (+1)+  i <- readIORef counter+  pure (Sing i)++---++test_automatic_memoizeAll_for_with_registry =+  test "withRegistry automatically uses memoizeAll with RIO" $ do+    messagesRef <- liftIO $ newIORef []+    let registry =+            funTo @RIO App+         +: funTo @RIO newA+         +: funTo @RIO newB+         +: fun   (newC messagesRef)+         +: end++    --  just instantiate the app for its effects+    withRegistry @App registry $ \_ _ -> pure ()++    ms <- liftIO $ readIORef messagesRef++    annotate "if memoize works properly, then only one warmup is invoked"+    ms === ["x"]++newtype A = A { doItA :: IO () }+newtype B = B { doItB :: IO () }+newtype C = C { doItC :: IO () }++newA :: C -> A+newA c = A { doItA = doItC c }++newB :: C -> B+newB c = B { doItB = doItC c }++newC :: IORef [Text] -> RIO C+newC messagesRef = do+  let c = C { doItC = pure () }+  warmupWith (createWarmup (modifyIORef messagesRef ("x":) $> Ok ["good"]))+  pure c++data App = App { a :: A, b :: B }++++----+tests = $(testGroupGenerator)
+ test/Test/Data/Registry/Make/SpecializationSpec.hs view
@@ -0,0 +1,200 @@+{-# LANGUAGE DataKinds             #-}+{-# LANGUAGE PartialTypeSignatures #-}+{-# LANGUAGE RecordWildCards       #-}+{-# LANGUAGE TemplateHaskell       #-}+{-# OPTIONS_GHC -fno-warn-partial-type-signatures #-}+{-# OPTIONS_GHC -fno-warn-missing-signatures #-}++module Test.Data.Registry.Make.SpecializationSpec where++import           Data.Registry+import           Protolude             hiding (C1)+import           Test.Tasty.Extensions++-- | Case 1: contextual setting of different values for a given type+test_specialization_1 = test "values can use other values depending on some context" $ do+  (c1, c2) <- liftIO $+    do let r =    val (Config 3)+               +: fun newUseConfig1+               +: fun newUseConfig2+               +: end+       let r' = specialize @UseConfig1 (Config 1) $+                specialize @UseConfig2 (Config 2) r+       pure (printConfig1 (make @UseConfig1 r'), printConfig2 (make @UseConfig2 r'))++  c1 === Config 1+  c2 === Config 2++-- | Case 2: if there are 2 specialization taking effect for 2 different types+--   the one that is the children of the other in the current context wins+test_specialization_2 = test "more specialized context" $ do+  c <- liftIO $+    do let r =    val (Config 3)+               +: fun newUseConfig+               +: fun newClient1+               +: end+       let r' = specialize @Client1 (Config 1) $+                specialize @UseConfig (Config 2) r+       pure $ printClientConfig1 (make @Client1 r')++  annotate "this is the more specialized context"+  c === Config 2++-- | Case 3: this time the specialization must "propagate" to components+--   using the specialized values, note that the `UseConfig` component needs to be+--   duplicated because it is on the path of the specialization+test_specialization_3 = test "specialized values must be kept up to their start context" $ do+  (c1, c2) <- liftIO $+    do let r =    val (Config 3)+               +: fun newUseConfig+               +: fun newClient1+               +: fun newClient2+               +: fun newBase+               +: end+       let r' = specialize @Client1 (Config 1) $+                specialize @Client2 (Config 2) r+       pure $ printBase (make @Base r')++  c1 === Config 1+  c2 === Config 2+++-- we want the following graph+{-+            +----------  Base  ------------++            |                              |+            v                              v+   (client1 :: Client1)          (client2 :: Client2)+            |                              |+            v                              v+   (useConfig1 :: UseConfig) (useConfig2 :: UseConfig)+            |                              |+            v                              v+   (config1 :: Config)           (config2 :: Config)+++-}++newtype Config = Config Int deriving (Eq, Show)++newtype UseConfig1 = UseConfig1 { printConfig1 :: Config }+newUseConfig1 config = UseConfig1 { printConfig1 = config }++newtype UseConfig2 = UseConfig2 { printConfig2 :: Config }+newUseConfig2 config = UseConfig2 { printConfig2 = config }++newtype UseConfig = UseConfig { printConfig :: Config }+newUseConfig config = UseConfig { printConfig = config }++newtype Client1 = Client1 { printClientConfig1 :: Config }+newClient1 useConfig = Client1 { printClientConfig1 = printConfig useConfig }++newtype Client2 = Client2 { printClientConfig2 :: Config }+newClient2 useConfig = Client2 { printClientConfig2 = printConfig useConfig }++newtype Base = Base { printBase :: (Config, Config) }+newBase client1 client2 = Base { printBase = (printClientConfig1 client1, printClientConfig2 client2) }++-- | Case 4: we can specialize values across a given "path" in the graph+test_specialization_4 = test "values can be specialized for a given path" $ do+  (c1, c2, c3) <- liftIO $+    do let r =    valTo @RIO (Config 3)+               +: funTo @RIO newUseConfig+               +: funTo @RIO newClient1+               +: funTo @RIO newClient2+               +: funTo @RIO newBase2+               +: end+       let r' = specializePathValTo @RIO @[RIO Base2, RIO Client1, RIO UseConfig] (Config 1) .+                specializeValTo @RIO @(RIO UseConfig) (Config 2) $ r++       printBase2 <$> (unsafeRun @Base2 r')++  c1 === Config 1+  c2 === Config 2+  c3 === Config 3++data Base2 = Base2 {+  client1   :: Client1+, useConfig :: UseConfig+, config3   :: Config+}+newBase2 = Base2++printBase2 Base2 {..} = (printClientConfig1 client1, printConfig useConfig, config3)++-- we want the following graph+{-+            +----------  Base2  -----------+-----> (config3 :: Config)+            |                              |+            v                              v+   (client1 :: Client1)        (useConfig2 :: UseConfig)+            |                              |+            v                              v+   (useConfig1 :: UseConfig)      (config2 :: Config)+            |+            v+   (config1 :: Config)+-}++-- | Case 5 (taken from a real case)+--   In that case a non-specialized value could be taken for a given+--   instead of being re-created because it has specialized dependencies for+--   a given context+--   For this test, we track how a component, the Supervisor, is being configured+--   depending on which path it belongs+test_specialization_5 = test "values can be specialized for a given path - other case" $ do+  let app = make @App appRegistry++  annotate "the stats store client is well configured"+  let (twitterConfig, statsSqlConfig, statsSupervisorConfig) = statsStoreConfig (statsStore app)+  twitterConfig         === "for the twitter client"+  statsSqlConfig        === "for sql under the stats store"+  statsSupervisorConfig === "default"++  annotate "the app is well configured"+  (app & supervisor & supervisorConfig) === ("default" :: Text)+  (app & sql & sqlConfig)               === ("for sql in general" :: Text)+++appRegistry :: Registry _ _+appRegistry =+  specializeUnsafeVal @Sql (SupervisorConfig "for sql in general") .+  specializePathUnsafeVal @[StatsStore, Sql] (SupervisorConfig "for sql under the stats store") .+  specializeUnsafeVal @TwitterClient (SupervisorConfig "for the twitter client") $+     val (SupervisorConfig "default")+  +: fun newTwitterClient+  +: fun newSupervisor+  +: fun newStatsStore+  +: fun newSql+  +: fun App+  +: end++data App = App {+  sql           :: Sql+, twitterClient :: TwitterClient+, supervisor    :: Supervisor+, statsStore    :: StatsStore+}++newtype Sql = Sql { sqlConfig :: Text }+newtype StatsStore  = StatsStore { statsStoreConfig :: (Text, Text, Text) } -- (twitter, sql, supervisor)+newtype TwitterClient = TwitterClient { twitterConfig :: Text }+newtype Supervisor = Supervisor { supervisorConfig :: Text }+newtype SupervisorConfig = SupervisorConfig Text deriving (Eq, Show)++newSupervisor :: SupervisorConfig -> Supervisor+newSupervisor (SupervisorConfig n) = Supervisor { supervisorConfig = n }++newSql :: Supervisor -> Sql+newSql s = Sql { sqlConfig = supervisorConfig s }++newTwitterClient :: Supervisor -> TwitterClient+newTwitterClient s = TwitterClient { twitterConfig = supervisorConfig s }++newStatsStore :: TwitterClient -> Sql -> Supervisor -> StatsStore+newStatsStore client sql supervisor = StatsStore {+  statsStoreConfig = (twitterConfig client, sqlConfig sql, supervisorConfig supervisor)+}++----+tests = $(testGroupGenerator)
+ test/Test/Data/Registry/Make/TweakingSpec.hs view
@@ -0,0 +1,32 @@+{-# LANGUAGE DataKinds        #-}+{-# LANGUAGE TemplateHaskell  #-}+{-# OPTIONS_GHC -fno-warn-missing-signatures #-}++module Test.Data.Registry.Make.TweakingSpec where++import           Data.Registry+import           Protolude+import           Test.Tasty.Extensions++-- | Modification of stored values+test_tweak = test "created values can be modified prior to being stored" $ do+  c1 <- liftIO $+    do let r =    val (Config 1)+               +: fun newUseConfig1+               +: fun newAppUsingConfig1+               +: end+       let r' = tweak (\(UseConfig1 _) -> UseConfig1 (Config 10)) r+       pure (printAppConfig (make @AppUsingConfig1 r'))++  c1 === Config 10++newtype AppUsingConfig1 = AppUsingConfig1  { printAppConfig :: Config }+newAppUsingConfig1 config1 = AppUsingConfig1  { printAppConfig = printConfig1 config1 }++newtype Config = Config Int deriving (Eq, Show)++newtype UseConfig1 = UseConfig1 { printConfig1 :: Config }+newUseConfig1 config = UseConfig1 { printConfig1 = config }++----+tests = $(testGroupGenerator)
test/Test/Data/Registry/RIOSpec.hs view
@@ -36,4 +36,5 @@   content <- liftIO $ readIORef ref   content === ["start", "use", "close" :: Text] +---- tests = $(testGroupGenerator)
+ test/Test/Data/Registry/SimpleExamples.hs view
@@ -0,0 +1,110 @@+{-# LANGUAGE DataKinds        #-}+{-# OPTIONS_GHC -fno-warn-missing-signatures #-}++{-+  These examples are simply provided to show what must compile+-}+module Test.Data.Registry.SimpleExamples where++import           Data.Registry+import           Data.Text     as T (length)+import           Protolude hiding (C1)+++-- | No typeclass instance is necessary for a "record of functions" to be a Registry component+data Logging = Logging {+  info  :: Text -> IO ()+, debug :: Text -> IO ()+}++logging = make @Logging (fun Logging { info = print, debug = print } +: end)++-- | Simple datatypes which can be used in a registry+newtype Text1 = Text1 Text deriving (Eq, Show)+newtype Text2 = Text2 Text deriving (Eq, Show)+newtype Int1 = Int1 Int deriving (Eq, Show)++-- | values and functions+int1 :: Int+int1 = 1++add1 :: Int -> Text+add1 i = show (i + 1)++add2 :: Int -> Text -> Text1+add2 i j = Text1 (show (i+1) <> j)++text1 :: Text+text1 = "text1"++toText2 :: Text1 -> Text2+toText2 (Text1 t) = Text2 t++registry1 :: Registry (Inputs Int :++ '[Int, Int, Text, Text1])+                      '[Output Int, Text, Text1, Text2]+registry1 =+     val int1+  +: fun add1+  +: fun add2+  +: fun toText2+  +: end++countSize :: Text -> Maybe Int+countSize t = Just (T.length t)++m = make @Text $ fun (\(t::Text) -> t) +: end++made1 :: Text+made1 = make @Text registry1++made2 :: Text1+made2 = make @Text1 registry1++made3 :: Text2+made3 = make @Text2 registry1++--+countSize1 :: Text -> Int1+countSize1 t = Int1 (T.length t)++registry2 :: Registry (Inputs Int :++ '[Int, Text]) '[Output Int, Text, Int1]+registry2 =+     fun int1+  +: fun add1+  +: fun countSize1+  +: end++made4 :: Int1+made4 = make @Int1 registry2++-- | This does *not* compile because Double in not in the+--   list of outputs for registry2+{-+wrong :: Double+wrong = make @Double registry2+-}++-- | This does *not* compile because the list of inputs+--   in registry2 is not included in the list of outputs+unknown :: Double -> Text1+unknown _ = Text1 "text1"++registry3 :: Registry (Inputs Int :++ '[Double, Int, Text])+                      '[Output Int, Text1, Text, Int1]+registry3 =+     val int1+  +: fun unknown+  +: fun add1+  +: fun countSize1+  +: end++-- | This does not compile because we need a double+--   to make Text1 and it is not in the list of outputs+{-+wrong :: Text1+wrong = make @Text1 registry3+-}++-- | This version compiles but throws an exception at runtime+dangerous :: Text1+dangerous = makeUnsafe @Text1 registry3
test/Test/Data/Registry/WarmupSpec.hs view
@@ -6,10 +6,11 @@ module Test.Data.Registry.WarmupSpec where  import           Control.Monad.Catch-import           Prelude                (show)+import           Data.IORef+import           Data.Registry+import           Prelude               (show) import           Protolude import           Test.Tasty.Extensions-import           Data.Registry.Warmup  test_runBoth1 =   prop "all results are collected when running 2 warmup tasks" $ do@@ -23,7 +24,41 @@     r  <- liftIO $ throwM (Error "boom1") `runBoth` throwM (Error "boom2")     messages r === ["boom1", "boom2"] --- * helpers+test_run_side_effects_once =+  test "a component having a warmup must be memoized" $ do+    messagesRef <- liftIO $ newIORef []+    registry <-+       liftIO $ memoizeAll @RIO $+             funTo @RIO App+          +: funTo @RIO newA+          +: funTo @RIO newB+          +: fun   (newC messagesRef)+          +: end++    void $ withRIO (makeUnsafe @(RIO App) registry) $ const (pure ())++    ms <- liftIO $ readIORef messagesRef+    ms === ["x"]++newtype A = A { doItA :: IO () }+newtype B = B { doItB :: IO () }+newtype C = C { doItC :: IO () }++newA :: C -> A+newA c = A { doItA = doItC c }++newB :: C -> B+newB c = B { doItB = doItC c }++newC :: IORef [Text] -> RIO C+newC messagesRef = do+  let c = C { doItC = pure () }+  warmupWith (createWarmup (modifyIORef messagesRef ("x":) $> Ok ["good"]))+  pure c++data App = App { a :: A, b :: B }++-- * HELPERS newtype Error = Error Text  instance Show Error where