packages feed

cauldron 0.4.0.0 → 0.6.0.0

raw patch · 12 files changed

+2433/−1225 lines, 12 filesdep −multicurryabledep −sop-corePVP ok

version bump matches the API change (PVP)

Dependencies removed: multicurryable, sop-core

API changes (from Hackage documentation)

- Cauldron: Packer :: (r -> m (Regs regs bean)) -> Packer (m :: Type -> Type) (regs :: [Type]) bean r
- Cauldron: [Bean] :: forall (m :: Type -> Type) bean. Constructor m bean -> Decos m bean -> Bean m bean
- Cauldron: addInner :: forall (m :: Type -> Type) bean. Constructor m bean -> Decos m bean -> Decos m bean
- Cauldron: addOuter :: forall (m :: Type -> Type) bean. Constructor m bean -> Decos m bean -> Decos m bean
- Cauldron: collapsePrimaryBeans :: DependencyGraph -> DependencyGraph
- Cauldron: data BadBeans
- Cauldron: data Bean (m :: Type -> Type) bean
- Cauldron: data BoiledBeans
- Cauldron: data Decos (m :: Type -> Type) bean
- Cauldron: data Regs (regs :: [Type]) bean
- Cauldron: defaultStepToText :: BeanConstructionStep -> Text
- Cauldron: effect :: Applicative m => Packer m ('[] :: [Type]) bean (m bean)
- Cauldron: effectWith :: forall m (regs :: [Type]) r bean. (Applicative m, All (And (Typeable :: Type -> Constraint) Monoid) regs) => (r -> Regs regs bean) -> Packer m regs bean (m r)
- Cauldron: emptyCauldron :: forall (m :: Type -> Type). Cauldron m
- Cauldron: emptyDecos :: forall (m :: Type -> Type) bean. Decos m bean
- Cauldron: exportToDot :: (BeanConstructionStep -> Text) -> FilePath -> DependencyGraph -> IO ()
- Cauldron: fromConstructors :: forall (m :: Type -> Type) bean. [Constructor m bean] -> Decos m bean
- Cauldron: hoistBean :: (forall x. () => m x -> n x) -> Bean m bean -> Bean n bean
- Cauldron: hoistDecos :: (forall x. () => m x -> n x) -> Decos m bean -> Decos n bean
- Cauldron: instance Data.Functor.Contravariant.Contravariant (Cauldron.Packer m regs bean)
- Cauldron: instance GHC.Internal.Base.Applicative (Cauldron.Args args)
- Cauldron: instance GHC.Internal.Base.Applicative Cauldron.Extractor
- Cauldron: instance GHC.Internal.Base.Functor (Cauldron.Args args)
- Cauldron: instance GHC.Internal.Base.Functor (Cauldron.Regs regs)
- Cauldron: instance GHC.Internal.Base.Functor Cauldron.Extractor
- Cauldron: instance GHC.Internal.Base.Monad (Cauldron.Args args)
- Cauldron: instance GHC.Internal.Base.Monoid (Cauldron.Decos m bean)
- Cauldron: instance GHC.Internal.Base.Semigroup (Cauldron.Decos m bean)
- Cauldron: instance GHC.Internal.IsList.IsList (Cauldron.Decos m bean)
- Cauldron: instance GHC.Internal.Show.Show Cauldron.BadBeans
- Cauldron: makeBean :: forall (m :: Type -> Type) a. Constructor m a -> Bean m a
- Cauldron: newtype Packer (m :: Type -> Type) (regs :: [Type]) bean r
- Cauldron: overDecos :: forall (m :: Type -> Type) bean. (Decos m bean -> Decos m bean) -> Bean m bean -> Bean m bean
- Cauldron: pack :: forall (args :: [Type]) r curried (regs :: [Type]) bean (m :: Type -> Type). (MulticurryableF args r curried (IsFunction curried), All (Typeable :: Type -> Constraint) args, All (And (Typeable :: Type -> Constraint) Monoid) regs) => Packer m regs bean r -> curried -> Constructor m bean
- Cauldron: pack0 :: forall (regs :: [Type]) (m :: Type -> Type) bean r. All (And (Typeable :: Type -> Constraint) Monoid) regs => Packer m regs bean r -> r -> Constructor m bean
- Cauldron: pack1 :: forall arg1 r (m :: Type -> Type) (regs :: [Type]) bean. (Typeable arg1, All (And (Typeable :: Type -> Constraint) Monoid) regs) => Packer m regs bean r -> (arg1 -> r) -> Constructor m bean
- Cauldron: pack2 :: forall arg1 arg2 r (m :: Type -> Type) (regs :: [Type]) bean. (Typeable arg1, Typeable arg2, All (And (Typeable :: Type -> Constraint) Monoid) regs) => Packer m regs bean r -> (arg1 -> arg2 -> r) -> Constructor m bean
- Cauldron: pack3 :: forall arg1 arg2 arg3 r (m :: Type -> Type) (regs :: [Type]) bean. (Typeable arg1, Typeable arg2, Typeable arg3, All (And (Typeable :: Type -> Constraint) Monoid) regs) => Packer m regs bean r -> (arg1 -> arg2 -> arg3 -> r) -> Constructor m bean
- Cauldron: regs0 :: bean -> Regs ('[] :: [Type]) bean
- Cauldron: regs1 :: reg1 -> bean -> Regs '[reg1] bean
- Cauldron: regs2 :: reg1 -> reg2 -> bean -> Regs '[reg1, reg2] bean
- Cauldron: regs3 :: reg1 -> reg2 -> reg3 -> bean -> Regs '[reg1, reg2, reg3] bean
- Cauldron: setConstructor :: forall (m :: Type -> Type) bean. Constructor m bean -> Bean m bean -> Bean m bean
- Cauldron: setDecos :: forall (m :: Type -> Type) bean. Decos m bean -> Bean m bean -> Bean m bean
- Cauldron: type PathToCauldron = [Int]
- Cauldron: value :: forall (m :: Type -> Type) bean. Applicative m => Packer m ('[] :: [Type]) bean bean
- Cauldron: valueWith :: forall (m :: Type -> Type) (regs :: [Type]) r bean. (Applicative m, All (And (Typeable :: Type -> Constraint) Monoid) regs) => (r -> Regs regs bean) -> Packer m regs bean r
+ Cauldron: (<|) :: a -> Seq a -> Seq a
+ Cauldron: (|>) :: Seq a -> a -> Seq a
+ Cauldron: DependencyCycleError :: DependencyCycle -> RecipeError
+ Cauldron: DoubleDutyBeansError :: DoubleDutyBeans -> RecipeError
+ Cauldron: MissingDependenciesError :: MissingDependencies -> RecipeError
+ Cauldron: Recipe :: Constructor m bean -> Seq (Constructor m bean) -> Recipe (m :: Type -> Type) bean
+ Cauldron: [bean] :: Recipe (m :: Type -> Type) bean -> Constructor m bean
+ Cauldron: [decos] :: Recipe (m :: Type -> Type) bean -> Seq (Constructor m bean)
+ Cauldron: allowDepCycles :: forall (m :: Type -> Type). MonadFix m => Fire m
+ Cauldron: class ToRecipe (recipelike :: Type -> Type -> Type -> Type)
+ Cauldron: collapseToPrimaryBeans :: DependencyGraph -> DependencyGraph
+ Cauldron: data Beans
+ Cauldron: data MissingDependencies
+ Cauldron: data Recipe (m :: Type -> Type) bean
+ Cauldron: data RecipeError
+ Cauldron: data SomeRecipe (m :: Type -> Type)
+ Cauldron: defaultStyle :: Maybe RecipeError -> Style BeanConstructionStep Text
+ Cauldron: eff :: (Registrable nested bean, Monad m, HasCallStack) => Args (m nested) -> Constructor m bean
+ Cauldron: eff' :: forall bean m. HasCallStack => Args (m (Regs bean)) -> Constructor m bean
+ Cauldron: eff_ :: forall bean m. (Functor m, HasCallStack) => Args (m bean) -> Constructor m bean
+ Cauldron: empty :: forall (m :: Type -> Type). Cauldron m
+ Cauldron: fromDecoList :: forall (m :: Type -> Type) bean. [Constructor m bean] -> Seq (Constructor m bean)
+ Cauldron: fromRecipeList :: forall (m :: Type -> Type). [SomeRecipe m] -> Cauldron m
+ Cauldron: getConstructorArgs :: Constructor m bean -> Args (m (Regs bean))
+ Cauldron: getConstructorCallStack :: forall (m :: Type -> Type) bean. Constructor m bean -> CallStack
+ Cauldron: getDependencyGraph :: forall (m :: Type -> Type). Cauldron m -> DependencyGraph
+ Cauldron: getRecipeCallStack :: forall (m :: Type -> Type). SomeRecipe m -> CallStack
+ Cauldron: hoistCauldron' :: (forall x. Typeable x => Args (m (Regs x)) -> Args (n (Regs x))) -> (forall x. Typeable x => Int -> Args (m (Regs x)) -> Args (n (Regs x))) -> Cauldron m -> Cauldron n
+ Cauldron: hoistConstructor' :: (Args (m (Regs bean)) -> Args (n (Regs bean))) -> Constructor m bean -> Constructor n bean
+ Cauldron: hoistRecipe :: (forall x. () => m x -> n x) -> Recipe m bean -> Recipe n bean
+ Cauldron: hoistRecipe' :: (Args (m (Regs bean)) -> Args (n (Regs bean))) -> (Int -> Args (m (Regs bean)) -> Args (n (Regs bean))) -> Recipe m bean -> Recipe n bean
+ Cauldron: infixl 5 |>
+ Cauldron: infixr 5 <|
+ Cauldron: instance Cauldron.ToRecipe Cauldron.Constructor
+ Cauldron: instance Cauldron.ToRecipe Cauldron.Recipe
+ Cauldron: instance GHC.Classes.Eq Cauldron.DependencyGraph
+ Cauldron: instance GHC.Classes.Ord Cauldron.DependencyGraph
+ Cauldron: instance GHC.Internal.Base.Monoid Cauldron.DependencyGraph
+ Cauldron: instance GHC.Internal.Base.Semigroup Cauldron.DependencyGraph
+ Cauldron: instance GHC.Internal.Exception.Type.Exception Cauldron.RecipeError
+ Cauldron: instance GHC.Internal.IsList.IsList (Cauldron.Cauldron m)
+ Cauldron: instance GHC.Internal.Show.Show Cauldron.DependencyCycle
+ Cauldron: instance GHC.Internal.Show.Show Cauldron.DependencyGraph
+ Cauldron: instance GHC.Internal.Show.Show Cauldron.DoubleDutyBeans
+ Cauldron: instance GHC.Internal.Show.Show Cauldron.MissingDependencies
+ Cauldron: instance GHC.Internal.Show.Show Cauldron.RecipeError
+ Cauldron: keysSet :: forall (m :: Type -> Type). Cauldron m -> Set TypeRep
+ Cauldron: newtype DependencyCycle
+ Cauldron: newtype DoubleDutyBeans
+ Cauldron: prettyRecipeError :: RecipeError -> String
+ Cauldron: prettyRecipeErrorLines :: RecipeError -> [String]
+ Cauldron: recipe :: forall {recipelike} {m :: Type -> Type} bean. (ToRecipe recipelike, Typeable bean, HasCallStack) => recipelike m bean -> SomeRecipe m
+ Cauldron: restrictKeys :: forall (m :: Type -> Type). Cauldron m -> Set TypeRep -> Cauldron m
+ Cauldron: setVertexName :: (BeanConstructionStep -> Text) -> Style BeanConstructionStep Text -> Style BeanConstructionStep Text
+ Cauldron: toRecipeMap :: forall (m :: Type -> Type). Cauldron m -> Map TypeRep (SomeRecipe m)
+ Cauldron: val :: forall {nested} bean (m :: Type -> Type). (Registrable nested bean, Applicative m, HasCallStack) => Args nested -> Constructor m bean
+ Cauldron: val' :: forall bean (m :: Type -> Type). (Applicative m, HasCallStack) => Args (Regs bean) -> Constructor m bean
+ Cauldron: val_ :: forall bean (m :: Type -> Type). (Applicative m, HasCallStack) => Args bean -> Constructor m bean
+ Cauldron: wire :: Wireable curried tip => curried -> Args tip
+ Cauldron: withRecipe :: forall {m :: Type -> Type} r. (forall bean. Typeable bean => Recipe m bean -> r) -> SomeRecipe m -> r
+ Cauldron: writeAsDot :: Style BeanConstructionStep Text -> FilePath -> DependencyGraph -> IO ()
+ Cauldron.Args: LazilyReadBeanMissing :: TypeRep -> LazilyReadBeanMissing
+ Cauldron.Args: arg :: Typeable a => Args a
+ Cauldron.Args: class Registrable nested tip | nested -> tip
+ Cauldron.Args: class Wireable curried tip | curried -> tip
+ Cauldron.Args: contramapArgs :: (forall t. Typeable t => Maybe t -> Maybe t) -> Args a -> Args a
+ Cauldron.Args: data Args a
+ Cauldron.Args: data Beans
+ Cauldron.Args: data Regs a
+ Cauldron.Args: data SomeMonoidTypeRep
+ Cauldron.Args: foretellReg :: (Typeable a, Monoid a) => Args (a -> Regs ())
+ Cauldron.Args: fromDynList :: [Dynamic] -> Beans
+ Cauldron.Args: getArgsReps :: Args a -> Set TypeRep
+ Cauldron.Args: getRegsReps :: Args a -> Set SomeMonoidTypeRep
+ Cauldron.Args: instance (GHC.Internal.Data.Typeable.Internal.Typeable b, Cauldron.Args.Wireable_ (Cauldron.Args.IsFunction rest) rest tip) => Cauldron.Args.Wireable_ 'Cauldron.Args.NotYetThere (b -> rest) tip
+ Cauldron.Args: instance (GHC.Internal.Data.Typeable.Internal.Typeable b, GHC.Internal.Base.Monoid b, Cauldron.Args.Registrable_ (Cauldron.Args.IsReg rest) rest tip) => Cauldron.Args.Registrable_ 'Cauldron.Args.Tup2 (b, rest) tip
+ Cauldron.Args: instance (GHC.Internal.Data.Typeable.Internal.Typeable b, GHC.Internal.Base.Monoid b, GHC.Internal.Data.Typeable.Internal.Typeable c, GHC.Internal.Base.Monoid c, Cauldron.Args.Registrable_ (Cauldron.Args.IsReg rest) rest tip) => Cauldron.Args.Registrable_ 'Cauldron.Args.Tup3 (b, c, rest) tip
+ Cauldron.Args: instance (GHC.Internal.Data.Typeable.Internal.Typeable b, GHC.Internal.Base.Monoid b, GHC.Internal.Data.Typeable.Internal.Typeable c, GHC.Internal.Base.Monoid c, GHC.Internal.Data.Typeable.Internal.Typeable d, GHC.Internal.Base.Monoid d, Cauldron.Args.Registrable_ (Cauldron.Args.IsReg rest) rest tip) => Cauldron.Args.Registrable_ 'Cauldron.Args.Tup3 (b, c, d, rest) tip
+ Cauldron.Args: instance Cauldron.Args.Registrable_ 'Cauldron.Args.Innermost a a
+ Cauldron.Args: instance Cauldron.Args.Registrable_ (Cauldron.Args.IsReg nested) nested tip => Cauldron.Args.Registrable nested tip
+ Cauldron.Args: instance Cauldron.Args.Wireable_ 'Cauldron.Args.AtTheTip a a
+ Cauldron.Args: instance Cauldron.Args.Wireable_ (Cauldron.Args.IsFunction curried) curried tip => Cauldron.Args.Wireable curried tip
+ Cauldron.Args: instance GHC.Internal.Base.Applicative Cauldron.Args.Args
+ Cauldron.Args: instance GHC.Internal.Base.Applicative Cauldron.Args.Regs
+ Cauldron.Args: instance GHC.Internal.Base.Functor Cauldron.Args.Args
+ Cauldron.Args: instance GHC.Internal.Base.Functor Cauldron.Args.Regs
+ Cauldron.Args: instance GHC.Internal.Base.Monad Cauldron.Args.Regs
+ Cauldron.Args: instance GHC.Internal.Exception.Type.Exception Cauldron.Args.LazilyReadBeanMissing
+ Cauldron.Args: instance GHC.Internal.Show.Show Cauldron.Args.LazilyReadBeanMissing
+ Cauldron.Args: newtype LazilyReadBeanMissing
+ Cauldron.Args: register :: (Registrable nested tip, Functor m) => Args (m nested) -> Args (m (Regs tip))
+ Cauldron.Args: runArgs :: (forall b. Typeable b => Maybe b) -> Args a -> a
+ Cauldron.Args: runRegs :: Set SomeMonoidTypeRep -> Regs a -> (Beans, a)
+ Cauldron.Args: taste :: Typeable bean => Beans -> Maybe bean
+ Cauldron.Args: wire :: Wireable curried tip => curried -> Args tip
+ Cauldron.Beans: [SomeMonoidTypeRep] :: forall a. Monoid a => TypeRep a -> SomeMonoidTypeRep
+ Cauldron.Beans: data Beans
+ Cauldron.Beans: data SomeMonoidTypeRep
+ Cauldron.Beans: delete :: TypeRep -> Beans -> Beans
+ Cauldron.Beans: empty :: Beans
+ Cauldron.Beans: fromDynList :: [Dynamic] -> Beans
+ Cauldron.Beans: insert :: Typeable bean => bean -> Beans -> Beans
+ Cauldron.Beans: instance GHC.Classes.Eq Cauldron.Beans.SomeMonoidTypeRep
+ Cauldron.Beans: instance GHC.Classes.Ord Cauldron.Beans.SomeMonoidTypeRep
+ Cauldron.Beans: instance GHC.Internal.Base.Monoid Cauldron.Beans.Beans
+ Cauldron.Beans: instance GHC.Internal.Base.Semigroup Cauldron.Beans.Beans
+ Cauldron.Beans: instance GHC.Internal.IsList.IsList Cauldron.Beans.Beans
+ Cauldron.Beans: instance GHC.Internal.Show.Show Cauldron.Beans.Beans
+ Cauldron.Beans: instance GHC.Internal.Show.Show Cauldron.Beans.SomeMonoidTypeRep
+ Cauldron.Beans: keysSet :: Beans -> Set TypeRep
+ Cauldron.Beans: restrictKeys :: Beans -> Set TypeRep -> Beans
+ Cauldron.Beans: singleton :: Typeable bean => bean -> Beans
+ Cauldron.Beans: someMonoidTypeRepMempty :: SomeMonoidTypeRep -> Dynamic
+ Cauldron.Beans: taste :: Typeable bean => Beans -> Maybe bean
+ Cauldron.Beans: toDyn :: Typeable a => a -> Dynamic
+ Cauldron.Beans: toDynMap :: Beans -> Map TypeRep Dynamic
+ Cauldron.Beans: unionBeansMonoidally :: Set SomeMonoidTypeRep -> Beans -> Beans -> Beans
- Cauldron: DependencyCycle :: NonEmpty BeanConstructionStep -> BadBeans
+ Cauldron: DependencyCycle :: NonEmpty (BeanConstructionStep, Maybe CallStack) -> DependencyCycle
- Cauldron: DoubleDutyBeans :: Set TypeRep -> BadBeans
+ Cauldron: DoubleDutyBeans :: Map TypeRep (CallStack, CallStack) -> DoubleDutyBeans
- Cauldron: MissingDependencies :: PathToCauldron -> Map TypeRep (Set TypeRep) -> BadBeans
+ Cauldron: MissingDependencies :: CallStack -> TypeRep -> Set TypeRep -> MissingDependencies
- Cauldron: adjust :: forall bean (m :: Type -> Type). Typeable bean => (Bean m bean -> Bean m bean) -> Cauldron m -> Cauldron m
+ Cauldron: adjust :: forall {m :: Type -> Type} bean. Typeable bean => (Recipe m bean -> Recipe m bean) -> Cauldron m -> Cauldron m
- Cauldron: cook :: Monad m => Fire m -> Cauldron m -> Either BadBeans (DependencyGraph, m BoiledBeans)
+ Cauldron: cook :: Monad m => Fire m -> Cauldron m -> Either RecipeError (m Beans)
- Cauldron: cookNonEmpty :: Monad m => NonEmpty (Fire m, Cauldron m) -> Either BadBeans (NonEmpty DependencyGraph, m (NonEmpty BoiledBeans))
+ Cauldron: cookNonEmpty :: Monad m => NonEmpty (Fire m, Cauldron m) -> Either RecipeError (m (NonEmpty Beans))
- Cauldron: cookTree :: Monad m => Tree (Fire m, Cauldron m) -> Either BadBeans (Tree DependencyGraph, m (Tree BoiledBeans))
+ Cauldron: cookTree :: Monad m => Tree (Fire m, Cauldron m) -> Either RecipeError (m (Tree Beans))
- Cauldron: delete :: forall {k} (bean :: k) (m :: Type -> Type). Typeable bean => Cauldron m -> Cauldron m
+ Cauldron: delete :: forall (m :: Type -> Type). TypeRep -> Cauldron m -> Cauldron m
- Cauldron: insert :: forall bean (m :: Type -> Type). Typeable bean => Bean m bean -> Cauldron m -> Cauldron m
+ Cauldron: insert :: forall {recipelike} {m :: Type -> Type} bean. (Typeable bean, ToRecipe recipelike, HasCallStack) => recipelike m bean -> Cauldron m -> Cauldron m
- Cauldron: taste :: Typeable bean => BoiledBeans -> Maybe bean
+ Cauldron: taste :: Typeable bean => Beans -> Maybe bean

Files

CHANGELOG.md view
@@ -1,5 +1,45 @@ # Revision history for cauldron +## 0.6.0.0++* Remove sop-core dependency, incorporate just the needed functionality into the library.++  Also make the internals of the library less dependent on n-ary tuples. Now+  they are more of an added layer for convenience.++* The `cook` family of functions don't return a `DependencyGraph` anymore. Instead, the +  graph can be obtained at any moment using `getDependencyGraph`, even for non-wireable `Cauldron`s.++* `BoiledBeans` is now just `Beans` and has its own module.++* A new `Cauldron.Args` module which defines the `Args` applicative.++* The way of creating `Constructor`s has been overhauled.++  `Packer` and `pack` are gone, along with `value`, `eff` and similar functions. The old `Regs` type is gone.++  To create `Constructor`s, now we should use `val` and `eff` along with `wire`.++* The `Bean` record is now called `Recipe`. There's also a `SomeRecipe` that hides the bean type parameter.+ +* New `ToRecipe` typeclass that helps treating single `Constructor`s as recipes-without-decos.++* The `Decos` type is now just a `Seq` of constructors of the same type.++* New `allowDepCycles` `Fire`.++* Now `DependencyGraph` renders all the dependencies, even those that are ignored during plan construction to allow for dependency cycles.++* New `Monoid` instance for `DependencyGraph`.++* `BadBeans` is now `RecipeError`. It has now an `Exception` instance and a pretty function.++* `exportToDot` is now `writeAsDot` and accepts a `RecipeError` to highlight problematic nodes.++* Now `Constructor`s and `Recipe`s keep track of the `CallStack` of when they were created. This is used+  by errors to print the relevant code locations.+  Because now we have code locations, `PathToCauldron` is no longer useful and has been removed.+ ## 0.4.0.0  * `exportToDot` takes a new parameter to configure how to print the steps. Before, only the TyCon was printed. Now, the full type is printed by default.
+ README.md view
@@ -0,0 +1,162 @@+# cauldron++> Double, double toil and trouble;+>+> Fire burn and caldron bubble.+>+> Fillet of a fenny snake,+>+> In the caldron boil and bake;++**cauldron** is a library for performing dependency injection. It's an alternative to+manually wiring the constructors for the components ("beans") of your+application. ++It expects the bean constructors to conform to a certain shape.++**cauldron** should be used at the [composition root](https://stackoverflow.com/questions/6277771/what-is-a-composition-root-in-the-context-of-dependency-injection). Bean constructors shouldn't be aware that **cauldron** exists, or depend on its types.++**cauldron** relies on dynamic typing and finds wiring errors at runtime, not compilation time.++# Why you should(n't) use this library++To be honest, you probably shouldn't use this library. I have noticed that using+**cauldron** is actually *more* verbose that manually doing the wiring yourself.+Perhaps it would start to pay for complex beans with many dependencies, but+I'm not sure.++Another possible objection to this library is that wiring errors are detected at+runtime. I don't find that to be a problem though: the wiring happens at the+very beginning of the application, and it's easy to write an unit test for it.++On the plus side, this library lets you render the graph of dependencies between+beans, something which is difficult to do with naive manual wiring.++Another advantage is that you can easily modify an existing web of dependencies,+be it by inserting a new bean, overriding another, or adding a decorator.++# The expected shape of constructors++**cauldron** expects "bean" constructors to have a shape like:++```+makeServer :: Logger -> Repository -> Server+```++Where `Logger`, `Repository` and `Server` are [records-of-functions](https://www.iankduncan.com/articles/2024-01-26-records-of-effects). `Server` is+the component produced by this constructor, and it has `Logger` and `Repository`+as dependencies.++Sometimes constructors are effectful because they must perform some+initialization (for example allocating some `IORef` for the internal `Server`+state). In that case the shape of the constructor becomes something like:++```+makeServer :: Logger -> Repository -> IO Server+```++or even, for constructors which want to ensure that [resources are+deallocated](https://hackage.haskell.org/package/managed) after we are finished using the bean:++```+makeServer :: Logger -> Repository -> Managed Server+```++Having more than one constructor for the same bean type is disallowed. The+wiring is *type-directed*, so there can't be any ambiguity about which bean+constructor to use.++## Monoidally aggregated secondary beans++More complex constructors can return—besides a "primary" bean as seen in the+previous section—one or more "secondary" beans. For example:++```+makeServer :: Logger -> Repository -> (Initializer, Inspector, Server)+```++or ++```+makeServer :: Logger -> Repository -> IO (Initializer, Inspector, Server)+```++These secondary outputs of a constructor, like `Initializer` and `Inspector`,+must have `Monoid` instances. Unlike with the "primary" bean the constructor produces, they+*can* be produced by more than one constructor. Their values will be aggregated+across all the constructors that produce them.++Constructors can depend on the aggregated value of a secondary bean by taking+the bean as a regular argument. Here, `makeDebuggingServer` receives the+`mappend`ed value of all the `Inspector`s produced by other constructors (or+`mempty`, if no constructor produces them):++```+makeDebuggingServer :: Inspector -> IO DebuggingServer+```++## Decorators++Decorators are like normal constructors, but they're used to *modify* a primary+bean, instead of *producing* it. Because of that, they usually take the bean+they decorate as an argument:++```+makeServerDecorator :: Server -> Server+```++Like normal constructors, decorators can have their own dependencies (other than the+decorated bean), perform effects, and register secondary beans:++```+makeServerDecorator :: Logger -> Server -> IO (Initializer,Server)+```++# Example code++See [this example application](/app/Main.hs) with dummy components.++For a slightly more realistic example, see [here](https://github.com/danidiaz/comments-project/blob/8206c50b9af2097e2246cec0992d489029b84686/comments/lib/Comments/Main.hs#L36).++# Similarities with the [Java Spring framework IoC container](https://docs.spring.io/spring-framework/reference/core/beans.html)++Some features of this library have loose analogues in how Java Spring handles+dependency injection (although of course Spring has many more features).++First, a big *difference*: there's no analogue here of annotations, or classpath+scanning. Beans and decorators must be explicitly registered. ++- Java POJOs are Haskell [records-of-functions](https://www.iankduncan.com/articles/2024-01-26-records-of-effects), where the functions will usually+be closures which encapsulate access to some shared internal state (state like+configuration values, or mutable references). Functions that return+records-of-functions correspond to POJO constructors.++- [@PostConstruct](https://docs.spring.io/spring-framework/reference/core/beans/annotation-config/postconstruct-and-predestroy-annotations.html#page-title) roughly corresponds to effectful constructors.++  Although I expect effectful constructors to be used comparatively more in this+ library than in Spring, because here they're required to initialize mutable+ references used by the beans.++- [decorated self-invocations](https://docs.spring.io/spring-framework/reference/core/aop/proxying.html#aop-understanding-aop-proxies) correspond to constructors that+  depend on the same bean that they produce.++  Note that this is different from decorators that depend on the bean they+  modify. The constructor will receive the fully decorated bean "from the+  future" (with the possibility of infinite loops if it makes use of it too+  eagerly). In contrast, a decorator will receive either the bare "undecorated"+  bean, or the in-construction result of applying the decorators that come+  earlier in the decorator sequence.++- [context hierachies](https://docs.spring.io/spring-framework/reference/testing/testcontext-framework/ctx-management/hierarchies.html) correspond to distributing the constructors into various sets organized in parent-child relationships, so that constructors in a child can see the beans of the parent, but not vice-versa. ++- [injecting all the beans that implement a certain interface as a list](https://twitter.com/NiestrojRobert/status/1746808940435042410) roughly corresponds to a constructor that takes a monoidally aggregated "secondary bean" as an argument. ++Some features I'm not yet sure how to mimic:++- [bean scopes](https://docs.spring.io/spring-framework/reference/core/beans/factory-scopes.html), like [request scope](https://docs.spring.io/spring-framework/reference/core/beans/factory-scopes.html#beans-factory-scopes-other-injection). [This Stack Overflow post](https://stackoverflow.com/a/77174979/1364288) gives some information about how they are implemented in Spring.++  The SO post explains that in Spring the injection of request scoped beans into long-lived beans involves thread-local variables. I explored such a technique for Cauldron [here](https://discourse.haskell.org/t/i-got-rid-of-readert-and-now-my-application-is-hanging-by-a-thread/9330).++# See also++- [registry](https://hackage.haskell.org/package/registry) is a more mature and useable library for dependency injection in Haskell. See [this explanatory video](https://www.youtube.com/watch?v=fFCcvsbCrH8).
app/Main.hs view
@@ -1,5 +1,5 @@-{-# LANGUAGE BlockArguments #-} {-# LANGUAGE DerivingStrategies #-}+{-# LANGUAGE OverloadedLists #-}  -- | We have a bunch of datatypes, and a single recipe (constructor) for each -- datatype. This means the wiring can be type-directed: we don't have to make a@@ -10,7 +10,6 @@ module Main where  import Cauldron-import Data.Function ((&)) import Data.Maybe (fromJust)  {-@@ -122,7 +121,7 @@ -- -- Dependency cycles of more than one bean are forbidden, however. makeG :: E -> F -> G -> G-makeG _ _ !_ = G+makeG _ _ _ = G  -- | A decorator. --@@ -155,7 +154,9 @@ makeZDeco2 :: (F -> Z -> (Initializer, Z)) makeZDeco2 = \_ z -> (Initializer (putStrLn "Z deco init"), z) -boringWiring :: IO (Initializer, Inspector, Z)+data Entrypoint = Entrypoint Initializer Inspector Z++boringWiring :: IO Entrypoint boringWiring = do   let -- We have to remember to collect the monoidal registrations.       initializer = init1 <> init2@@ -178,63 +179,67 @@       z1 = makeZDeco1 b e z0       (init2, z2) = makeZDeco2 f z1       z = z2-  pure (initializer, inspector, z)+  pure $ Entrypoint initializer inspector z  -- | Here we don't have to worry about positional parameters. We throw all the -- constructors into the 'Cauldron' and taste the bean values at the end, plus a -- graph we may want to draw. -- -- Note that we detect wiring errors *before* running the effectful constructors.-coolWiring :: Fire IO -> Either BadBeans (DependencyGraph, IO (Initializer, Inspector, Z))-coolWiring fire = do-  let cauldron :: Cauldron IO =-        mempty-          & insert @A do makeBean do pack value makeA-          & insert @B do makeBean do pack (valueWith \(reg, bean) -> regs1 reg bean) do makeB-          & insert @C do makeBean do pack value makeC-          & insert @D do makeBean do pack value makeD-          & insert @E do makeBean do pack value makeE-          & insert @F do makeBean do pack (valueWith \(reg, bean) -> regs1 reg bean) do makeF-          & insert @G do-            Bean-              { constructor = pack value makeG,-                decos =-                  fromConstructors-                    [ pack value makeGDeco1-                    ]-              }-          & insert @H do makeBean do pack (valueWith \(reg1, reg2, bean) -> regs2 reg1 reg2 bean) do makeH-          & insert @Z do-            Bean-              { constructor = pack value makeZ,-                decos =-                  fromConstructors-                    [ pack value makeZDeco1,-                      pack (valueWith \(reg, bean) -> regs1 reg bean) do makeZDeco2-                    ]-              }-          & insert @(Initializer, Inspector, Z) do makeBean do pack value \a b c -> (a, b, c)-  fmap (fmap (fmap (fromJust . taste @(Initializer, Inspector, Z)))) do cook fire cauldron+coolWiring :: Either RecipeError (IO Entrypoint)+coolWiring = fmap (fmap (fromJust . taste @Entrypoint)) $ cook allowSelfDeps cauldron +cauldron :: Cauldron IO+cauldron :: Cauldron IO =+  [ recipe @A $ val $ pure makeA,+    recipe @B $ val $ pure makeB,+    recipe @C $ val $ wire makeC,+    recipe @D $ val $ wire makeD,+    recipe @E $ val $ wire makeE,+    recipe @F $ val $ wire makeF,+    recipe @G $+      Recipe+        { bean = val $ wire makeG,+          decos =+            [ val $ wire makeGDeco1+            ]+        },+    recipe @H $ val $ wire makeH,+    recipe @Z+      Recipe+        { bean = val $ wire makeZ,+          decos =+            [ val $ wire makeZDeco1,+              val $ wire makeZDeco2+            ]+        },+    recipe @Entrypoint $ val $ wire Entrypoint+  ]+ main :: IO () main = do+  -- "manual" wiring   do-    (Initializer {runInitializer}, Inspector {inspect}, z) <- boringWiring+    Entrypoint (Initializer {runInitializer}) (Inspector {inspect}) z <- boringWiring     inspection <- inspect     print inspection     print z     runInitializer-  case coolWiring allowSelfDeps of+  -- wiring with Cauldron+  merr <- case coolWiring of     Left badBeans -> do-      print badBeans-    Right (depGraph, action) -> do-      exportToDot defaultStepToText "beans.dot" depGraph-      exportToDot defaultStepToText "beans-no-agg.dot" do removeSecondaryBeans do depGraph-      exportToDot defaultStepToText "beans-no-agg-no-decos.dot" do removeDecos do removeSecondaryBeans do depGraph-      exportToDot defaultStepToText "beans-simple.dot" do collapsePrimaryBeans do removeDecos do removeSecondaryBeans do depGraph-      exportToDot defaultStepToText "beans-simple-with-decos.dot" do collapsePrimaryBeans do removeSecondaryBeans do depGraph-      (Initializer {runInitializer}, Inspector {inspect}, z) <- action+      putStrLn $ prettyRecipeError badBeans+      pure $ Just badBeans+    Right action -> do+      Entrypoint (Initializer {runInitializer}) (Inspector {inspect}) z <- action       inspection <- inspect       print inspection       print z       runInitializer+      pure $ Nothing+  let depGraph = getDependencyGraph cauldron+  writeAsDot (defaultStyle merr) "beans.dot" depGraph+  writeAsDot (defaultStyle merr) "beans-no-agg.dot" $ removeSecondaryBeans $ depGraph+  writeAsDot (defaultStyle merr) "beans-no-agg-no-decos.dot" $ removeDecos $ removeSecondaryBeans $ depGraph+  writeAsDot (defaultStyle merr) "beans-simple.dot" $ collapseToPrimaryBeans $ removeDecos $ removeSecondaryBeans $ depGraph+  writeAsDot (defaultStyle merr) "beans-simple-with-decos.dot" $ collapseToPrimaryBeans $ removeSecondaryBeans $ depGraph
cauldron.cabal view
@@ -1,18 +1,18 @@ cabal-version:      3.4 name:               cauldron-version:            0.4.0.0-synopsis:           Toy dependency injection framework-description:        Toy dependency injection framework that wires things at runtime.+version:            0.6.0.0+synopsis:           Dependency injection library+description:        Dependency injection library that wires things at runtime. license:            BSD-3-Clause license-file:       LICENSE author:             diaz_carrete@yahoo.com maintainer:         Daniel Díaz -- copyright: build-type:         Simple-extra-doc-files:    CHANGELOG.md+extra-doc-files:    README.md, CHANGELOG.md -- extra-source-files: category: Dependency Injection-tested-with: GHC ==9.8.1 || ==9.6.3+tested-with: GHC ==9.8.2 source-repository    head     type:     git     location: https://github.com/danidiaz/cauldron.git@@ -35,18 +35,19 @@       tasty           ^>= 1.5,       tasty-hunit     ^>= 0.10,       transformers >= 0.5 && < 0.7,+      algebraic-graphs ^>= 0.7,       cauldron,  library     import:           common-lib     build-depends:         algebraic-graphs ^>= 0.7,-        multicurryable ^>= 0.1.1.0,         bytestring >= 0.10.0 && < 0.13,-        sop-core >= 0.5.0 && < 0.6,     hs-source-dirs:   lib     exposed-modules:           Cauldron+        Cauldron.Beans+        Cauldron.Args         Cauldron.Managed  executable cauldron-example-wiring@@ -63,6 +64,12 @@     hs-source-dirs:   test     main-is:          tests.hs +test-suite codec-tests+    import:           common-tests+    type:             exitcode-stdio-1.0+    hs-source-dirs:   test+    main-is:          codecTests.hs+ test-suite app-tests     import:           common-tests     type:             exitcode-stdio-1.0@@ -75,3 +82,8 @@     hs-source-dirs:   test     main-is:          managedTests.hs +test-suite tests-args+    import:           common-tests+    type:             exitcode-stdio-1.0+    hs-source-dirs:   test+    main-is:          argsTests.hs
lib/Cauldron.hs view
@@ -1,1081 +1,1184 @@-{-# LANGUAGE AllowAmbiguousTypes #-}-{-# LANGUAGE BlockArguments #-}-{-# LANGUAGE DataKinds #-}-{-# LANGUAGE DerivingStrategies #-}-{-# LANGUAGE DerivingVia #-}-{-# LANGUAGE DuplicateRecordFields #-}-{-# LANGUAGE FunctionalDependencies #-}-{-# LANGUAGE GADTs #-}-{-# LANGUAGE LambdaCase #-}-{-# LANGUAGE MultiParamTypeClasses #-}-{-# LANGUAGE OverloadedRecordDot #-}-{-# LANGUAGE RankNTypes #-}-{-# LANGUAGE TypeFamilies #-}-{-# LANGUAGE UndecidableInstances #-}-{-# LANGUAGE ViewPatterns #-}-{-# LANGUAGE NoFieldSelectors #-}---- | This is a library for performing dependency injection. It's an alternative--- to manually wiring your functions and passing all required parameters--- explicitly. Instead of that, you throw your functions into a 'Cauldron', which wires--- them for you, guiding itself by the types.------ Wiring errors are detected at runtime, not at compile time.------ This library should be used at the ["composition root"](https://stackoverflow.com/questions/6277771/what-is-a-composition-root-in-the-context-of-dependency-injection) of the application,--- and only there: the components we are wiring together need not be aware that the library exists.------ >>> :{--- data A = A deriving Show--- data B = B deriving Show--- data C = C deriving Show--- makeA :: A--- makeA = A--- makeB :: A -> B--- makeB = \_ -> B--- makeC :: A -> B -> IO C--- makeC = \_ _ -> pure C--- :}------ >>> :{--- do---   let cauldron :: Cauldron IO---       cauldron =---         emptyCauldron---         & insert @A do makeBean do pack value makeA---         & insert @B do makeBean do pack value makeB---         & insert @C do makeBean do pack effect makeC---       Right (_ :: DependencyGraph, action) = cook forbidDepCycles cauldron---   beans <- action---   pure do taste @C beans--- :}--- Just C-module Cauldron-  ( -- * Filling the cauldron-    Cauldron,-    emptyCauldron,-    insert,-    adjust,-    delete,-    hoistCauldron,--    -- * Beans-    Bean (..),-    makeBean,-    setConstructor,-    setDecos,-    overDecos,-    hoistBean,--    -- ** Decorators-    -- $decos-    Decos,-    emptyDecos,-    fromConstructors,-    addOuter,-    addInner,-    hoistDecos,--    -- ** Constructors-    -- $constructors-    Constructor,-    pack,-    pack0,-    pack1,-    pack2,-    pack3,-    hoistConstructor,-    Packer (..),-    value,-    effect,--    -- *** Registering secondary beans-    -- $registrations-    valueWith,-    effectWith,-    Regs,-    regs0,-    regs1,-    regs2,-    regs3,--    -- * Cooking the beans-    cook,-    cookNonEmpty,-    cookTree,--    -- ** How loopy can we get?-    Fire,-    forbidDepCycles,-    allowSelfDeps,--    -- ** Tasting the results-    BoiledBeans,-    taste,-    BadBeans (..),-    PathToCauldron,--    -- ** Drawing deps-    DependencyGraph,-    exportToDot,-    defaultStepToText,-    BeanConstructionStep (..),-    removeSecondaryBeans,-    removeDecos,-    collapsePrimaryBeans,-    toAdjacencyMap,-  )-where--import Algebra.Graph.AdjacencyMap (AdjacencyMap)-import Algebra.Graph.AdjacencyMap qualified as Graph-import Algebra.Graph.AdjacencyMap.Algorithm qualified as Graph-import Algebra.Graph.Export.Dot qualified as Dot-import Control.Applicative-import Control.Monad.Fix-import Data.Bifunctor (first)-import Data.ByteString qualified-import Data.Dynamic-import Data.Foldable qualified-import Data.Functor (($>), (<&>))-import Data.Functor.Compose-import Data.Functor.Contravariant-import Data.Kind-import Data.List.NonEmpty (NonEmpty)-import Data.List.NonEmpty qualified-import Data.Map.Strict (Map)-import Data.Map.Strict qualified as Map-import Data.Maybe (fromJust)-import Data.Monoid (Endo (..))-import Data.SOP (All, And, K (..))-import Data.SOP.NP-import Data.Sequence (Seq)-import Data.Sequence qualified as Seq-import Data.Set (Set)-import Data.Set qualified as Set-import Data.Text qualified-import Data.Text.Encoding qualified-import Data.Tree-import Data.Type.Equality (testEquality)-import Data.Typeable-import GHC.Exts (IsList (..))-import Multicurryable-import Type.Reflection qualified---- | A map of 'Bean' recipes. Parameterized by the monad @m@ in which the 'Bean'--- 'Constructor's might have effects.-newtype Cauldron m where-  Cauldron :: {recipes :: Map TypeRep (SomeBean m)} -> Cauldron m---- | Union of two 'Cauldron's, right-biased: prefers values from the /right/ cauldron when--- both contain the same bean. (Note that 'Data.Map.Map' is left-biased.)-instance Semigroup (Cauldron m) where-  Cauldron {recipes = r1} <> Cauldron {recipes = r2} = Cauldron do Map.unionWith (flip const) r1 r2--instance Monoid (Cauldron m) where-  mempty = Cauldron do Map.empty--emptyCauldron :: Cauldron m-emptyCauldron = mempty---- | Change the monad used by the beans in the 'Cauldron'.-hoistCauldron :: (forall x. m x -> n x) -> Cauldron m -> Cauldron n-hoistCauldron f (Cauldron {recipes}) = Cauldron {recipes = hoistSomeBean f <$> recipes}--data SomeBean m where-  SomeBean :: (Typeable bean) => Bean m bean -> SomeBean m--hoistSomeBean :: (forall x. m x -> n x) -> SomeBean m -> SomeBean n-hoistSomeBean f (SomeBean bean) = SomeBean do hoistBean f bean---- | A bean recipe, to be inserted into a 'Cauldron'.-data Bean m bean where-  Bean ::-    { -- | How to build the bean itself.-      constructor :: Constructor m bean,-      -- | How to build the decorators that wrap the bean. There might be no decorators.-      decos :: Decos m bean-    } ->-    Bean m bean---- | Change the monad used by the bean\'s 'Constructor' and its 'Decos'.-hoistBean :: (forall x. m x -> n x) -> Bean m bean -> Bean n bean-hoistBean f (Bean {constructor, decos}) =-  Bean-    { constructor = hoistConstructor f constructor,-      decos = hoistDecos f decos-    }---- | A 'Bean' without decorators, having only the main constructor.-makeBean :: Constructor m a -> Bean m a-makeBean constructor = Bean {constructor, decos = mempty}---- $decos------ Decorators are 'Constructor's which, instead constructing the original--- version of a bean, they modify it in some way (but without changing its--- type). Because they modify the bean, typically decorators will take the bean--- as an argument.------ Decorators can have other dependencies beyond the modified bean.------ When the bean is a record-of-functions, decorators can be used to--- add behaviors like caching, logging... to the functions.--------- >>> :{--- newtype Foo = Foo { sayFoo :: IO () }--- makeFoo :: Foo--- makeFoo = Foo { sayFoo = putStrLn "foo" }--- makeFooDeco1 :: Foo -> Foo--- makeFooDeco1 Foo { sayFoo } = Foo { sayFoo = putStrLn "deco1 enter" >> sayFoo >> putStrLn "deco1 exit" }--- makeFooDeco2 :: Foo -> IO Foo--- makeFooDeco2 Foo { sayFoo } = putStrLn "deco2 init" >> pure Foo { sayFoo = putStrLn "deco2 enter" >> sayFoo >> putStrLn "deco2 exit" }--- :}------ >>> :{--- do---   let cauldron :: Cauldron IO---       cauldron =---         emptyCauldron---         & insert @Foo---           Bean {---             constructor = pack value makeFoo,---             decos = fromConstructors [---                  pack value makeFooDeco1,---                  pack effect makeFooDeco2---               ]---           }---       Right (_ :: DependencyGraph, action) = cook forbidDepCycles cauldron---   beans <- action---   let Just Foo {sayFoo} = taste beans---   sayFoo--- :}--- deco2 init--- deco2 enter--- deco1 enter--- foo--- deco1 exit--- deco2 exit---- | A list of 'Constructor's for the decorators of some 'Bean'.------ 'Constructor's for a decorator will have the @bean@ itself among their--- arguments. That @bean@ argument will be either the \"bare\" undecorated--- bean (for the first decorator) or the result of applying the previous--- decorator in the list.------ Decorators can have other dependencies besides the @bean@.-newtype Decos m bean where-  Decos :: {decoCons :: Seq (Constructor m bean)} -> Decos m bean-  deriving newtype (Semigroup, Monoid)--instance IsList (Decos m bean) where-  type Item (Decos m bean) = Constructor m bean-  fromList decos = Decos do GHC.Exts.fromList decos-  toList (Decos {decoCons}) = GHC.Exts.toList decoCons---- | Empty list of decorators.-emptyDecos :: Decos m bean-emptyDecos = mempty---- | Change the monad used by the decorators.-hoistDecos :: (forall x. m x -> n x) -> Decos m bean -> Decos n bean-hoistDecos f (Decos {decoCons}) = Decos {decoCons = hoistConstructor f <$> decoCons}--setConstructor :: Constructor m bean -> Bean m bean -> Bean m bean-setConstructor constructor (Bean {decos}) = Bean {constructor, decos}--setDecos :: Decos m bean -> Bean m bean -> Bean m bean-setDecos decos (Bean {constructor}) = Bean {constructor, decos}--overDecos :: (Decos m bean -> Decos m bean) -> Bean m bean -> Bean m bean-overDecos f (Bean {constructor, decos}) = Bean {constructor, decos = f decos}---- | Add a new decorator that modifies the bean /after/ all existing decorators.------ This means the behaviours it adds to the bean\'s methods will be applied--- /first/ when entering the method.-addOuter :: Constructor m bean -> Decos m bean -> Decos m bean-addOuter con (Decos {decoCons}) = Decos do decoCons Seq.|> con---- | Add a new decorator that modifies the bean /before/ all existing--- decorators.------ This means the behaviours it adds to the bean\'s methods will be applied--- /last/, just before entering the base bean's method.------ Usually 'addOuter' is preferrable.-addInner :: Constructor m bean -> Decos m bean -> Decos m bean-addInner con (Decos {decoCons}) = Decos do con Seq.<| decoCons---- | Build the decorators from a list of 'Constructor's, first innermost,--- last outermost.-fromConstructors ::-  [Constructor m bean] ->-  Decos m bean-fromConstructors cons = Decos do Seq.fromList cons---- $constructors------ The bean-producing or bean-decorating functions that we want to wire need to be--- coaxed into a 'Constructor' value before creating a 'Bean' recipe and adding it to the 'Cauldron'.------ If your aren't dealing with secondary beans, don't sweat it: use @pack value@ for pure--- constructors functions and @pack effect@ for effectful ones. That should be enough.---- | A way of building some @bean@ value, potentially requiring some--- dependencies, potentially returning some secondary beans--- along the primary @bean@ result, and also potentially requiring some--- initialization effect in a monad @m@.------ Note that only the type of the primary @bean@ is reflected in the--- 'Constructor' type. Those of the dependencies and secondary beans are not.------ A typical initialization monad will be 'IO', used for example to create--- mutable references that the bean will use internally. Sometimes the a--- constructor will allocate resources with bracket-like operations, and in that--- case a monad like 'Managed' might be needed instead.-data Constructor m bean where-  Constructor ::-    (All Typeable args, All (Typeable `And` Monoid) regs) =>-    { constructor_ :: Args args (m (Regs regs bean))-    } ->-    Constructor m bean--data ConstructorReps where-  ConstructorReps ::-    { beanRep :: TypeRep,-      argReps :: Set TypeRep,-      regReps :: Map TypeRep Dynamic-    } ->-    ConstructorReps---- | Change the monad in which the 'Constructor'\'s effects take place.-hoistConstructor :: (forall x. m x -> n x) -> Constructor m bean -> Constructor n bean-hoistConstructor f (Constructor {constructor_}) = Constructor do fmap f constructor_---- | Put a recipe for a 'Bean' into the 'Cauldron'.------ Only one recipe is allowed for each different @bean@ type, so 'insert' for a--- @bean@ will overwrite previous recipes for that type.-insert ::-  forall (bean :: Type) m.-  (Typeable bean) =>-  Bean m bean ->-  Cauldron m ->-  Cauldron m-insert recipe Cauldron {recipes} = do-  let rep = typeRep (Proxy @bean)-  Cauldron {recipes = Map.insert rep (SomeBean recipe) recipes}---- | Tweak an already existing 'Bean' recipe.-adjust ::-  forall bean m.-  (Typeable bean) =>-  (Bean m bean -> Bean m bean) ->-  Cauldron m ->-  Cauldron m-adjust f (Cauldron {recipes}) = do-  let rep = typeRep (Proxy @bean)-  Cauldron-    { recipes =-        Map.adjust-          do-            \(SomeBean (r :: Bean m a)) ->-              case testEquality (Type.Reflection.typeRep @bean) (Type.Reflection.typeRep @a) of-                Nothing -> error "should never happen"-                Just Refl -> SomeBean (f r)-          rep-          recipes-    }--delete ::-  forall bean m.-  (Typeable bean) =>-  Cauldron m ->-  Cauldron m-delete Cauldron {recipes} =-  Cauldron {recipes = Map.delete (typeRep (Proxy @bean)) recipes}---- | Strategy for dealing with dependency cycles.------ (Terrible uninformative name caused by a metaphor stretched too far.)-data Fire m = Fire-  { shouldOmitDependency :: (BeanConstructionStep, BeanConstructionStep) -> Bool,-    followPlanCauldron ::-      Cauldron m ->-      BoiledBeans ->-      Plan ->-      m BoiledBeans-  }--removeBeanFromArgs :: ConstructorReps -> ConstructorReps-removeBeanFromArgs ConstructorReps {argReps, regReps, beanRep} =-  ConstructorReps {argReps = Set.delete beanRep argReps, regReps, beanRep}---- | Allow /direct/ self-dependencies.------ A bean constructor might depend on itself. This can be useful for having--- decorated self-invocations, because the version of the bean received as--- argument comes \"from the future\" and is already decorated. (__BEWARE__:--- Pattern-matching too eagerly on this \"bean from the future\" during--- construction will cause infinite loops.)------ Note that a 'MonadFix' instance is required of the initialization monad.-allowSelfDeps :: (MonadFix m) => Fire m-allowSelfDeps =-  Fire-    { shouldOmitDependency = \case-        (BarePrimaryBean bean, PrimaryBean anotherBean) | bean == anotherBean -> True-        _ -> False,-      followPlanCauldron = \cauldron initial plan ->-        mfix do-          \final ->-            Data.Foldable.foldlM-              do followPlanStep cauldron final-              initial-              plan-    }---- | Forbid any kind of cyclic dependencies between beans. This is probably what you want.-forbidDepCycles :: (Monad m) => Fire m-forbidDepCycles =-  Fire-    { shouldOmitDependency = \_ -> False,-      followPlanCauldron = \cauldron initial plan ->-        Data.Foldable.foldlM-          do followPlanStep cauldron BoiledBeans {beans = Map.empty}-          initial-          plan-    }---- https://discord.com/channels/280033776820813825/280036215477239809/1147832555828162594--- https://github.com/ghc-proposals/ghc-proposals/pull/126#issuecomment-1363403330---- | This function DOESN'T return the bean rep itself in the argreps.-constructorReps :: (Typeable bean) => Constructor m bean -> ConstructorReps-constructorReps Constructor {constructor_ = (_ :: Args args (m (Regs accums bean)))} =-  ConstructorReps-    { beanRep,-      argReps =-        do-          Set.fromList do-            collapse_NP do-              cpure_NP @_ @args-                do Proxy @Typeable-                typeRepHelper,-      regReps =-        Map.fromList do-          collapse_NP do-            cpure_NP @_ @accums-              do Proxy @(Typeable `And` Monoid)-              typeRepHelper'-    }-  where-    typeRepHelper :: forall a. (Typeable a) => K TypeRep a-    typeRepHelper = K (typeRep (Proxy @a))-    typeRepHelper' :: forall a. ((Typeable `And` Monoid) a) => K (TypeRep, Dynamic) a-    typeRepHelper' = K (typeRep (Proxy @a), toDyn @a mempty)-    beanRep = typeRep (Proxy @bean)--type Plan = [BeanConstructionStep]---- | A step in the construction of a bean value.-data BeanConstructionStep-  = -- | Undecorated bean.-    BarePrimaryBean TypeRep-  | -- | Apply the decorator with the given index. Comes after the 'BarePrimaryBean' and all 'PrimaryBeanDeco's with a lower index value.-    PrimaryBeanDeco TypeRep Int-  | -- | Final, fully decorated version of a bean. If there are no decorators, comes directly after 'BarePrimaryBean'.-    PrimaryBean TypeRep-  | -- | Beans that are secondary registrations of a 'Constructor' and which are aggregated monoidally.-    SecondaryBean TypeRep-  deriving stock (Show, Eq, Ord)---- | The successful result of 'cook'ing a 'Cauldron'. Can't do a lot with them other than to 'taste' them.-newtype BoiledBeans where-  BoiledBeans :: {beans :: Map TypeRep Dynamic} -> BoiledBeans---- | Build the beans using the recipes stored in the 'Cauldron'.-cook ::-  forall m.-  (Monad m) =>-  Fire m ->-  Cauldron m ->-  Either BadBeans (DependencyGraph, m BoiledBeans)-cook fire cauldron = do-  let result = cookTree (Node (fire, cauldron) [])-  result <&> \(tg, m) -> (rootLabel tg, rootLabel <$> m)---- | Cook a list of 'Cauldron's.------ 'Cauldron's later in the list can see the beans in all previous 'Cauldron's,--- but not vice versa.------ Beans in a 'Cauldron' have priority over the same beans in previous 'Cauldron's.-cookNonEmpty ::-  forall m.-  (Monad m) =>-  NonEmpty (Fire m, Cauldron m) ->-  Either BadBeans (NonEmpty DependencyGraph, m (NonEmpty BoiledBeans))-cookNonEmpty nonemptyCauldronList = do-  let result = cookTree (nonEmptyToTree nonemptyCauldronList)-  result <&> \(ng, m) -> (unsafeTreeToNonEmpty ng, unsafeTreeToNonEmpty <$> m)---- | Cook a hierarchy of 'Cauldron's.------ 'Cauldron's down in the branches can see the beans of their ancestor--- 'Cauldron's, but not vice versa.------ Beans in a 'Cauldron' have priority over the same beans in ancestor 'Cauldron's.-cookTree ::-  forall m.-  (Monad m) =>-  Tree (Fire m, Cauldron m) ->-  Either BadBeans (Tree DependencyGraph, m (Tree BoiledBeans))-cookTree (treecipes) = do-  accumMap <- first DoubleDutyBeans do checkNoDoubleDutyBeans (snd <$> treecipes)-  () <- first (uncurry MissingDependencies) do checkMissingDeps (Map.keysSet accumMap) (snd <$> treecipes)-  treeplan <- first DependencyCycle do buildPlans (Map.keysSet accumMap) treecipes-  Right-    ( treeplan <&> \(graph, _) -> DependencyGraph {graph},-      followPlan (BoiledBeans accumMap) (snd <$> treeplan)-    )--checkNoDoubleDutyBeans ::-  Tree (Cauldron m) ->-  Either (Set TypeRep) (Map TypeRep Dynamic)-checkNoDoubleDutyBeans treecipes = do-  let (accumMap, beanSet) = cauldronTreeRegs treecipes-  let common = Set.intersection (Map.keysSet accumMap) beanSet-  if not (Set.null common)-    then Left common-    else Right accumMap---- | Will always be @[]@ when using 'cook'; identifies a 'Cauldron' in a hierarchy of 'Cauldron's when--- using 'cookNonEmpty' or 'cookTree'.-type PathToCauldron = [Int]--cauldronTreeRegs :: Tree (Cauldron m) -> (Map TypeRep Dynamic, Set TypeRep)-cauldronTreeRegs = foldMap cauldronRegs--cauldronRegs :: Cauldron m -> (Map TypeRep Dynamic, Set TypeRep)-cauldronRegs Cauldron {recipes} =-  Map.foldMapWithKey-    do \rep recipe -> (recipeRegs recipe, Set.singleton rep)-    recipes---- | Returns the accumulators, not the main bean-recipeRegs :: SomeBean m -> Map TypeRep Dynamic-recipeRegs (SomeBean (Bean {constructor, decos = Decos {decoCons}})) = do-  let extractRegReps = (.regReps) . constructorReps-  extractRegReps constructor-    <> foldMap extractRegReps decoCons--checkMissingDeps ::-  -- | accums-  Set TypeRep ->-  Tree (Cauldron m) ->-  Either (PathToCauldron, Map TypeRep (Set TypeRep)) ()-checkMissingDeps accums treecipes = do-  let decoratedTreecipes = decorate ([], Map.empty, treecipes)-      missing = (\(key, available, requested) -> first (key,) do checkMissingDepsCauldron accums (Map.keysSet available) requested) <$> decoratedTreecipes-  sequence_ missing-  where-    decorate ::-      (PathToCauldron, Map TypeRep PathToCauldron, Tree (Cauldron m)) ->-      Tree (PathToCauldron, Map TypeRep PathToCauldron, Cauldron m)-    decorate = unfoldTree-      do-        \(key, acc, Node (current@Cauldron {recipes}) rest) ->-          let -- current level has priority-              newAcc = (recipes $> key) `Map.union` acc-              newSeeds = do-                (i, z) <- zip [0 ..] rest-                let newKey = key ++ [i]-                [(newKey, newAcc, z)]-           in ((key, newAcc, current), newSeeds)--checkMissingDepsCauldron ::-  -- | accums-  Set TypeRep ->-  -- | available at this level-  Set TypeRep ->-  Cauldron m ->-  Either (Map TypeRep (Set TypeRep)) ()-checkMissingDepsCauldron accums available Cauldron {recipes} = do-  let missingMap = (`Map.mapMaybe` recipes) \someBean -> do-        let missing = Set.filter (`Set.notMember` available) do demanded someBean-        if Set.null missing-          then Nothing-          else Just missing-  if not (Map.null missingMap)-    then Left missingMap-    else Right ()-  where-    demanded :: SomeBean m -> Set TypeRep-    demanded (SomeBean Bean {constructor, decos = Decos {decoCons}}) =-      ( Set.fromList do-          let ConstructorReps {argReps = beanArgReps} = constructorReps constructor-          Set.toList beanArgReps ++ do-            decoCon <- Data.Foldable.toList decoCons-            let ConstructorReps {argReps = decoArgReps} = constructorReps decoCon-            Set.toList decoArgReps-      )-        `Set.difference` accums--buildPlans :: Set TypeRep -> Tree (Fire m, Cauldron m) -> Either (NonEmpty BeanConstructionStep) (Tree (AdjacencyMap BeanConstructionStep, (Plan, Fire m, Cauldron m)))-buildPlans secondary = traverse \(fire@Fire {shouldOmitDependency}, cauldron) -> do-  let deps = filter (not . shouldOmitDependency) do buildDepsCauldron secondary cauldron-  let graph = Graph.edges deps-  case Graph.topSort graph of-    Left recipeCycle ->-      Left recipeCycle-    Right (reverse -> plan) -> do-      let completeGraph = Graph.edges deps-      Right (completeGraph, (plan, fire, cauldron))--buildDepsCauldron :: Set TypeRep -> Cauldron m -> [(BeanConstructionStep, BeanConstructionStep)]-buildDepsCauldron secondary Cauldron {recipes} = do-  let makeTargetStep :: TypeRep -> BeanConstructionStep-      makeTargetStep rep =-        if rep `Set.member` secondary-          then SecondaryBean rep-          else PrimaryBean rep-  (flip Map.foldMapWithKey)-    recipes-    \beanRep-     ( SomeBean-         ( Bean-             { constructor = constructor :: Constructor m bean,-               decos = Decos {decoCons}-             }-           )-       ) -> do-        let bareBean = BarePrimaryBean beanRep-            boiledBean = PrimaryBean beanRep-            decos = do-              (decoIndex, decoCon) <- zip [0 :: Int ..] (Data.Foldable.toList decoCons)-              [(PrimaryBeanDeco beanRep decoIndex, decoCon)]-            beanDeps = do-              constructorEdges makeTargetStep bareBean (do constructorReps constructor)-            decoDeps = do-              (decoBean, decoCon) <- decos-              constructorEdges makeTargetStep decoBean (removeBeanFromArgs do constructorReps decoCon)-            full = bareBean Data.List.NonEmpty.:| (fst <$> decos) ++ [boiledBean]-            innerDeps = zip (Data.List.NonEmpty.tail full) (Data.List.NonEmpty.toList full)-        beanDeps ++ decoDeps ++ innerDeps--constructorEdges ::-  (TypeRep -> BeanConstructionStep) ->-  BeanConstructionStep ->-  ConstructorReps ->-  [(BeanConstructionStep, BeanConstructionStep)]-constructorEdges makeTargetStep item (ConstructorReps {argReps, regReps}) =-  -- consumers depend on their args-  ( do-      argRep <- Set.toList argReps-      let argStep = makeTargetStep argRep-      [(item, argStep)]-  )-    ++-    -- secondary beans depend on their producers-    ( do-        (regRep, _) <- Map.toList regReps-        let repStep = SecondaryBean regRep-        [(repStep, item)]-    )--followPlan ::-  (Monad m) =>-  BoiledBeans ->-  (Tree (Plan, Fire m, Cauldron m)) ->-  m (Tree BoiledBeans)-followPlan initial treecipes =-  unfoldTreeM-    ( \(initial', Node (plan, Fire {followPlanCauldron}, cauldron) rest) -> do-        newInitial' <- followPlanCauldron cauldron initial' plan-        pure (newInitial', (,) newInitial' <$> rest)-    )-    (initial, treecipes)--followPlanStep ::-  (Monad m) =>-  Cauldron m ->-  BoiledBeans ->-  BoiledBeans ->-  BeanConstructionStep ->-  m BoiledBeans-followPlanStep Cauldron {recipes} (BoiledBeans final) (BoiledBeans super) item =-  BoiledBeans <$> case item of-    BarePrimaryBean rep -> case fromJust do Map.lookup rep recipes of-      SomeBean (Bean {constructor}) -> do-        let ConstructorReps {beanRep} = constructorReps constructor-        -- We delete the beanRep before running the constructor,-        -- because if we have a self-dependency, we don't want to use the bean-        -- from a previous context (if it exists) we want the bean from final.-        -- There is a test for this.-        (super', bean) <- followConstructor constructor final (Map.delete beanRep super)-        pure do Map.insert beanRep (toDyn bean) super'-    PrimaryBeanDeco rep index -> case fromJust do Map.lookup rep recipes of-      SomeBean (Bean {decos = Decos {decoCons}}) -> do-        let decoCon = fromJust do Seq.lookup index decoCons-        let ConstructorReps {beanRep} = constructorReps decoCon-        -- Unlike before, we don't delete the beanRep before running the constructor.-        (super', bean) <- followConstructor decoCon final super-        pure do Map.insert beanRep (toDyn bean) super'-    -- \| We do nothing here, the work has been done in previous 'BarePrimaryBean' and-    -- 'PrimaryBeanDeco' steps.-    PrimaryBean _ -> pure super-    -- \| We do nothing here, secondary beans are built as a byproduct-    -- of primary beans and decorators.-    SecondaryBean _ -> pure super---- | Build a bean out of already built beans.--- This can only work without blowing up if there aren't dependecy cycles--- and the order of construction respects the depedencies!-followConstructor ::-  (Monad m) =>-  Constructor m bean ->-  Map TypeRep Dynamic ->-  Map TypeRep Dynamic ->-  m (Map TypeRep Dynamic, bean)-followConstructor Constructor {constructor_ = Args {runArgs}} final super = do-  let Extractor {runExtractor} = sequence_NP do cpure_NP (Proxy @Typeable) makeExtractor-      args = runExtractor final super-  results <- runArgs args-  case results of-    Regs regs bean -> do-      let inserters = cfoldMap_NP (Proxy @(Typeable `And` Monoid)) makeRegInserter regs-      pure (appEndo inserters super, bean)--newtype Extractor a where-  Extractor :: {runExtractor :: Map TypeRep Dynamic -> Map TypeRep Dynamic -> a} -> Extractor a-  deriving (Functor, Applicative) via ((->) (Map TypeRep Dynamic) `Compose` ((->) (Map TypeRep Dynamic)))--makeExtractor :: forall a. (Typeable a) => Extractor a-makeExtractor =-  let runExtractor final super =-        fromJust do taste' @a super <|> taste' @a final-   in Extractor {runExtractor}--makeRegInserter :: forall a. ((Typeable `And` Monoid) a) => I a -> Endo (Map TypeRep Dynamic)-makeRegInserter (I a) =-  let appEndo dynMap = do-        let reg = fromJust do taste' @a dynMap-            dyn = toDyn (reg <> a)-        Map.insert (dynTypeRep dyn) dyn dynMap-   in Endo {appEndo}---- | Return the resulting @bean@, if present.-taste :: forall bean. (Typeable bean) => BoiledBeans -> Maybe bean-taste BoiledBeans {beans} = taste' beans--taste' :: forall bean. (Typeable bean) => Map TypeRep Dynamic -> Maybe bean-taste' beans = do-  let rep = typeRep (Proxy @bean)-  dyn <- Map.lookup rep beans-  fromDynamic @bean dyn---- | Sometimes the 'cook'ing process goes wrong.-data BadBeans-  = -- | The 'Cauldron' identified by 'PathToCauldron' has beans-    -- that depend on beans that can't be found either in the current 'Cauldron' or its ancestors.-    MissingDependencies PathToCauldron (Map TypeRep (Set TypeRep))-  | -- | Beans that work both as primary beans and as secondary beans-    -- are disallowed.-    DoubleDutyBeans (Set TypeRep)-  | -- | Dependency cycles are disallowed by some 'Fire's.-    DependencyCycle (NonEmpty BeanConstructionStep)-  deriving stock (Show)---- | An edge means that the source depends on the target.------ The dependencies of each bean are given separatedly from its decorators.-newtype DependencyGraph = DependencyGraph {graph :: AdjacencyMap BeanConstructionStep}---- | Conversion to a graph type--- from the--- [algebraic-graphs](https://hackage.haskell.org/package/algebraic-graphs-0.7/docs/Algebra-Graph-AdjacencyMap.html)--- library for further processing.-toAdjacencyMap :: DependencyGraph -> AdjacencyMap BeanConstructionStep-toAdjacencyMap DependencyGraph {graph} = graph--removeSecondaryBeans :: DependencyGraph -> DependencyGraph-removeSecondaryBeans DependencyGraph {graph} =-  DependencyGraph {graph = Graph.induce (\case SecondaryBean {} -> False; _ -> True) graph}--removeDecos :: DependencyGraph -> DependencyGraph-removeDecos DependencyGraph {graph} =-  DependencyGraph {graph = Graph.induce (\case PrimaryBeanDeco {} -> False; _ -> True) graph}---- | Unifies 'PrimaryBean's with their respective 'BarePrimaryBean's and 'PrimaryBeanDeco's.------ Also removes any self-loops.-collapsePrimaryBeans :: DependencyGraph -> DependencyGraph-collapsePrimaryBeans DependencyGraph {graph} = do-  let simplified =-        Graph.gmap-          ( \case-              BarePrimaryBean rep -> PrimaryBean rep-              PrimaryBeanDeco rep _ -> PrimaryBean rep-              other -> other-          )-          graph-      -- Is there a simpler way to removoe self-loops?-      vertices = Graph.vertexList simplified-      edges = Graph.edgeList simplified-      edgesWithoutSelfLoops =-        filter-          ( \case-              (PrimaryBean source, PrimaryBean target) -> if source == target then False else True-              _ -> True-          )-          edges-  DependencyGraph {graph = Graph.vertices vertices `Graph.overlay` Graph.edges edgesWithoutSelfLoops}---- | See the [DOT format](https://graphviz.org/doc/info/lang.html).-exportToDot :: (BeanConstructionStep -> Data.Text.Text) -> FilePath -> DependencyGraph -> IO ()-exportToDot prettyRep filepath DependencyGraph {graph} = do-  let dot =-        Dot.export-          do Dot.defaultStyle prettyRep-          graph-  Data.ByteString.writeFile filepath (Data.Text.Encoding.encodeUtf8 dot)--defaultStepToText :: BeanConstructionStep -> Data.Text.Text-defaultStepToText =-  let p rep = Data.Text.pack do show rep-   in \case-        BarePrimaryBean rep -> p rep <> Data.Text.pack "#bare"-        PrimaryBeanDeco rep index -> p rep <> Data.Text.pack ("#deco#" ++ show index)-        PrimaryBean rep -> p rep-        SecondaryBean rep -> p rep <> Data.Text.pack "#sec"--newtype Args args r = Args {runArgs :: NP I args -> r}-  deriving newtype (Functor, Applicative, Monad)--argsN ::-  forall (args :: [Type]) r curried.-  (MulticurryableF args r curried (IsFunction curried)) =>-  curried ->-  Args args r-argsN = Args . multiuncurry---- $registrations------ 'Constructor's produce a single primary bean, but sometimes they might also--- \"register\" a number of secondary beans.------ These secondary beans--- must have 'Monoid' instances and, unlike the primary bean, can be produced by--- more that one 'Constructor'. Their values are aggregated across all the 'Constructor's--- that produce them. The final aggregated value can be depended upon by other 'Constructor's--- as if it were a normal bean.------ The 'Regs' type is used to represent the main bean along with the secondary--- beans that it registers. Because usually we'll be working with functions that--- do not use the 'Regs' type, a 'Packer' must be used to coax the \"tip\" of--- the constructor function into the required shape expected by 'Constructor'.------ >>> :{--- data A = A deriving Show--- data B = B deriving Show--- data C = C (Sum Int) deriving Show--- makeA :: (Sum Int, A)--- makeA = (Sum 1, A)--- makeB :: A -> IO (Sum Int, B)--- makeB = \_ -> pure (Sum 2, B)--- makeC :: Sum Int -> C--- makeC = \theSum -> C theSum--- :}--------- >>> :{--- do---   let cauldron :: Cauldron IO---       cauldron =---         emptyCauldron---         & insert @A do makeBean do pack (valueWith \(s, a) -> regs1 s a) makeA---         & insert @B do makeBean do pack (effectWith \(s, b) -> regs1 s b) makeB---         & insert @C do makeBean do pack value makeC---       Right (_ :: DependencyGraph, action) = cook forbidDepCycles cauldron---   beans <- action---   pure do taste @C beans--- :}--- Just (C (Sum {getSum = 3}))---- | Auxiliary type which contains a primary bean along with zero or more--- secondary beans. The secondary beans must have--- 'Monoid' instances.-data Regs (regs :: [Type]) bean = Regs (NP I regs) bean-  deriving (Functor)---- | A primary @bean@ without secondary beans.-regs0 :: bean -> Regs '[] bean-regs0 bean = Regs Nil bean---- | A primary @bean@ with one secondary bean.-regs1 :: reg1 -> bean -> Regs '[reg1] bean-regs1 reg1 bean = Regs (I reg1 :* Nil) bean---- | A primary @bean@ with two secondary beans.-regs2 :: reg1 -> reg2 -> bean -> Regs '[reg1, reg2] bean-regs2 reg1 reg2 bean = Regs (I reg1 :* I reg2 :* Nil) bean---- | A primary @bean@ with three secondary beans.-regs3 :: reg1 -> reg2 -> reg3 -> bean -> Regs '[reg1, reg2, reg3] bean-regs3 reg1 reg2 reg3 bean = Regs (I reg1 :* I reg2 :* I reg3 :* Nil) bean---- | Applies a transformation to the tip of a curried function, coaxing--- it into the shape expected by a 'Constructor', which includes information--- about which is the primary bean and which are the secondary ones.------ * For pure constructors without registrations, try 'value'.------ * For effectful constructors without registrations, try 'effect'.------ More complex cases might require 'valueWith', 'effectWith', or working with--- the 'Packer' constructor itself.-newtype Packer m regs bean r = Packer (r -> m (Regs regs bean))--runPacker :: Packer m regs bean r -> r -> m (Regs regs bean)-runPacker (Packer f) = f--instance Contravariant (Packer m regs bean) where-  contramap f (Packer p) = Packer (p . f)---- | For pure constructors that return the @bean@ directly, and do not register--- secondary beans.-value :: (Applicative m) => Packer m '[] bean bean-value = Packer \bean -> pure do regs0 bean---- | For effectul constructors that return an @m bean@ initialization action,--- and do not register secondary beans.-effect :: (Applicative m) => Packer m '[] bean (m bean)-effect = Packer \action -> do fmap regs0 action---- |--- >>> :{--- data A = A deriving Show--- data B = B deriving Show--- makeB :: A -> (Sum Int, B)--- makeB = \_ -> (Sum 1, B)--- constructorB :: Constructor IO B--- constructorB = pack (valueWith \(s,bean) -> regs1 s bean) makeB--- :}-valueWith ::-  (Applicative m, All (Typeable `And` Monoid) regs) =>-  -- | Massage the pure value at the tip of the constructor into a 'Regs'.-  (r -> Regs regs bean) ->-  Packer m regs bean r-valueWith f = Packer do pure . f---- |--- >>> :{--- data A = A deriving Show--- data B = B deriving Show--- makeB :: A -> IO (Sum Int, B)--- makeB = \_ -> pure (Sum 1, B)--- constructorB :: Constructor IO B--- constructorB = pack (effectWith \(s,bean) -> regs1 s bean) makeB--- :}-effectWith ::-  (Applicative m, All (Typeable `And` Monoid) regs) =>-  -- | Massage the value returned by the action at the tip of the constructor into a 'Regs'.-  (r -> Regs regs bean) ->-  Packer m regs bean (m r)-effectWith f = Packer do fmap f---- | Take a curried function that constructs a bean, uncurry it recursively and--- then apply a 'Packer' to its tip, resulting in a 'Constructor'.------ >>> :{--- data A = A deriving Show--- data B = B deriving Show--- data C = C deriving Show--- makeB :: A -> B--- makeB = \_ -> B--- makeC :: A -> B -> IO C--- makeC = \_ _ -> pure C--- constructorB :: Constructor IO B--- constructorB = pack value makeB--- constructorC :: Constructor IO C--- constructorC = pack effect makeC--- :}------ There are 'pack0', 'pack1'... functions which work for specific number of arguments, but--- the generic 'pack' should work in most cases anyway.-pack ::-  forall (args :: [Type]) r curried regs bean m.-  ( MulticurryableF args r curried (IsFunction curried),-    All Typeable args,-    All (Typeable `And` Monoid) regs-  ) =>-  -- | Fit the outputs of the constructor into the auxiliary 'Regs' type.-  ---  -- See 'regs1' and similar functions.-  Packer m regs bean r ->-  -- | Action returning a function ending in @r@, some datatype containing-  -- @regs@ and @bean@ values.-  curried ->-  Constructor m bean-pack packer curried = Constructor do runPacker packer <$> do argsN curried---- | Slightly simpler version of 'pack' for @0@-argument functions.-pack0 ::-  (All (Typeable `And` Monoid) regs) =>-  Packer m regs bean r ->-  -- | @0@-argument constructor-  r ->-  Constructor m bean-pack0 packer r = Constructor do Args @'[] \Nil -> runPacker packer r---- | Slightly simpler version of 'pack' for @1@-argument functions.-pack1 ::-  forall arg1 r m regs bean.-  (Typeable arg1, All (Typeable `And` Monoid) regs) =>-  Packer m regs bean r ->-  -- | @1@-argument constructor-  (arg1 -> r) ->-  Constructor m bean-pack1 packer f = Constructor do Args @'[arg1] \(I arg1 :* Nil) -> runPacker packer (f arg1)---- | Slightly simpler version of 'pack' for @2@-argument functions.-pack2 ::-  forall arg1 arg2 r m regs bean.-  (Typeable arg1, Typeable arg2, All (Typeable `And` Monoid) regs) =>-  Packer m regs bean r ->-  -- | @2@-argument constructor-  (arg1 -> arg2 -> r) ->-  Constructor m bean-pack2 packer f = Constructor do Args @[arg1, arg2] \(I arg1 :* I arg2 :* Nil) -> runPacker packer (f arg1 arg2)---- | Slightly simpler version of 'pack' for @3@-argument functions.-pack3 ::-  forall arg1 arg2 arg3 r m regs bean.-  (Typeable arg1, Typeable arg2, Typeable arg3, All (Typeable `And` Monoid) regs) =>-  Packer m regs bean r ->-  -- | @3@-argument constructor-  (arg1 -> arg2 -> arg3 -> r) ->-  Constructor m bean-pack3 packer f = Constructor do Args @[arg1, arg2, arg3] \(I arg1 :* I arg2 :* I arg3 :* Nil) -> runPacker packer (f arg1 arg2 arg3)--nonEmptyToTree :: NonEmpty a -> Tree a-nonEmptyToTree = \case-  a Data.List.NonEmpty.:| [] -> Node a []-  a Data.List.NonEmpty.:| (b : rest) -> Node a [nonEmptyToTree (b Data.List.NonEmpty.:| rest)]--unsafeTreeToNonEmpty :: Tree a -> NonEmpty a-unsafeTreeToNonEmpty = \case-  Node a [] -> a Data.List.NonEmpty.:| []-  Node a [b] -> Data.List.NonEmpty.cons a (unsafeTreeToNonEmpty b)-  _ -> error "tree not list-shaped"---- $setup--- >>> :set -XBlockArguments--- >>> :set -Wno-incomplete-uni-patterns--- >>> import Data.Functor.Identity--- >>> import Data.Function ((&))--- >>> import Data.Monoid+{-# LANGUAGE BlockArguments #-}+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE DeriveAnyClass #-}+{-# LANGUAGE DerivingStrategies #-}+{-# LANGUAGE DerivingVia #-}+{-# LANGUAGE FunctionalDependencies #-}+{-# LANGUAGE GADTs #-}+{-# LANGUAGE LambdaCase #-}+{-# LANGUAGE TypeFamilies #-}+{-# LANGUAGE ViewPatterns #-}+{-# LANGUAGE NoFieldSelectors #-}++-- | This is a library for performing dependency injection. It's an alternative+-- to manually wiring your functions and passing all required parameters+-- explicitly. Instead of that, you throw your functions into a 'Cauldron', which wires+-- them for you, guiding itself by the types.+--+-- Wiring errors are detected at runtime, not at compile time.+--+-- This library should be used at the ["composition root"](https://stackoverflow.com/questions/6277771/what-is-a-composition-root-in-the-context-of-dependency-injection) of the application,+-- and only there: the components we are wiring together need not be aware that the library exists.+--+-- These extensions, while not required, play well with the library:+--+-- @+-- {-# LANGUAGE ApplicativeDo #-} -- For building complex values in the Args applicative.+-- {-# LANGUAGE OverloadedLists #-} -- For avoiding explicit calls to fromRecipeList and fromDecoList+-- @+--+-- An example of using a 'Cauldron' to wire the constructors of dummy @A@, @B@, @C@ datatypes:+--+-- >>> :{+-- data A = A deriving Show+-- data B = B deriving Show+-- data C = C deriving Show+-- makeA :: A+-- makeA = A+-- makeB :: A -> B+-- makeB = \_ -> B+-- makeC :: A -> B -> IO C+-- makeC = \_ _ -> pure C+-- :}+--+-- >>> :{+-- do+--   let cauldron :: Cauldron IO+--       cauldron = [+--           recipe @A $ val $ wire makeA,+--           recipe @B $ val $ wire makeB,+--           recipe @C $ eff $ wire makeC -- we use eff because the constructor has IO effects+--         ]+--   action <- either throwIO pure $ cook forbidDepCycles cauldron+--   beans <- action+--   pure $ taste @C beans+-- :}+-- Just C+module Cauldron+  ( -- * Filling the cauldron+    Cauldron,+    empty,+    insert,+    adjust,+    delete,+    keysSet,+    restrictKeys,+    fromRecipeList,+    toRecipeMap,+    hoistCauldron,+    hoistCauldron',++    -- * Recipes+    Recipe (..),+    ToRecipe,+    fromDecoList,+    (Data.Sequence.|>),+    (Data.Sequence.<|),+    hoistRecipe,+    hoistRecipe',++    -- ** How decorators work+    -- $decos++    -- ** Hiding a 'Recipe''s bean type+    SomeRecipe,+    recipe,+    withRecipe,+    getRecipeCallStack,++    -- * Constructors+    -- $constructors+    Constructor,+    val_,+    val,+    val',+    eff_,+    eff,+    eff',+    wire,+    getConstructorArgs,+    getConstructorCallStack,+    hoistConstructor,+    hoistConstructor',++    -- ** Registering secondary beans+    -- $secondarybeans++    -- * Cooking the beans+    cook,+    cookNonEmpty,+    cookTree,++    -- ** How loopy can we get?+    Fire,+    forbidDepCycles,+    allowSelfDeps,+    allowDepCycles,++    -- ** Tasting the results+    Beans,+    taste,++    -- ** When things go wrong+    RecipeError (..),+    MissingDependencies (..),+    DoubleDutyBeans (..),+    DependencyCycle (..),+    prettyRecipeError,+    prettyRecipeErrorLines,++    -- ** Visualizing dependencies between beans.+    getDependencyGraph,+    DependencyGraph,+    writeAsDot,+    defaultStyle,+    setVertexName,+    BeanConstructionStep (..),+    toAdjacencyMap,++    -- *** Simplifying the dep graph+    -- $simplifygraph+    removeSecondaryBeans,+    removeDecos,+    collapseToPrimaryBeans,+  )+where++import Algebra.Graph.AdjacencyMap (AdjacencyMap)+import Algebra.Graph.AdjacencyMap qualified as Graph+import Algebra.Graph.AdjacencyMap.Algorithm qualified as Graph+import Algebra.Graph.Export.Dot qualified as Dot+import Cauldron.Args+import Cauldron.Beans (SomeMonoidTypeRep (..))+import Cauldron.Beans qualified+import Control.Exception (Exception (..))+import Control.Monad.Fix+import Data.Bifunctor (first)+import Data.ByteString qualified+import Data.Dynamic+import Data.Foldable qualified+import Data.Function ((&))+import Data.Functor ((<&>))+import Data.Functor.Identity (Identity (..))+import Data.Kind+import Data.List qualified+import Data.List.NonEmpty (NonEmpty)+import Data.List.NonEmpty qualified+import Data.Map.Strict (Map)+import Data.Map.Strict qualified as Map+import Data.Maybe (fromJust)+import Data.Semigroup qualified+import Data.Sequence (Seq)+import Data.Sequence qualified+import Data.Set (Set)+import Data.Set qualified as Set+import Data.Text qualified+import Data.Text.Encoding qualified+import Data.Tree+import Data.Type.Equality (testEquality)+import Data.Typeable+import GHC.Exception (CallStack, prettyCallStackLines)+import GHC.IsList+import GHC.Stack (HasCallStack, callStack, withFrozenCallStack)+import Type.Reflection qualified++-- | A map of bean recipes, indexed by the 'TypeRep' of the bean each recipe+-- ultimately produces. Only one recipe is allowed for each bean type.+-- Parameterized by the monad @m@ in which the recipe 'Constructor's might have+-- effects.+type Cauldron :: (Type -> Type) -> Type+newtype Cauldron m where+  Cauldron :: {recipeMap :: Map TypeRep (SomeRecipe m)} -> Cauldron m++empty :: Cauldron m+empty = Cauldron Map.empty++-- | Union of two 'Cauldron's, right-biased: prefers 'Recipe's from the /right/ cauldron when+-- both contain the same key. (Note that 'Data.Map.Map' is left-biased.)+instance Semigroup (Cauldron m) where+  Cauldron {recipeMap = r1} <> Cauldron {recipeMap = r2} = Cauldron do Map.unionWith (flip const) r1 r2++instance Monoid (Cauldron m) where+  mempty = Cauldron Map.empty++instance IsList (Cauldron m) where+  type Item (Cauldron m) = SomeRecipe m+  toList (Cauldron {recipeMap}) = Map.elems recipeMap+  fromList = fromRecipeList++-- | Change the monad used by the 'Recipe's in the 'Cauldron'.+hoistCauldron :: (forall x. m x -> n x) -> Cauldron m -> Cauldron n+hoistCauldron f (Cauldron {recipeMap}) = Cauldron {recipeMap = hoistSomeRecipe f <$> recipeMap}++-- | More general form of 'hoistCauldron' that lets you modify the 'Args'+-- inside all the 'Recipe's in the 'Cauldron'. See 'hoistRecipe''.+hoistCauldron' ::+  -- | Transformation to apply to the base constructor of each recipe.+  (forall x. (Typeable x) => Args (m (Regs x)) -> Args (n (Regs x))) ->+  -- | Transformation to apply to each decorator. Takes the decorator index as parameter.+  (forall x. (Typeable x) => Int -> Args (m (Regs x)) -> Args (n (Regs x))) ->+  Cauldron m ->+  Cauldron n+hoistCauldron' f fds Cauldron {recipeMap} =+  Cauldron+    { recipeMap = Map.map (hoistSomeRecipe' f fds) recipeMap+    }++-- | In order to put recipes producing different bean types into a container, we+-- need to hide each recipe's bean type. This wrapper allows that.+type SomeRecipe :: (Type -> Type) -> Type+data SomeRecipe m where+  SomeRecipe :: (Typeable bean) => {_recipeCallStack :: CallStack, _recipe :: Recipe m bean} -> SomeRecipe m++-- | Build a 'SomeRecipe' from a 'Recipe' or a 'Constructor'. See 'ToRecipe'.+--+-- Useful in combination with 'fromRecipeList'.+recipe ::+  forall {recipelike} {m} bean.+  (ToRecipe recipelike, Typeable bean, HasCallStack) =>+  -- | A 'Recipe' or a 'Constructor'.+  recipelike m bean ->+  SomeRecipe m+recipe theRecipe = withFrozenCallStack do+  SomeRecipe callStack (toRecipe theRecipe)++-- | Access the 'Recipe' inside a 'SomeRecipe'.+withRecipe :: forall {m} r. (forall bean. (Typeable bean) => Recipe m bean -> r) -> SomeRecipe m -> r+withRecipe f (SomeRecipe {_recipe}) = f _recipe++getRecipeRep :: SomeRecipe m -> TypeRep+getRecipeRep = withRecipe go+  where+    go :: forall bean m. (Typeable bean) => Recipe m bean -> TypeRep+    go _ = typeRep (Proxy @bean)++fromRecipeList :: [SomeRecipe m] -> Cauldron m+fromRecipeList =+  foldMap \sr -> Cauldron {recipeMap = Map.singleton (getRecipeRep sr) sr}++toRecipeMap :: Cauldron m -> Map TypeRep (SomeRecipe m)+toRecipeMap Cauldron {recipeMap} = recipeMap++hoistSomeRecipe :: (forall x. m x -> n x) -> SomeRecipe m -> SomeRecipe n+hoistSomeRecipe f r@SomeRecipe {_recipe} = r {_recipe = hoistRecipe f _recipe}++hoistSomeRecipe' ::+  forall m n.+  (forall x. (Typeable x) => Args (m (Regs x)) -> Args (n (Regs x))) ->+  (forall x. (Typeable x) => Int -> Args (m (Regs x)) -> Args (n (Regs x))) ->+  SomeRecipe m ->+  SomeRecipe n+hoistSomeRecipe' f fds sr = withRecipe go sr+  where+    go :: forall bean. (Typeable bean) => Recipe m bean -> SomeRecipe n+    go r = sr {_recipe = hoistRecipe' (f @bean) (fds @bean) r}++-- | Instructions for how to build a value of type @bean@ while possibly+-- performing actions in the monad @m@.+--+-- Because the instructions aren't really run until the 'Cauldron' is 'cook'ed,+-- they can be modified with functions like 'adjust', in order to change the+-- base bean 'Constructor', or add or remove decorators.+type Recipe :: (Type -> Type) -> Type -> Type+data Recipe m bean = Recipe+  { -- | How to build the bean itself.+    bean :: Constructor m bean,+    -- | A 'Data.Sequence.Sequence' of decorators that will wrap the bean. There might be no decorators.+    --+    -- See 'fromDecoList', 'Data.Sequence.|>' and 'Data.Sequence.<|'.+    decos :: Seq (Constructor m bean)+  }++fromDecoList :: [Constructor m bean] -> Seq (Constructor m bean)+fromDecoList = Data.Sequence.fromList++-- | Convenience typeclass that allows passing either 'Recipe's or 'Constructor's+-- to the 'insert' and 'recipe' functions.+type ToRecipe :: ((Type -> Type) -> Type -> Type) -> Constraint+class ToRecipe recipelike where+  toRecipe :: recipelike m bean -> Recipe m bean++-- | Simply identity.+instance ToRecipe Recipe where+  toRecipe = id++-- | 'Constructor' is converted to a 'Recipe' without decorators.+instance ToRecipe Constructor where+  toRecipe bean = Recipe {bean, decos = Data.Sequence.empty}++-- | Change the monad used by the bean\'s main 'Constructor' and its decos.+hoistRecipe :: (forall x. m x -> n x) -> Recipe m bean -> Recipe n bean+hoistRecipe f (Recipe {bean, decos}) =+  Recipe+    { bean = hoistConstructor f bean,+      decos = hoistConstructor f <$> decos+    }++-- | More general form of 'hoistRecipe' that enables precise control over the inner `Args`+-- of each constructor in the 'Recipe'.+hoistRecipe' ::+  -- | Transformation to apply to the base constructor.+  (Args (m (Regs bean)) -> Args (n (Regs bean))) ->+  -- | Transformation to apply to each decorator. Takes the decorator index as parameter.+  (Int -> Args (m (Regs bean)) -> Args (n (Regs bean))) ->+  Recipe m bean ->+  Recipe n bean+hoistRecipe' f fds (Recipe {bean, decos}) =+  Recipe+    { bean = hoistConstructor' f bean,+      decos = Data.Sequence.mapWithIndex (\i deco -> hoistConstructor' (fds i) deco) decos+    }++-- $decos+--+-- Decorators are 'Constructor's which, instead constructing the original+-- version of a bean, they modify it in some way (but without changing its+-- type). Because they modify the bean, typically decorators will take the bean+-- as an argument.+--+-- Decorators can have other dependencies beyond the modified bean.+--+-- When the bean is a record-of-functions, decorators can be used to+-- add behaviors like caching and logging to the functions.+--+-- The order of the decorators in the sequence is the order in which they modify+-- the underlying bean. First decorator wraps first, last decorator wraps last.+--+-- >>> :{+-- newtype Foo = Foo { sayFoo :: IO () }+-- makeFoo :: Foo+-- makeFoo = Foo { sayFoo = putStrLn "foo" }+-- makeFooDeco1 :: Foo -> Foo+-- makeFooDeco1 Foo { sayFoo } = Foo { sayFoo = putStrLn "deco1 enter" >> sayFoo >> putStrLn "deco1 exit" }+-- makeFooDeco2 :: Foo -> IO Foo+-- makeFooDeco2 Foo { sayFoo } = putStrLn "deco2 init" >> pure Foo { sayFoo = putStrLn "deco2 enter" >> sayFoo >> putStrLn "deco2 exit" }+-- :}+--+-- >>> :{+-- do+--   let cauldron :: Cauldron IO+--       cauldron = [+--           recipe @Foo $ Recipe {+--             bean = val $ wire makeFoo,+--             decos = [+--                  val $ wire makeFooDeco1,+--                  eff $ wire makeFooDeco2+--               ]+--           }+--         ]+--   action <- either throwIO pure $ cook forbidDepCycles cauldron+--   beans <- action+--   let Just Foo {sayFoo} = taste beans+--   sayFoo+-- :}+-- deco2 init+-- deco2 enter+-- deco1 enter+-- foo+-- deco1 exit+-- deco2 exit++-- $constructors+--+-- Bean-producing and bean-decorating functions need to be coaxed into 'Constructor's in order to be used in 'Cauldron's.++data ConstructorReps where+  ConstructorReps ::+    { beanRep :: TypeRep,+      argReps :: Set TypeRep,+      regReps :: Map TypeRep Dynamic+    } ->+    ConstructorReps++-- | Put a 'Recipe' into the 'Cauldron'.+--+-- Only one recipe is allowed for each bean type, so 'insert' for a+-- bean will overwrite any previous recipe for that bean.+insert ::+  forall {recipelike} {m} (bean :: Type).+  (Typeable bean, ToRecipe recipelike, HasCallStack) =>+  -- | A 'Recipe' or a 'Constructor'.+  recipelike m bean ->+  Cauldron m ->+  Cauldron m+insert recipelike Cauldron {recipeMap} = withFrozenCallStack do+  let rep = typeRep (Proxy @bean)+  Cauldron {recipeMap = Map.insert rep (SomeRecipe callStack (toRecipe recipelike)) recipeMap}++-- | Tweak a 'Recipe' inside the 'Cauldron', if the recipe exists.+adjust ::+  forall {m} bean.+  (Typeable bean) =>+  (Recipe m bean -> Recipe m bean) ->+  Cauldron m ->+  Cauldron m+adjust f (Cauldron {recipeMap}) = withFrozenCallStack do+  let rep = typeRep (Proxy @bean)+  Cauldron+    { recipeMap =+        recipeMap+          & Map.adjust+            do+              \r@SomeRecipe {_recipe = _recipe :: Recipe m a} ->+                case testEquality (Type.Reflection.typeRep @bean) (Type.Reflection.typeRep @a) of+                  Nothing -> error "should never happen"+                  Just Refl -> r {_recipe = f _recipe}+            rep+    }++delete ::+  forall m.+  TypeRep ->+  Cauldron m ->+  Cauldron m+delete tr Cauldron {recipeMap} =+  Cauldron {recipeMap = Map.delete tr recipeMap}++-- | Strategy for dealing with dependency cycles.+--+-- (The name is admittedly uninformative; the culinary metaphor was stretched too far.)+data Fire m = Fire+  { shouldOmitDependency :: (BeanConstructionStep, BeanConstructionStep) -> Bool,+    followPlanCauldron ::+      Cauldron m ->+      Set TypeRep ->+      Beans ->+      Plan ->+      m Beans+  }++removeBeanFromArgs :: ConstructorReps -> ConstructorReps+removeBeanFromArgs ConstructorReps {argReps, regReps, beanRep} =+  ConstructorReps {argReps = Set.delete beanRep argReps, regReps, beanRep}++-- | Forbid any kind of cyclic dependencies between beans. This is probably what you want.+forbidDepCycles :: (Monad m) => Fire m+forbidDepCycles =+  Fire+    { shouldOmitDependency = \_ -> False,+      followPlanCauldron = \cauldron _secondaryBeanReps initial plan ->+        Data.Foldable.foldlM+          do followPlanStep (\_ -> id) (\_ -> id) cauldron mempty+          initial+          plan+    }++-- | Allow /direct/ self-dependencies.+--+-- A bean constructor might depend on itself. This can be useful for having+-- decorated self-invocations, because the version of the bean received as+-- argument comes \"from the future\" and is already decorated.+--+-- Note that a 'MonadFix' instance is required of the initialization monad.+--+-- __BEWARE__: Pattern-matching too eagerly on a \"bean from the future\" during+-- construction will cause infinite loops or, if you are lucky, throw+-- 'Control.Exception.FixIOException's.+allowSelfDeps :: (MonadFix m) => Fire m+allowSelfDeps =+  Fire+    { shouldOmitDependency = \case+        (BarePrimaryBean bean, PrimaryBean anotherBean) | bean == anotherBean -> True+        _ -> False,+      followPlanCauldron = \cauldron _secondaryBeanReps initial plan ->+        mfix do+          \final ->+            Data.Foldable.foldlM+              do followPlanStep Cauldron.Beans.delete (\_ -> id) cauldron final+              initial+              plan+    }++-- | Allow /any/ kind of dependency cycles.+--+-- Usually comes in handy for creating serializers / deserializers for mutually+-- dependent types.+--+-- Note that a 'MonadFix' instance is required of the initialization monad.+--+-- __BEWARE__: Pattern-matching too eagerly on argument beans during+-- construction will cause infinite loops or, if you are lucky, throw+-- 'Control.Exception.FixIOException's.+allowDepCycles :: (MonadFix m) => Fire m+allowDepCycles =+  Fire+    { shouldOmitDependency = \case+        (BarePrimaryBean _, PrimaryBean _) -> True+        (PrimaryBeanDeco _ _, PrimaryBean _) -> True+        _ -> False,+      followPlanCauldron = \cauldron secondaryBeanReps initial plan -> do+        let makeBareView _ = (`Cauldron.Beans.restrictKeys` secondaryBeanReps)+        let makeDecoView tr = (`Cauldron.Beans.restrictKeys` (Set.insert tr secondaryBeanReps))+        mfix do+          \final ->+            Data.Foldable.foldlM+              do followPlanStep makeBareView makeDecoView cauldron final+              initial+              plan+    }++-- https://discord.com/channels/280033776820813825/280036215477239809/1147832555828162594+-- https://github.com/ghc-proposals/ghc-proposals/pull/126#issuecomment-1363403330++-- | This function DOESN'T return the bean rep itself in the argreps.+constructorReps :: forall {m} bean. (Typeable bean) => Constructor m bean -> ConstructorReps+constructorReps (getConstructorArgs -> c) =+  ConstructorReps+    { beanRep = typeRep (Proxy @bean),+      argReps = getArgsReps c,+      regReps =+        c+          & getRegsReps+          & Set.map (\mtr@(SomeMonoidTypeRep tr) -> Data.Semigroup.Arg (Type.Reflection.SomeTypeRep tr) (toDyn (Cauldron.Beans.someMonoidTypeRepMempty mtr)))+          & Map.fromArgSet+    }++type Plan = [BeanConstructionStep]++-- | A step in the construction of a bean value.+data BeanConstructionStep+  = -- | Undecorated bean.+    BarePrimaryBean TypeRep+  | -- | Apply the decorator with the given index. Comes after the 'BarePrimaryBean' and all 'PrimaryBeanDeco's with a lower index value.+    PrimaryBeanDeco TypeRep Int+  | -- | Final, fully decorated version of a bean. If there are no decorators, comes directly after 'BarePrimaryBean'.+    PrimaryBean TypeRep+  | -- | Beans that are secondary registrations of a 'Constructor' and which are aggregated monoidally.+    SecondaryBean TypeRep+  deriving stock (Show, Eq, Ord)++-- | Build the beans using the recipeMap stored in the 'Cauldron'.+--+-- Any secondary beans that are registered by constructors are aggregated+-- monoidally.+cook ::+  forall m.+  (Monad m) =>+  Fire m ->+  Cauldron m ->+  Either RecipeError (m Beans)+cook fire cauldron =+  fmap @(Either RecipeError) (fmap @m rootLabel) $+    cookTree (Node (fire, cauldron) [])++-- | Cook a nonempty list of 'Cauldron's.+--+-- 'Cauldron's later in the list can see the beans in all previous 'Cauldron's,+-- but not vice versa.+--+-- Beans in a 'Cauldron' have priority over the same beans in previous 'Cauldron's.+cookNonEmpty ::+  forall m.+  (Monad m) =>+  NonEmpty (Fire m, Cauldron m) ->+  Either RecipeError (m (NonEmpty Beans))+cookNonEmpty nonemptyCauldronList = do+  fmap @(Either RecipeError) (fmap @m unsafeTreeToNonEmpty) $+    cookTree (nonEmptyToTree nonemptyCauldronList)++-- | Cook a hierarchy of 'Cauldron's.+--+-- 'Cauldron's down in the branches can see the beans of their ancestor+-- 'Cauldron's, but not vice versa.+--+-- Beans in a 'Cauldron' have priority over the same beans in ancestor 'Cauldron's.+cookTree ::+  forall m.+  (Monad m) =>+  Tree (Fire m, Cauldron m) ->+  Either RecipeError (m (Tree Beans))+cookTree (treecipes) = do+  accumMap <- first DoubleDutyBeansError do checkNoDoubleDutyBeans (snd <$> treecipes)+  () <- first MissingDependenciesError do checkMissingDeps (Map.keysSet accumMap) (snd <$> treecipes)+  treeplan <- first DependencyCycleError do buildPlans (Map.keysSet accumMap) treecipes+  Right $ followPlan (fromDynList (Data.Foldable.toList accumMap)) (treeplan)++newtype DoubleDutyBeans = DoubleDutyBeans (Map TypeRep (CallStack, CallStack))+  deriving stock (Show)++-- | Get a graph of dependencies between 'BeanConstructionStep's. The graph can+-- be obtained even if the 'Cauldron' can't be 'cook'ed successfully.+getDependencyGraph :: Cauldron m -> DependencyGraph+getDependencyGraph cauldron =+  let (accumMap, _) = cauldronRegs cauldron+      (_, deps) = buildDepsCauldron (Map.keysSet accumMap) cauldron+   in DependencyGraph {graph = Graph.edges deps}++checkNoDoubleDutyBeans ::+  Tree (Cauldron m) ->+  Either DoubleDutyBeans (Map TypeRep Dynamic)+checkNoDoubleDutyBeans treecipes = do+  let (accumMap, beanSet) = cauldronTreeRegs treecipes+  let common = Map.intersectionWith (,) (fst <$> accumMap) beanSet+  if not (Map.null common)+    then Left $ DoubleDutyBeans common+    else Right $ snd <$> accumMap++cauldronTreeRegs :: Tree (Cauldron m) -> (Map TypeRep (CallStack, Dynamic), Map TypeRep CallStack)+cauldronTreeRegs = foldMap cauldronRegs++cauldronRegs :: Cauldron m -> (Map TypeRep (CallStack, Dynamic), Map TypeRep CallStack)+cauldronRegs Cauldron {recipeMap} =+  Map.foldMapWithKey+    do \rep aRecipe -> (recipeRegs aRecipe, Map.singleton rep (getRecipeCallStack aRecipe))+    recipeMap++-- | Returns the accumulators, not the main bean+recipeRegs :: SomeRecipe m -> Map TypeRep (CallStack, Dynamic)+recipeRegs (SomeRecipe _ (Recipe {bean, decos})) = do+  let extractRegReps c = (getConstructorCallStack c,) <$> (\ConstructorReps {regReps} -> regReps) (constructorReps c)+  extractRegReps bean+    <> foldMap extractRegReps decos++data MissingDependencies = MissingDependencies CallStack TypeRep (Set TypeRep)+  deriving stock (Show)++checkMissingDeps ::+  -- | accums+  Set TypeRep ->+  Tree (Cauldron m) ->+  Either MissingDependencies ()+checkMissingDeps accums treecipes = do+  let decoratedTreecipes = decorate (Map.empty, treecipes)+      missing =+        decoratedTreecipes <&> \(available, requested) ->+          do checkMissingDepsCauldron accums (Map.keysSet available) requested+  sequence_ missing+  where+    decorate ::+      (Map TypeRep (SomeRecipe m), Tree (Cauldron m)) ->+      Tree (Map TypeRep (SomeRecipe m), Cauldron m)+    decorate = unfoldTree+      do+        \(acc, Node (current@Cauldron {recipeMap}) rest) ->+          let -- current level has priority+              newAcc = recipeMap `Map.union` acc+              newSeeds = do+                z <- rest+                [(newAcc, z)]+           in ((newAcc, current), newSeeds)++checkMissingDepsCauldron ::+  -- | accums+  Set TypeRep ->+  -- | available at this level+  Set TypeRep ->+  Cauldron m ->+  Either MissingDependencies ()+checkMissingDepsCauldron accums available cauldron =+  Data.Foldable.for_ (demandsByConstructorsInCauldron cauldron) \(stack, tr, demanded) ->+    let missing = Set.filter (`Set.notMember` (available `Set.union` accums)) demanded+     in if Set.null missing+          then Right ()+          else Left $ MissingDependencies stack tr missing++demandsByConstructorsInCauldron :: Cauldron m -> [(CallStack, TypeRep, Set TypeRep)]+demandsByConstructorsInCauldron Cauldron {recipeMap} = do+  (tr, SomeRecipe _ (Recipe {bean, decos})) <- Map.toList recipeMap+  ( let ConstructorReps {argReps = beanArgReps} = constructorReps bean+     in [(getConstructorCallStack bean, tr, beanArgReps)]+    )+    ++ do+      decoCon <- Data.Foldable.toList decos+      let ConstructorReps {argReps = decoArgReps} = constructorReps decoCon+       in [(getConstructorCallStack decoCon, tr, decoArgReps)]++newtype DependencyCycle = DependencyCycle (NonEmpty (BeanConstructionStep, Maybe CallStack))+  deriving stock (Show)++buildPlans :: Set TypeRep -> Tree (Fire m, Cauldron m) -> Either DependencyCycle (Tree (Plan, Fire m, Cauldron m))+buildPlans secondary = traverse \(fire@Fire {shouldOmitDependency}, cauldron) -> do+  let (locations, deps) = buildDepsCauldron secondary cauldron+  -- We may omit some dependency edges to allow for cyclic dependencies.+  let graph = Graph.edges $ filter (not . shouldOmitDependency) deps+  case Graph.topSort graph of+    Left recipeCycle ->+      Left $ DependencyCycle $ recipeCycle <&> \step -> (step, Map.lookup step locations)+    Right (reverse -> plan) -> do+      Right (plan, fire, cauldron)++buildDepsCauldron :: Set TypeRep -> Cauldron m -> (Map BeanConstructionStep CallStack, [(BeanConstructionStep, BeanConstructionStep)])+buildDepsCauldron secondary Cauldron {recipeMap} = do+  -- Are we depending on a primary bean, or on a monoidally aggregated secondary bean?+  -- I wonder if we could make this more uniform, it's kind of annoying to have to make this decision here...+  let makeTargetStep :: TypeRep -> BeanConstructionStep+      makeTargetStep rep =+        if rep `Set.member` secondary+          then SecondaryBean rep+          else PrimaryBean rep+  recipeMap+    & Map.foldMapWithKey+      \beanRep+       SomeRecipe+         { _recipeCallStack,+           _recipe =+             Recipe+               { bean = bean :: Constructor m bean,+                 decos+               }+         } ->+          do+            let bareBean = BarePrimaryBean beanRep+                boiledBean = PrimaryBean beanRep+                decoSteps = do+                  (decoIndex, decoCon) <- zip [0 :: Int ..] (Data.Foldable.toList decos)+                  [(PrimaryBeanDeco beanRep decoIndex, decoCon)]+                beanDeps = do+                  constructorEdges makeTargetStep bareBean (constructorReps bean)+                decoDeps = do+                  (decoStep, decoCon) <- decoSteps+                  -- We remove the bean because from the args becase, in the+                  -- case of decos, we want to depend on the in-the-making+                  -- version of the bean, not the completed bean.+                  constructorEdges makeTargetStep decoStep (removeBeanFromArgs do constructorReps decoCon)+                innerSteps = bareBean Data.List.NonEmpty.:| (fst <$> decoSteps) ++ [boiledBean]+                innerDeps =+                  -- This explicit dependency between the completed bean and its+                  -- "bare" undecorated form is not strictly required. It will+                  -- always exist in an indirect manner, through the decorators.+                  -- But it might be useful when rendering the dep graph.+                  (PrimaryBean beanRep, BarePrimaryBean beanRep)+                    :+                    -- The dep chain of completed bean -> decorators -> bare bean.+                    zip (Data.List.NonEmpty.tail innerSteps) (Data.List.NonEmpty.toList innerSteps)+            ( Map.fromList $+                [ (bareBean, getConstructorCallStack bean),+                  (boiledBean, _recipeCallStack)+                ]+                  ++ do+                    (decoStep, decoCon) <- decoSteps+                    [(decoStep, getConstructorCallStack decoCon)],+              beanDeps ++ decoDeps ++ innerDeps+              )++constructorEdges ::+  (TypeRep -> BeanConstructionStep) ->+  BeanConstructionStep ->+  ConstructorReps ->+  [(BeanConstructionStep, BeanConstructionStep)]+constructorEdges makeTargetStep item (ConstructorReps {argReps, regReps}) =+  -- consumers depend on their args+  ( do+      argRep <- Set.toList argReps+      let argStep = makeTargetStep argRep+      [(item, argStep)]+  )+    +++    -- secondary beans depend on their producers+    ( do+        (regRep, _) <- Map.toList regReps+        let repStep = SecondaryBean regRep+        [(repStep, item)]+    )++followPlan ::+  (Monad m) =>+  Beans ->+  (Tree (Plan, Fire m, Cauldron m)) ->+  m (Tree Beans)+followPlan initialBeans treecipes =+  let secondaryBeanReps = Cauldron.Beans.keysSet initialBeans+   in unfoldTreeM+        ( \(previousStageBeans, Node (plan, Fire {followPlanCauldron}, cauldron) rest) -> do+            currentStageBeans <- followPlanCauldron cauldron secondaryBeanReps previousStageBeans plan+            pure (currentStageBeans, (,) currentStageBeans <$> rest)+        )+        (initialBeans, treecipes)++followPlanStep ::+  (Monad m) =>+  (TypeRep -> Beans -> Beans) ->+  (TypeRep -> Beans -> Beans) ->+  Cauldron m ->+  Beans ->+  Beans ->+  BeanConstructionStep ->+  m Beans+followPlanStep makeBareView makeDecoView Cauldron {recipeMap} final super item =+  case item of+    BarePrimaryBean rep -> case fromJust do Map.lookup rep recipeMap of+      SomeRecipe {_recipe = Recipe {bean}} -> do+        let ConstructorReps {beanRep} = constructorReps bean+        -- We delete the beanRep before running the bean,+        -- because if we have a self-dependency, we don't want to use the bean+        -- from a previous context (if it exists) we want the bean from final.+        -- There is a test for this.+        inserter <- followConstructor bean final (makeBareView beanRep super)+        pure do inserter super+    PrimaryBeanDeco rep index -> case fromJust do Map.lookup rep recipeMap of+      SomeRecipe {_recipe = Recipe {decos}} -> do+        let deco = decos `Data.Sequence.index` index+        let ConstructorReps {beanRep} = constructorReps deco+        -- Unlike before, we don't delete the beanRep before running the constructor.+        inserter <- followConstructor deco final (makeDecoView beanRep super)+        pure do inserter super+    -- \| We do nothing here, the work has been done in previous 'BarePrimaryBean' and+    -- 'PrimaryBeanDeco' steps.+    PrimaryBean {} -> pure super+    -- \| We do nothing here, secondary beans are built as a byproduct+    -- of primary beans and decorators.+    SecondaryBean {} -> pure super++-- | Build a bean out of already built beans.+-- This can only work without blowing up if there aren't dependecy cycles+-- and the order of construction respects the depedencies!+followConstructor ::+  (Monad m, Typeable bean) =>+  Constructor m bean ->+  Beans ->+  Beans ->+  m (Beans -> Beans)+followConstructor c final super = do+  (regs, bean) <- runConstructor [super, final] c+  pure \bs ->+    Cauldron.Beans.unionBeansMonoidally (getRegsReps (getConstructorArgs c)) bs regs+      & Cauldron.Beans.insert bean++-- | Sometimes the 'cook'ing process goes wrong.+data RecipeError+  = -- | The 'Cauldron' identified by 'PathToCauldron' has beans+    -- that depend on beans that can't be found either in the current 'Cauldron' or its ancestors.+    MissingDependenciesError MissingDependencies+  | -- | Beans that work both as primary beans and as secondary beans+    -- are disallowed.+    DoubleDutyBeansError DoubleDutyBeans+  | -- | Dependency cycles are disallowed by some 'Fire's.+    DependencyCycleError DependencyCycle+  deriving stock (Show)++instance Exception RecipeError where+  displayException = prettyRecipeError++prettyRecipeError :: RecipeError -> String+prettyRecipeError = Data.List.intercalate "\n" . prettyRecipeErrorLines++prettyRecipeErrorLines :: RecipeError -> [String]+prettyRecipeErrorLines = \case+  MissingDependenciesError+    (MissingDependencies constructorCallStack constructorResultRep missingDependenciesReps) ->+      [ "This constructor for a value of type "+          ++ show constructorResultRep+          ++ ":"+      ]+        ++ (("\t" ++) <$> prettyCallStackLines constructorCallStack)+        ++ [ "is missing the following dependencies:"+           ]+        ++ do+          rep <- Data.Foldable.toList missingDependenciesReps+          ["- " ++ show rep]+  DoubleDutyBeansError (DoubleDutyBeans doubleDutyMap) ->+    [ "The following beans work both as primary beans and secondary beans:"+    ]+      ++ ( flip Map.foldMapWithKey doubleDutyMap \rep (secCS, primCS) ->+             [ "- " ++ show rep ++ " is a secondary bean in this constructor:"+             ]+               ++ (("\t" ++) <$> prettyCallStackLines secCS)+               ++ [ "  and a primary bean in this recipe:"+                  ]+               ++ (("\t" ++) <$> prettyCallStackLines primCS)+         )+  DependencyCycleError (DependencyCycle theCycle) ->+    [ "Forbidden dependency cycle between bean construction steps:"+    ]+      ++ ( flip foldMap theCycle \(step, mstack) ->+             [ "- " ++ case step of+                 BarePrimaryBean rep -> "Bare bean " ++ show rep+                 PrimaryBeanDeco rep i -> "Decorator " ++ show i ++ " for bean " ++ show rep+                 PrimaryBean rep -> "Complete bean " ++ show rep+                 SecondaryBean rep -> "Secondary bean " ++ show rep+             ]+               ++ case mstack of+                 Nothing -> []+                 Just stack -> (("\t" ++) <$> prettyCallStackLines stack)+         )++-- | An edge means that the source depends on the target.+--+-- The dependencies of each bean are given separatedly from its decorators.+newtype DependencyGraph = DependencyGraph {graph :: AdjacencyMap BeanConstructionStep}+  deriving newtype (Show, Eq, Ord, Semigroup, Monoid)++-- | Conversion to a graph type+-- from the+-- [algebraic-graphs](https://hackage.haskell.org/package/algebraic-graphs-0.7/docs/Algebra-Graph-AdjacencyMap.html)+-- library for further processing.+toAdjacencyMap :: DependencyGraph -> AdjacencyMap BeanConstructionStep+toAdjacencyMap DependencyGraph {graph} = graph++-- | Remove all vertices and edges related to secondary beans.+removeSecondaryBeans :: DependencyGraph -> DependencyGraph+removeSecondaryBeans DependencyGraph {graph} =+  DependencyGraph {graph = Graph.induce (\case SecondaryBean {} -> False; _ -> True) graph}++-- | Remove all vertices and edges related to bean decorators.+removeDecos :: DependencyGraph -> DependencyGraph+removeDecos DependencyGraph {graph} =+  DependencyGraph {graph = Graph.induce (\case PrimaryBeanDeco {} -> False; _ -> True) graph}++-- | Unifies 'PrimaryBean's with their respective 'BarePrimaryBean's and 'PrimaryBeanDeco's.+--+-- Also removes any self-loops.+collapseToPrimaryBeans :: DependencyGraph -> DependencyGraph+collapseToPrimaryBeans DependencyGraph {graph} = do+  let simplified =+        Graph.gmap+          ( \case+              BarePrimaryBean rep -> PrimaryBean rep+              PrimaryBeanDeco rep _ -> PrimaryBean rep+              other -> other+          )+          graph+      -- Is there a simpler way to removoe self-loops?+      vertices = Graph.vertexList simplified+      edges = Graph.edgeList simplified+      edgesWithoutSelfLoops =+        filter+          ( \case+              (PrimaryBean source, PrimaryBean target) -> if source == target then False else True+              _ -> True+          )+          edges+  DependencyGraph {graph = Graph.vertices vertices `Graph.overlay` Graph.edges edgesWithoutSelfLoops}++-- | See the [DOT format](https://graphviz.org/doc/info/lang.html).+writeAsDot :: Dot.Style BeanConstructionStep Data.Text.Text -> FilePath -> DependencyGraph -> IO ()+writeAsDot style filepath DependencyGraph {graph} = do+  let dot = Dot.export style graph+  Data.ByteString.writeFile filepath (Data.Text.Encoding.encodeUtf8 dot)++-- | Default DOT rendering style to use with 'writeAsDot'.+-- When a 'RecipeError' exists, is highlights the problematic 'BeanConstructionStep's.+defaultStyle :: Maybe RecipeError -> Dot.Style BeanConstructionStep Data.Text.Text+defaultStyle merr =+  -- https://graphviz.org/docs/attr-types/style/+  -- https://hackage.haskell.org/package/algebraic-graphs-0.7/docs/Algebra-Graph-Export-Dot.html+  (Dot.defaultStyle defaultStepToText)+    { Dot.vertexAttributes = \step -> case merr of+        Nothing -> []+        Just (MissingDependenciesError (MissingDependencies _ _ missing)) ->+          case step of+            PrimaryBean rep+              | Set.member rep missing ->+                  [ Data.Text.pack "style" Dot.:= Data.Text.pack "dashed",+                    Data.Text.pack "color" Dot.:= Data.Text.pack "red"+                  ]+            _ -> []+        Just (DoubleDutyBeansError (DoubleDutyBeans (Map.keysSet -> bs))) ->+          case step of+            PrimaryBean rep+              | Set.member rep bs ->+                  [ Data.Text.pack "style" Dot.:= Data.Text.pack "bold",+                    Data.Text.pack "color" Dot.:= Data.Text.pack "green"+                  ]+            SecondaryBean rep+              | Set.member rep bs ->+                  [ Data.Text.pack "style" Dot.:= Data.Text.pack "bold",+                    Data.Text.pack "color" Dot.:= Data.Text.pack "green"+                  ]+            _ -> []+        Just (DependencyCycleError (DependencyCycle (Set.fromList . Data.Foldable.toList . fmap fst -> cycleStepSet))) ->+          if Set.member step cycleStepSet+            then+              [ Data.Text.pack "style" Dot.:= Data.Text.pack "bold",+                Data.Text.pack "color" Dot.:= Data.Text.pack "blue"+              ]+            else []+    }++-- | Change the default way of how 'BeanConstructionStep's are rendered to text.+setVertexName :: (BeanConstructionStep -> Data.Text.Text) -> Dot.Style BeanConstructionStep Data.Text.Text -> Dot.Style BeanConstructionStep Data.Text.Text+setVertexName vertexName style = style {Dot.vertexName}++defaultStepToText :: BeanConstructionStep -> Data.Text.Text+defaultStepToText =+  let p rep = Data.Text.pack do show rep+   in \case+        BarePrimaryBean rep -> p rep <> Data.Text.pack "#bare"+        PrimaryBeanDeco rep index -> p rep <> Data.Text.pack ("#deco#" ++ show index)+        PrimaryBean rep -> p rep+        SecondaryBean rep -> p rep <> Data.Text.pack "#agg"++nonEmptyToTree :: NonEmpty a -> Tree a+nonEmptyToTree = \case+  a Data.List.NonEmpty.:| [] -> Node a []+  a Data.List.NonEmpty.:| (b : rest) -> Node a [nonEmptyToTree (b Data.List.NonEmpty.:| rest)]++unsafeTreeToNonEmpty :: Tree a -> NonEmpty a+unsafeTreeToNonEmpty = \case+  Node a [] -> a Data.List.NonEmpty.:| []+  Node a [b] -> Data.List.NonEmpty.cons a (unsafeTreeToNonEmpty b)+  _ -> error "tree not list-shaped"++-- | A way of building value of type @bean@, potentially requiring some+-- dependencies, potentially returning some secondary beans+-- along the primary @bean@ result, and also potentially requiring some+-- initialization effect in a monad @m@.+--+-- Note that only the type of the primary @bean@ is reflected in the+-- 'Constructor' type. Those of the dependencies and secondary beans are not.+--+-- A typical initialization monad will be 'IO', used for example to create+-- mutable references that the bean will use internally. Sometimes the+-- constructor will allocate resources with bracket-like operations, and in that+-- case a monad like 'Cauldron.Managed.Managed' might be needed instead.+data Constructor m bean = Constructor+  { _constructorCallStack :: CallStack,+    _args :: Args (m (Regs bean))+  }++-- | Create a 'Constructor' from an 'Args' value that returns a 'bean'.+--+-- Usually, the 'Args' value will be created by 'wire'ing a constructor function.+val_ :: forall bean m. (Applicative m, HasCallStack) => Args bean -> Constructor m bean+val_ x = Constructor callStack $ fmap (pure . pure) x++-- | Like 'val_', but examines the @nested@ value returned by the 'Args' looking+-- for (potentially nested) tuples.  All tuple components except the+-- rightmost-innermost one are registered as secondary beans (if they have+-- 'Monoid' instances, otherwise 'val' won't compile).+val :: forall {nested} bean m. (Registrable nested bean, Applicative m, HasCallStack) => Args nested -> Constructor m bean+val x = withFrozenCallStack (val' $ fmap runIdentity $ register $ fmap Identity x)++-- | Like 'val', but uses an alternative form of registering secondary beans.+-- Less 'Registrable' typeclass magic, but more verbose.+val' :: forall bean m. (Applicative m, HasCallStack) => Args (Regs bean) -> Constructor m bean+val' x = Constructor callStack $ fmap pure x++-- | Create a 'Constructor' from an 'Args' value that returns an initialization+-- effect that produces 'bean'.+--+-- Usually, the 'Args' value will be created by 'wire'ing an effectul constructor function.+eff_ :: forall bean m. (Functor m, HasCallStack) => Args (m bean) -> Constructor m bean+eff_ x = Constructor callStack $ fmap (fmap pure) x++-- | Like 'eff_', but examines the @nested@ value produced by the action+-- returned by the 'Args' looking for (potentially nested) tuples.  All tuple+-- components except the rightmost-innermost one are registered as secondary+-- beans (if they have 'Monoid' instances, otherwise 'eff' won't compile).+eff :: forall {nested} bean m. (Registrable nested bean, Monad m, HasCallStack) => Args (m nested) -> Constructor m bean+eff x = withFrozenCallStack (eff' $ register x)++-- | Like 'eff', but uses an alternative form of registering secondary beans.+-- Less 'Registrable' typeclass magic, but more verbose.+eff' :: forall bean m. (HasCallStack) => Args (m (Regs bean)) -> Constructor m bean+eff' = Constructor callStack++runConstructor :: (Monad m) => [Beans] -> Constructor m bean -> m (Beans, bean)+runConstructor bss (Constructor {_args}) = do+  regs <- _args & runArgs (Data.Foldable.asum (taste <$> bss))+  pure (runRegs (getRegsReps _args) regs)++-- | Change the monad in which the 'Constructor'\'s effects take place.+hoistConstructor :: (forall x. m x -> n x) -> Constructor m bean -> Constructor n bean+hoistConstructor f c@Constructor {_args} = c {_args = fmap f _args}++-- | More general form of 'hoistConstructor' that enables precise control over the inner `Args`.+hoistConstructor' :: (Args (m (Regs bean)) -> Args (n (Regs bean))) -> Constructor m bean -> Constructor n bean+hoistConstructor' f c@Constructor {_args} = c {_args = f _args}++-- | Get the inner 'Args' value for the 'Constructor', typically for inspecting+-- 'TypeRep's of its arguments/registrations.+getConstructorArgs :: Constructor m bean -> Args (m (Regs bean))+getConstructorArgs (Constructor {_args}) = _args++-- | For debugging purposes, 'Constructor's remember the 'CallStack'+-- of when they were created.+getConstructorCallStack :: Constructor m bean -> CallStack+getConstructorCallStack (Constructor {_constructorCallStack}) = _constructorCallStack++-- | For debugging purposes, 'SomeRecipe's remember the 'CallStack'+-- of when they were created.+getRecipeCallStack :: SomeRecipe m -> CallStack+getRecipeCallStack (SomeRecipe {_recipeCallStack}) = _recipeCallStack++-- | The set of all 'TypeRep' keys of the map.+keysSet :: Cauldron m -> Set TypeRep+keysSet Cauldron {recipeMap} = Map.keysSet recipeMap++-- | Restrict a 'Cauldron' to only those 'TypeRep's found in a 'Set'.+restrictKeys :: Cauldron m -> Set TypeRep -> Cauldron m+restrictKeys Cauldron {recipeMap} trs = Cauldron {recipeMap = Map.restrictKeys recipeMap trs}++-- $simplifygraph+--+-- 'DependencyGraph's can get complex and difficult to intepret because they+-- include bean decorators and secondary beans, details in which we many not be+-- interested.+--+-- These functions help simplify 'DependencyGraph's before passing them to+-- 'writeAsDot'. They can be composed between themselves.++-- $secondarybeans+--+-- There is an exception to the 'Cauldron' rule that each bean type can only+-- be produced by a single 'Recipe' in the 'Cauldron'.+--+-- 'Constructor's can produce, besides their \"primary\" bean result,+-- \"secondary\" beans that are not reflected in the 'Constructor' signature.+-- Multiple constructors across different recipeMap can produce secondary beans of the+-- same type.+--+-- Secondary beans are a bit special, in that:+--+-- * The value that is \"seen"\ by a 'Constructor' that depends on a secondary bean+--   is the aggregation of /all/ values produced for that bean in the 'Cauldron'. This+--   means that secondary beans must have 'Monoid' instances, to enable aggregation.+--+-- * When calculating build plan steps for a 'Cauldron', 'Constructor's that depend on a+--   secondary bean come after /all/ of the 'Constructor's that produce that secondary bean.+--+-- * Secondary beans can't be decorated.+--+-- * A bean type can't be primary and secondary at the same time. See 'DoubleDutyBeansError'.+--+-- What are secondary beans useful for?+--+-- * Exposing some uniform control or inspection interface for certain beans.+--+-- * Registering tasks or workers that must be run after application initialization.+--+-- The simplest way of registering secondary beans is to pass an 'Args' value returning a tuple+-- to the 'val' (for pure constructors) or 'eff' (for effectful constructors) functions. Components+-- of the tuple other than the rightmost component are considered secondary beans:+--+-- >>> :{+-- con :: Constructor Identity String+-- con = val $ pure (Sum @Int, All False, "foo")+-- effCon :: Constructor IO String+-- effCon = eff $ pure $ pure @IO (Sum @Int, All False, "foo")+-- :}+--+-- Example of how secondary bean values are accumulated:+--+-- >>> :{+-- data U = U deriving Show+-- data V = V deriving Show+-- makeU :: (Sum Int, U)+-- makeU = (Sum 1, U)+-- makeV :: U -> (Sum Int, V)+-- makeV = \_ -> (Sum 7, V)+-- newtype W = W (Sum Int) deriving Show -- depends on the secondary bean+-- :}+--+-- >>> :{+-- do+--   let cauldron :: Cauldron Identity+--       cauldron = [+--           recipe @U $ val $ wire makeU,+--           recipe @V $ val $ wire makeV,+--           recipe @W $ val $ wire W+--         ]+--   Identity beans <- either throwIO pure $ cook forbidDepCycles cauldron+--   pure $ taste @W beans+-- :}+-- Just (W (Sum {getSum = 8}))++-- $setup+-- >>> :set -XBlockArguments+-- >>> :set -XOverloadedLists+-- >>> :set -Wno-incomplete-uni-patterns+-- >>> import Data.Functor.Identity+-- >>> import Data.Function ((&))+-- >>> import Data.Monoid+-- >>> import Data.Either (either)+-- >>> import Control.Exception (throwIO)
+ lib/Cauldron/Args.hs view
@@ -0,0 +1,434 @@+{-# LANGUAGE ApplicativeDo #-}+{-# LANGUAGE BlockArguments #-}+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE DeriveAnyClass #-}+{-# LANGUAGE DerivingStrategies #-}+{-# LANGUAGE DerivingVia #-}+{-# LANGUAGE FunctionalDependencies #-}+{-# LANGUAGE GADTs #-}+{-# LANGUAGE LambdaCase #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE TypeFamilies #-}+{-# LANGUAGE UndecidableInstances #-}+{-# LANGUAGE NoFieldSelectors #-}++module Cauldron.Args+  ( -- * Arguments+    Args,+    arg,+    runArgs,+    getArgsReps,+    contramapArgs,++    -- ** Reducing 'arg' boilerplate with 'wire'+    Wireable (wire),++    -- ** When a bean is missing+    LazilyReadBeanMissing (..),++    -- * Registrations+    -- $registrations+    Regs,+    foretellReg,+    runRegs,+    getRegsReps,++    -- ** Reducing 'foretellReg' boilerplate with 'register'+    Registrable (register),++    -- * Re-exports+    Beans,+    taste,+    fromDynList,+    SomeMonoidTypeRep,+  )+where++import Cauldron.Beans (Beans, SomeMonoidTypeRep (..), fromDynList, taste)+import Cauldron.Beans qualified+import Control.Exception (Exception, throw)+import Data.Dynamic+import Data.Foldable qualified+import Data.Function ((&))+import Data.Functor ((<&>))+import Data.Kind+import Data.Sequence (Seq)+import Data.Sequence qualified+import Data.Set (Set)+import Data.Set qualified as Set+import Data.Typeable+import Type.Reflection (SomeTypeRep (..))+import Type.Reflection qualified++-- | An 'Applicative' that knows how to construct values by searching in a+-- 'Beans' map, and keeps track of the types that will be searched in the+-- 'Beans' map.+data Args a = Args+  { _argReps :: Set SomeTypeRep,+    _regReps :: Set SomeMonoidTypeRep,+    _runArgs :: (forall t. (Typeable t) => Maybe t) -> a+  }+  deriving stock (Functor)++-- | Look for a type in the 'Beans' map and return its corresponding value.+--+-- >>> :{+-- fun1 :: Bool -> Int+-- fun1 _ = 5+-- w1 :: Args Int+-- w1 = fun1 <$> arg+-- fun2 :: String -> Bool -> Int+-- fun2 _ _ = 5+-- w2 :: Args Int+-- w2 = fun2 <$> arg <*> arg+-- :}+arg :: forall a. (Typeable a) => Args a+arg =+  let tr = typeRep (Proxy @a)+   in Args+        { _argReps = Set.singleton tr,+          _regReps = Set.empty,+          _runArgs = \f ->+            case f @a of+              Just v -> v+              Nothing -> throw (LazilyReadBeanMissing tr)+        }++-- | Here the 'Beans' map is not passed /directly/, instead, we pass a+-- function-like value that, given a type, will return a value of that type or+-- 'Nothing'. Such function is usually constructed using 'taste' on some 'Beans'+-- map.+--+-- >>> :{+-- let beans = fromDynList [toDyn @Int 5]+--  in runArgs (taste beans) (arg @Int)+-- :}+-- 5+--+-- See also 'LazilyReadBeanMissing'.+runArgs :: (forall b. (Typeable b) => Maybe b) -> Args a -> a+runArgs f (Args _ _ _runArgs) =+  -- https://www.reddit.com/r/haskell/comments/16diti/comment/c7vc9ky/+  _runArgs f++-- | Inspect ahead of time what types will be searched in the 'Beans' map.+--+-- >>> :{+-- let beans = fromDynList [toDyn @Int 5, toDyn False]+--     args = (,) <$> arg @Int <*> arg @Bool+--  in (getArgsReps args, runArgs (taste beans) args)+-- :}+-- (fromList [Int,Bool],(5,False))+getArgsReps :: Args a -> Set TypeRep+getArgsReps (Args {_argReps}) = _argReps++-- | Tweak the look-by-type function that is eventually passed to 'runArgs'.+--+-- Unlikely to be commonly useful.+--+-- >>> :{+-- let tweak :: forall t. Typeable t => Maybe t -> Maybe t+--     tweak _ = case Type.Reflection.typeRep @t+--                    `Type.Reflection.eqTypeRep`+--                    Type.Reflection.typeRep @Int of+--                  Just HRefl -> Just 5+--                  Nothing -> Nothing+--  in runArgs (taste Cauldron.Beans.empty) $ contramapArgs tweak $ arg @Int+-- :}+-- 5+contramapArgs :: (forall t. (Typeable t) => Maybe t -> Maybe t) -> Args a -> Args a+contramapArgs tweak args@Args {_runArgs} = args {_runArgs = \f -> _runArgs (tweak f)}++-- | Inspect ahead of time the types of registrations that might be contained in+-- the result value of an 'Args'.+--+-- >>> :{+-- let args = foretellReg @(Sum Int) *> pure ()+--  in getRegsReps args+-- :}+-- fromList [Sum Int]+getRegsReps :: Args a -> Set SomeMonoidTypeRep+getRegsReps (Args {_regReps}) = _regReps++-- | This function is used in an 'Args' context to create a tell-like function+-- that can later be used to register a value into a 'Regs'.+--+-- The type of the future registration must be an instance of 'Monoid'.+--+-- There are no other ways of registering values into 'Regs'.+foretellReg :: forall a. (Typeable a, Monoid a) => Args (a -> Regs ())+foretellReg =+  let tr = SomeMonoidTypeRep (Type.Reflection.typeRep @a)+   in Args+        { _argReps = Set.empty,+          _regReps = Set.singleton tr,+          _runArgs = \_ a -> Regs (Data.Sequence.singleton (toDyn a)) ()+        }++instance Applicative Args where+  pure a =+    Args+      { _argReps = Set.empty,+        _regReps = Set.empty,+        _runArgs = \_ -> a+      }+  Args+    { _argReps = _argReps1,+      _regReps = _regReps1,+      _runArgs = f+    }+    <*> Args+      { _argReps = _argReps2,+        _regReps = _regReps2,+        _runArgs = a+      } =+      Args+        { _argReps = _argReps1 `Set.union` _argReps2,+          _regReps = _regReps1 `Set.union` _regReps2,+          _runArgs = \beans -> (f beans) (a beans)+        }++someMonoidTypeRepToSomeTypeRep :: SomeMonoidTypeRep -> SomeTypeRep+someMonoidTypeRepToSomeTypeRep (SomeMonoidTypeRep tr) = SomeTypeRep tr++-- | A writer-like monad for collecting the values of registrations.+data Regs a = Regs (Seq Dynamic) a+  deriving stock (Functor)++-- | Extract the 'Beans' map of registrations, along with the main result value.+--+-- The 'Set' of 'SomeMonoidTypeRep's will typically come from 'getRegsReps'.+--+-- Only values for 'TypeRep's present in the set will be returned. There will be+-- values for all 'TypeRep's present in the set (some of them might be the+-- 'mempty' for that type).+runRegs :: Set SomeMonoidTypeRep -> Regs a -> (Beans, a)+runRegs monoidReps (Regs dyns a) =+  -- https://www.reddit.com/r/haskell/comments/16diti/comment/c7vc9ky/+  let onlyStaticlyKnown =+        ( manyMemptys monoidReps : do+            dyn <- Data.Foldable.toList dyns+            -- This bit is subtle. I mistakenly used Cauldron.Beans.singleton here+            -- and ended up with the Dynamic type as the *key*. It was hell to debug.+            [fromDynList [dyn]]+        )+          & do foldl (Cauldron.Beans.unionBeansMonoidally monoidReps) (mempty @Beans)+          & do flip Cauldron.Beans.restrictKeys (Set.map someMonoidTypeRepToSomeTypeRep monoidReps)+   in (onlyStaticlyKnown, a)++instance Applicative Regs where+  pure a = Regs Data.Sequence.empty a+  Regs w1 f <*> Regs w2 a2 =+    Regs (w1 Data.Sequence.>< w2) (f a2)++instance Monad Regs where+  (Regs w1 a) >>= k =+    let Regs w2 r = k a+     in Regs (w1 Data.Sequence.>< w2) r++manyMemptys :: Set SomeMonoidTypeRep -> Beans+manyMemptys reps =+  reps+    & Data.Foldable.toList+    <&> Cauldron.Beans.someMonoidTypeRepMempty+    & fromDynList++-- | Imprecise exception that might lie hidden in the result of 'runArgs', if+-- the 'Beans' map lacks a value for some type demanded by the 'Args'.+--+-- Why not make 'runArgs' return a 'Maybe' instead of throwing an imprecise+-- exception? The answer is that, for my purposes, using 'Maybe' or 'Either'+-- caused undesirable strictness when doing weird things like reading values+-- \"from the future\".+--+-- >>> :{+-- runArgs (taste Cauldron.Beans.empty) (arg @Int)+-- :}+-- *** Exception: LazilyReadBeanMissing Int+--+-- If more safety is needed, one can perform additional preliminary checks with+-- the help of 'getArgsReps'.+newtype LazilyReadBeanMissing = LazilyReadBeanMissing TypeRep+  deriving stock (Show)+  deriving anyclass (Exception)++-- | Convenience typeclass for wiring all the arguments of a curried function in one go.+class Wireable curried tip | curried -> tip where+  -- | Takes a curried function and reads all of its arguments by type using+  -- 'arg', returning an 'Args' for the final result value of the function.+  --+  -- >>> :{+  -- fun0 :: Int+  -- fun0 = 5+  -- w0 :: Args Int+  -- w0 = wire fun0+  -- fun1 :: Bool -> Int+  -- fun1 _ = 5+  -- w1 :: Args Int+  -- w1 = wire fun1+  -- fun2 :: String -> Bool -> Int+  -- fun2 _ _ = 5+  -- w2 :: Args Int+  -- w2 = wire fun2+  -- :}+  wire :: curried -> Args tip++instance (Wireable_ (IsFunction curried) curried tip) => Wireable curried tip where+  wire curried = wire_ (Proxy @(IsFunction curried)) do pure curried++class Wireable_ (where_ :: Where) curried tip | where_ curried -> tip where+  wire_ :: Proxy where_ -> Args curried -> Args tip++instance Wireable_ AtTheTip a a where+  wire_ _ r = r++instance (Typeable b, Wireable_ (IsFunction rest) rest tip) => Wireable_ NotYetThere (b -> rest) tip where+  wire_ _ af = wire_ (Proxy @(IsFunction rest)) do af <*> arg @b++type IsFunction :: Type -> Where+type family IsFunction f :: Where where+  IsFunction (_ -> _) = 'NotYetThere+  IsFunction _ = 'AtTheTip++data Where+  = NotYetThere+  | AtTheTip++data WhereNested+  = Tup2+  | Tup3+  | Tup4+  | Innermost++type IsReg :: Type -> WhereNested+type family IsReg f :: WhereNested where+  IsReg (_, _) = 'Tup2+  IsReg (_, _, _) = 'Tup3+  IsReg (_, _, _, _) = 'Tup4+  IsReg _ = 'Innermost++-- | Convenience typeclass for automatically extracting registrations from a value.+-- Counterpart of 'Wireable' for registrations.+class Registrable nested tip | nested -> tip where+  -- | We look for (potentially nested) tuples in the value. All tuple+  -- components except the rightmost-innermost must have 'Monoid' instances, and+  -- are put into a 'Regs'.+  --+  -- >>> :{+  -- args :: Args (Identity (Sum Int, All, String))+  -- args = pure (Identity (Sum 5, All False, "foo"))+  -- registeredArgs :: Args (Identity (Regs String))+  -- registeredArgs = register args+  -- :}+  --+  -- >>> :{+  -- let reps = getRegsReps registeredArgs+  --  in ( reps == Data.Set.fromList [ SomeMonoidTypeRep $ Type.Reflection.typeRep @(Sum Int)+  --                                 , SomeMonoidTypeRep $ Type.Reflection.typeRep @All]+  --     , registeredArgs & runArgs (taste Cauldron.Beans.empty)+  --                      & runIdentity+  --                      & runRegs reps+  --                      & \(beans,_) -> (taste @(Sum Int) beans, taste @All beans)+  --     )+  -- :}+  -- (True,(Just (Sum {getSum = 5}),Just (All {getAll = False})))+  --+  -- Tuples can be nested:+  --+  -- >>> :{+  -- args :: Args (Identity (Sum Int, (All, String)))+  -- args = pure (Identity (Sum 5, (All False, "foo")))+  -- registeredArgs :: Args (Identity (Regs String))+  -- registeredArgs = register args+  -- :}+  --+  -- If there are no tuples in the result type, no values are put into 'Regs'.+  --+  -- >>> :{+  -- args :: Args (Identity String)+  -- args = pure (Identity "foo")+  -- registeredArgs :: Args (Identity (Regs String))+  -- registeredArgs = register args+  -- :}+  register :: forall m. (Functor m) => Args (m nested) -> Args (m (Regs tip))++instance (Registrable_ (IsReg nested) nested tip) => Registrable nested tip where+  register amnested = register_ (Proxy @(IsReg nested)) do fmap (fmap pure) amnested++class Registrable_ (where_ :: WhereNested) nested tip | where_ nested -> tip where+  register_ :: forall m. (Functor m) => Proxy where_ -> Args (m (Regs nested)) -> Args (m (Regs tip))++instance Registrable_ Innermost a a where+  register_ _ = id++instance (Typeable b, Monoid b, Registrable_ (IsReg rest) rest tip) => Registrable_ Tup2 (b, rest) tip where+  register_ _ af =+    register_ (Proxy @(IsReg rest)) do+      tell1 <- foretellReg @b+      action <- af+      pure (action <&> \regs -> regs >>= \(b, rest) -> tell1 b *> pure rest)++instance (Typeable b, Monoid b, Typeable c, Monoid c, Registrable_ (IsReg rest) rest tip) => Registrable_ Tup3 (b, c, rest) tip where+  register_ _ af =+    register_ (Proxy @(IsReg rest)) do+      tell1 <- foretellReg @b+      tell2 <- foretellReg @c+      action <- af+      pure (action <&> \regs -> regs >>= \(b, c, rest) -> tell1 b *> tell2 c *> pure rest)++instance (Typeable b, Monoid b, Typeable c, Monoid c, Typeable d, Monoid d, Registrable_ (IsReg rest) rest tip) => Registrable_ Tup3 (b, c, d, rest) tip where+  register_ _ af =+    register_ (Proxy @(IsReg rest)) do+      tell1 <- foretellReg @b+      tell2 <- foretellReg @c+      tell3 <- foretellReg @d+      action <- af+      pure (action <&> \regs -> regs >>= \(b, c, d, rest) -> tell1 b *> tell2 c *> tell3 d *> pure rest)++-- $registrations+--+-- The 'Args' applicative has an additional feature: it lets you \"register\"+-- ahead of time the types of some values that /might/ be included in the result+-- of the 'Args', but without being reflected in the result type. It's not+-- mandatory that these values must be ultimately produced, however.+--+-- Here's an example. We have an 'Args' value that returns a 'Regs'. While+-- constructing the 'Args' value, we register the @Sum Int@ and @All@ types+-- using 'foretellReg', which also gives us the means of later writing into the+-- 'Regs'. By using 'getRegsReps', we can inspect the 'TypeRep's of the types we+-- registered without having to run the 'Args',+--+-- >>> :{+-- fun2 :: String -> Bool -> Int+-- fun2 _ _ = 5+-- args :: Args (Regs Int)+-- args = do -- Using ApplicativeDo+--   r <- fun2 <$> arg <*> arg -- could also have used 'wire'+--   tell1 <- foretellReg @(Sum Int)+--   tell2 <- foretellReg @All+--   pure $ do+--      tell1 (Sum 11)+--      tell2 (All False)+--      pure r+-- :}+--+-- >>> :{+-- let reps = getRegsReps args+--  in ( reps == Data.Set.fromList [ SomeMonoidTypeRep $ Type.Reflection.typeRep @(Sum Int)+--                                 , SomeMonoidTypeRep $ Type.Reflection.typeRep @All]+--     , args & runArgs (taste $ fromDynList [toDyn @String "foo", toDyn False])+--            & runRegs reps+--            & \(beans,_) -> (taste @(Sum Int) beans, taste @All beans)+--     )+-- :}+-- (True,(Just (Sum {getSum = 11}),Just (All {getAll = False})))++-- $setup+-- >>> :set -XBlockArguments+-- >>> :set -XOverloadedLists+-- >>> :set -XApplicativeDo+-- >>> :set -XGADTs+-- >>> :set -Wno-incomplete-uni-patterns+-- >>> import Data.Functor.Identity+-- >>> import Data.Function ((&))+-- >>> import Data.Monoid
+ lib/Cauldron/Beans.hs view
@@ -0,0 +1,152 @@+{-# LANGUAGE BlockArguments #-}+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE DerivingStrategies #-}+{-# LANGUAGE GADTs #-}+{-# LANGUAGE LambdaCase #-}+{-# LANGUAGE TypeFamilies #-}+{-# LANGUAGE NoFieldSelectors #-}++-- | A map of 'Dynamic' values.+module Cauldron.Beans+  ( Beans,+    empty,+    insert,+    delete,+    restrictKeys,+    keysSet,+    singleton,+    fromDynList,+    toDynMap,++    -- * Looking for values+    taste,++    -- * Monoidal stuff+    unionBeansMonoidally,+    SomeMonoidTypeRep (..),+    someMonoidTypeRepMempty,++    -- * Re-exported+    toDyn,+  )+where++import Data.Dynamic+import Data.Function ((&))+import Data.Functor ((<&>))+import Data.Map.Strict (Map)+import Data.Map.Strict qualified as Map+import Data.Semigroup qualified+import Data.Set (Set)+import Data.Set qualified as Set+import Data.Typeable+import GHC.IsList+import Type.Reflection (SomeTypeRep (..), eqTypeRep)+import Type.Reflection qualified++empty :: Beans+empty = Beans Map.empty++insert :: forall bean. (Typeable bean) => bean -> Beans -> Beans+insert bean Beans {beanMap} =+  Beans {beanMap = Map.insert (typeRep (Proxy @bean)) (toDyn bean) beanMap}++delete :: TypeRep -> Beans -> Beans+delete tr Beans {beanMap} =+  Beans {beanMap = Map.delete tr beanMap}++-- | Restrict a 'Beans' map to only those 'TypeRep's found in a 'Set'.+restrictKeys :: Beans -> Set TypeRep -> Beans+restrictKeys Beans {beanMap} trs = Beans {beanMap = Map.restrictKeys beanMap trs}++singleton :: forall bean. (Typeable bean) => bean -> Beans+singleton bean = Beans do Map.singleton (typeRep (Proxy @bean)) (toDyn bean)++-- | Check if the 'Beans' map contains a value of type @bean@.+taste :: forall bean. (Typeable bean) => Beans -> Maybe bean+taste Beans {beanMap} =+  let tr = Type.Reflection.typeRep @bean+   in case Map.lookup (SomeTypeRep tr) beanMap of+        Just (Dynamic tr' v) | Just HRefl <- tr `eqTypeRep` tr' -> Just v+        _ -> Nothing++-- | A map of 'Dynamic' values, indexed by the 'TypeRep' of each 'Dynamic'.+-- Maintains the invariant that the 'TypeRep' of the key matches the 'TypeRep'+-- of the 'Dynamic'.+newtype Beans = Beans {beanMap :: Map TypeRep Dynamic}+  deriving newtype (Show)++-- | Union of two 'Beans' maps, right-biased: prefers values from the /right/+-- 'Beans' map when both contain the same 'TypeRep' key. (Note that+-- 'Data.Map.Map' is left-biased.)+instance Semigroup Beans where+  Beans {beanMap = r1} <> Beans {beanMap = r2} = Beans do Map.unionWith (flip const) r1 r2++instance Monoid Beans where+  mempty = Beans mempty++instance IsList Beans where+  type Item Beans = Dynamic+  toList (Beans {beanMap}) = Map.elems beanMap+  fromList = fromDynList++-- |+-- >>> :{+-- let beans = fromDynList [toDyn False, toDyn @Int 5]+--  in (taste @Bool beans, taste @Int beans, taste @String beans)+-- :}+-- (Just False,Just 5,Nothing)+fromDynList :: [Dynamic] -> Beans+fromDynList ds = Beans do Map.fromList do ds <&> \d -> (dynTypeRep d, d)++toDynMap :: Beans -> Map TypeRep Dynamic+toDynMap Beans {beanMap} = beanMap++-- | Like 'SomeTypeRep', but also remembering that the type has a 'Monoid' instance, which can be \"recovered\"+-- after pattern-matching on the 'SomeMonoidTypeRep'.+data SomeMonoidTypeRep where+  SomeMonoidTypeRep ::+    forall a.+    (Monoid a) =>+    Type.Reflection.TypeRep a ->+    SomeMonoidTypeRep++instance Show SomeMonoidTypeRep where+  show (SomeMonoidTypeRep tr) = show tr++instance Eq SomeMonoidTypeRep where+  (SomeMonoidTypeRep tr1) == (SomeMonoidTypeRep tr2) =+    (SomeTypeRep tr1) == (SomeTypeRep tr2)++instance Ord SomeMonoidTypeRep where+  (SomeMonoidTypeRep tr1) `compare` (SomeMonoidTypeRep tr2) =+    (SomeTypeRep tr1) `compare` (SomeTypeRep tr2)++-- | The 'mempty' value corresponding to the inner 'Type.Reflection.TypeRep'.+someMonoidTypeRepMempty :: SomeMonoidTypeRep -> Dynamic+someMonoidTypeRepMempty (SomeMonoidTypeRep tr) = Type.Reflection.withTypeable tr (go tr)+  where+    go :: forall t proxy. (Typeable t, Monoid t) => proxy t -> Dynamic+    go _ = toDyn (mempty @t)++-- | Union of to 'Beans' maps. If both share a 'TypeRep' key and the key is+-- present in the 'SomeMonoidTypeRep' 'Set', combine the values monoidally.+-- Otherwise, keep the value from the /second/ 'Beans' map.+unionBeansMonoidally :: Set SomeMonoidTypeRep -> Beans -> Beans -> Beans+unionBeansMonoidally reps (Beans beans1) (Beans beans2) =+  let d =+        reps+          & Set.map (\v@(SomeMonoidTypeRep tr) -> Data.Semigroup.Arg (SomeTypeRep tr) v)+          & Map.fromArgSet+      combine tr d1 d2 =+        case (Map.lookup tr d, d1, d2) of+          (Just (SomeMonoidTypeRep tr'), Dynamic tr1 v1, Dynamic tr2 v2)+            | Just HRefl <- tr' `eqTypeRep` tr1,+              Just HRefl <- tr' `eqTypeRep` tr2 ->+                Type.Reflection.withTypeable tr' (toDyn (v1 <> v2))+          _ -> d2+   in Beans $ Map.unionWithKey combine beans1 beans2++-- | The set of all 'TypeRep' keys of the map.+keysSet :: Beans -> Set TypeRep+keysSet Beans {beanMap} = Map.keysSet beanMap
test/appTests.hs view
@@ -1,3 +1,4 @@+{-# LANGUAGE ApplicativeDo #-} {-# LANGUAGE BlockArguments #-} {-# LANGUAGE DerivingStrategies #-} {-# LANGUAGE DerivingVia #-}@@ -8,7 +9,6 @@ module Main (main) where  import Cauldron-import Data.Function ((&)) import Data.Maybe (fromJust) import Test.Tasty import Test.Tasty.HUnit@@ -65,10 +65,10 @@  data Z = Z deriving (Show) -newtype Inspector = Inspector {inspect :: IO [String]}+newtype Inspector = Inspector {_inspect :: IO [String]}   deriving newtype (Semigroup, Monoid) -newtype Initializer = Initializer {runInitializer :: IO ()}+newtype Initializer = Initializer {_runInitializer :: IO ()}   deriving newtype (Semigroup, Monoid)  makeA :: A@@ -107,37 +107,43 @@ makeZDeco2 :: F -> Z -> (Initializer, Z) makeZDeco2 = \_ z -> (Initializer (putStrLn "Z deco init"), z) -coolWiring :: Fire IO -> Either BadBeans (DependencyGraph, IO (Initializer, Inspector, Z))+coolWiring :: Fire IO -> Either RecipeError (IO Entrypoint) coolWiring fire = do-  let cauldron :: Cauldron IO =-        mempty-          & insert @A do makeBean do pack value makeA-          & insert @B do makeBean do pack (valueWith \(reg, bean) -> regs1 reg bean) do makeB-          & insert @C do makeBean do pack value makeC-          & insert @D do makeBean do pack value makeD-          & insert @E do makeBean do pack value makeE-          & insert @F do makeBean do pack (valueWith \(reg, bean) -> regs1 reg bean) do makeF-          & insert @G-            Bean-              { constructor = pack value do makeG,-                decos =-                  fromConstructors-                    [ pack value do makeGDeco1-                    ]-              }-          & insert @H do makeBean do pack (valueWith \(reg1, reg2, bean) -> regs2 reg1 reg2 bean) do makeH-          & insert @Z-            Bean-              { constructor = pack value do makeZ,-                decos =-                  fromConstructors-                    [ pack value do makeZDeco1,-                      pack (valueWith \(reg, bean) -> regs1 reg bean) do makeZDeco2-                    ]-              }-          & insert @(Initializer, Inspector, Z) do makeBean do pack value do \a b c -> (a, b, c)-  fmap (fmap (fmap (fromJust . taste @(Initializer, Inspector, Z)))) do cook fire cauldron+  fmap (fmap (fromJust . taste @Entrypoint)) $ cook fire cauldron +cauldron :: Cauldron IO+cauldron :: Cauldron IO =+  fromRecipeList+    [ recipe $ val $ pure makeA,+      recipe $ val $ pure makeB,+      recipe $ val $ wire makeC,+      recipe $ val $ wire makeD,+      recipe $ val $ wire makeE,+      -- The type app in val checks the specificity, see val definition.+      recipe @F $ val @F $ wire makeF,+      recipe @G+        Recipe+          { bean = val $ wire makeG,+            decos =+              fromDecoList+                [ val $ wire makeGDeco1+                ]+          },+      recipe @H $ val $ wire makeH,+      recipe @Z+        Recipe+          { bean = val $ wire makeZ,+            decos =+              fromDecoList+                [ val $ wire makeZDeco1,+                  val $ wire makeZDeco2+                ]+          },+      recipe @Entrypoint $ val $ wire Entrypoint+    ]++data Entrypoint = Entrypoint Initializer Inspector Z+ tests :: TestTree tests =   testGroup@@ -149,7 +155,7 @@         pure (),       testCase "dep cycles forbidden" do         case coolWiring forbidDepCycles of-          Left (DependencyCycle _) -> pure ()+          Left (DependencyCycleError _) -> pure ()           Left _ -> assertFailure do "wrong kind of error detected"           Right _ -> assertFailure do "self dependency not detected"         pure ()
+ test/argsTests.hs view
@@ -0,0 +1,72 @@+{-# LANGUAGE ApplicativeDo #-}+{-# LANGUAGE BlockArguments #-}+{-# LANGUAGE DerivingStrategies #-}+{-# LANGUAGE DerivingVia #-}+{-# LANGUAGE DuplicateRecordFields #-}+{-# LANGUAGE OverloadedLists #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE NoFieldSelectors #-}++module Main (main) where++import Cauldron+import Cauldron.Args+import Control.Exception+import Data.Dynamic+import Data.Function ((&))+import Data.Proxy+import Data.Text (Text)+import Data.Typeable (typeRep)+import Test.Tasty+import Test.Tasty.HUnit++data A = A++data B = B++data C = C++makeC :: A -> B -> ([Text], C)+makeC _ _ = (["monoid"], C)++argsForC :: Args (Regs C)+argsForC = do+  ~(reg1, bean) <- makeC <$> arg <*> arg+  tell1 <- foretellReg+  pure do+    tell1 reg1+    pure bean++data L1 = L1++data L2 = L2++makeL2 :: L1 -> L2+makeL2 !L1 = L2++throwyArgs :: Args L2+throwyArgs = makeL2 <$> arg++tests :: TestTree+tests =+  testGroup+    "All"+    [ testCase "withRegs" do+        let (beans, C) =+              argsForC+                & runArgs (taste $ fromDynList [toDyn A, toDyn B])+                & runRegs (getRegsReps argsForC)+        Just m <- pure do taste @[Text] beans+        assertEqual+          "monoid"+          ["monoid"]+          m,+      testCase "throwy" do+        r <- try $ evaluate $ runArgs Nothing throwyArgs+        case r of+          Left (LazilyReadBeanMissing tr) | tr == (typeRep (Proxy @L1)) -> pure ()+          _ -> assertFailure "expected exception did not happen"+    ]++main :: IO ()+main = defaultMain tests
+ test/codecTests.hs view
@@ -0,0 +1,161 @@+{-# LANGUAGE ApplicativeDo #-}+{-# LANGUAGE BlockArguments #-}+{-# LANGUAGE DerivingStrategies #-}+{-# LANGUAGE DerivingVia #-}+{-# LANGUAGE LambdaCase #-}+{-# LANGUAGE OverloadedLists #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE NoFieldSelectors #-}++module Main (main) where++import Cauldron+import Data.Foldable qualified+import Data.Functor.Identity+import Data.Monoid+import Test.Tasty+import Test.Tasty.HUnit++data Foo+  = EndFoo+  | FooToBar Bar+  deriving stock (Show)++data Bar+  = EndBar+  | BarToFoo Foo+  | BarToBaz Baz+  deriving stock (Show)++data Baz+  = EndBaz+  | BazToFoo Foo+  deriving stock (Show)++newtype Serializer a = Serializer {runSerializer :: a -> String}++makeFooSerializer :: Serializer Bar -> Serializer Foo+makeFooSerializer Serializer {runSerializer = runBar} =+  Serializer+    { runSerializer = \case+        EndFoo -> ".EndFoo"+        FooToBar bar -> ".FooToBar" ++ runBar bar+    }++makeBarSerializer :: Serializer Foo -> Serializer Baz -> Serializer Bar+makeBarSerializer Serializer {runSerializer = runFoo} Serializer {runSerializer = runBaz} =+  Serializer+    { runSerializer = \case+        EndBar -> ".EndBar"+        BarToFoo foo -> ".BarToFoo" ++ runFoo foo+        BarToBaz baz -> ".BarToBar" ++ runBaz baz+    }++makeBazSerializer :: Serializer Foo -> Serializer Baz+makeBazSerializer Serializer {runSerializer = runFoo} =+  Serializer+    { runSerializer = \case+        EndBaz -> ".EndBaz"+        BazToFoo foo -> ".BazToFoo" ++ runFoo foo+    }++cauldron :: Cauldron Identity+cauldron =+  fromRecipeList+    [ recipe @(Serializer Foo) $ val $ wire makeFooSerializer,+      recipe @(Serializer Bar) $ val $ wire makeBarSerializer,+      recipe @(Serializer Baz) $ val $ wire makeBazSerializer+    ]++newtype Acc = Acc Int+  deriving stock (Show)+  deriving stock (Eq)+  deriving (Semigroup, Monoid) via Sum Int++newtype Bcc = Bcc Int+  deriving stock (Show)+  deriving stock (Eq)+  deriving (Semigroup, Monoid) via Sum Int++cauldronAccums1 :: Cauldron Identity+cauldronAccums1 =+  fromRecipeList+    [ recipe @(Serializer Foo) $ val $ wire $ \sb -> (Acc 5, makeFooSerializer sb),+      recipe @(Serializer Bar) $ val $ wire $ \sf sb -> (Acc 3, makeBarSerializer sf sb),+      recipe @(Serializer Baz) $ val $ wire $ \sf -> (Acc 7, makeBazSerializer sf)+    ]++cauldronAccums2 :: Cauldron Identity+cauldronAccums2 =+  fromRecipeList+    [ recipe @(Serializer Foo) $ val $ wire $ \(_ :: Acc) sb -> makeFooSerializer sb,+      recipe @(Serializer Bar) $ val $ wire $ \sf sb -> (Acc 3, makeBarSerializer sf sb),+      recipe @(Serializer Baz) $ val $ wire $ \sf -> (Acc 7, makeBazSerializer sf)+    ]++cauldronAccumsOops1 :: Cauldron Identity+cauldronAccumsOops1 =+  fromRecipeList+    [ recipe @(Serializer Foo) $ val $ wire $ \(_ :: Acc) sb -> (Acc 5, makeFooSerializer sb),+      recipe @(Serializer Bar) $ val $ wire $ \sf sb -> (Acc 3, makeBarSerializer sf sb),+      recipe @(Serializer Baz) $ val $ wire $ \sf -> (Acc 7, makeBazSerializer sf)+    ]++cauldronAccumsOops2 :: Cauldron Identity+cauldronAccumsOops2 =+  fromRecipeList+    [ recipe @(Serializer Foo) $ val $ wire $ \(_ :: Acc) sb -> (Bcc 5, makeFooSerializer sb),+      recipe @(Serializer Bar) $ val $ wire $ \(_ :: Bcc) sf sb -> (Acc 5, makeBarSerializer sf sb),+      recipe @(Serializer Baz) $ val $ wire $ \sf -> (Acc 7, makeBazSerializer sf)+    ]++tests :: TestTree+tests =+  testGroup+    "All"+    [ testCase "successful cyclic wiring" do+        case cook allowDepCycles cauldron of+          Left _ -> do+            -- putStrLn $ prettyRecipeError err+            assertFailure "could not wire"+          Right (Identity bs) ->+            case taste bs of+              Nothing -> assertFailure "serializer not found"+              Just (Serializer {runSerializer}) -> do+                let value = FooToBar (BarToFoo (FooToBar (BarToBaz EndBaz)))+                assertEqual "experted result" ".FooToBar.BarToFoo.FooToBar.BarToBar.EndBaz" (runSerializer value),+      testCase "should fail cycle wiring" do+        Data.Foldable.for_ @[] [("forbid", forbidDepCycles), ("selfdeps", allowSelfDeps)] \(name, fire) ->+          case cook fire cauldron of+            Left (DependencyCycleError _) -> pure ()+            Left _ -> assertFailure $ "Unexpected error when wiring" ++ name+            Right _ -> assertFailure $ "Unexpected success when wiring" ++ name,+      testCase "cyclic wiring with accums" do+        Data.Foldable.for_ @[]+          [ ("normal", cauldronAccums1, Acc 15),+            ("someConsume", cauldronAccums2, Acc 10)+          ]+          \(name, c, expected) ->+            case cook allowDepCycles c of+              Left _err -> do+                -- putStrLn $ prettyRecipeError err+                assertFailure $ "could not wire " ++ name+              Right (Identity bs) ->+                case taste bs of+                  Nothing -> assertFailure $ "accum not found " ++ name+                  Just (acc :: Acc) -> do+                    assertEqual "experted result" expected acc,+      testCase "problematic wiring with accums" do+        Data.Foldable.for_ @[]+          [ ("selfacc", cauldronAccumsOops1),+            ("indirectacc", cauldronAccumsOops2)+          ]+          \(name, c) ->+            case cook allowDepCycles c of+              Left (DependencyCycleError _) -> pure ()+              Left _ -> assertFailure $ "Unexpected error when wiring" ++ name+              Right _ -> assertFailure $ "Unexpected success when wiring" ++ name+    ]++main :: IO ()+main = defaultMain tests
test/managedTests.hs view
@@ -8,7 +8,6 @@  import Cauldron import Cauldron.Managed-import Data.Function ((&)) import Data.IORef import Data.Maybe (fromJust) import Data.Text (Text)@@ -73,17 +72,19 @@  managedCauldron :: IORef [Text] -> Cauldron Managed managedCauldron ref =-  emptyCauldron-    & insert @(Logger IO) do makeBean do pack effect do managed (makeLogger ref)-    & insert @(Weird IO)-      Bean-        { constructor = pack effect do \logger self -> managed (makeSelfInvokingWeird ref logger self),-          decos =-            fromConstructors-              [ pack value makeWeirdDecorator-              ]-        }-    & insert @(Logger IO, Weird IO) do makeBean do pack value do (,)+  fromRecipeList+    [ recipe @(Logger IO) $ eff $ wire $ managed (makeLogger ref),+      recipe @(Weird IO)+        Recipe+          { bean = eff do+              wire \logger self -> managed (makeSelfInvokingWeird ref logger self),+            decos =+              fromDecoList+                [ val $ wire makeWeirdDecorator+                ]+          },+      recipe @(Logger IO, Weird IO) $ val_ do wire (,)+    ]  tests :: TestTree tests =@@ -93,7 +94,7 @@         ref <- newIORef []         case cook allowSelfDeps (managedCauldron ref) of           Left _ -> assertFailure "could not wire"-          Right (_, beansAction) -> with beansAction \boiledBeans -> do+          Right beansAction -> with beansAction \boiledBeans -> do             let (Logger {logMessage}, (Weird {anotherWeirdOp}) :: Weird IO) = fromJust . taste $ boiledBeans             logMessage "foo"             anotherWeirdOp
test/tests.hs view
@@ -1,15 +1,20 @@+{-# LANGUAGE ApplicativeDo #-} {-# LANGUAGE BlockArguments #-} {-# LANGUAGE DerivingStrategies #-} {-# LANGUAGE DerivingVia #-}+{-# LANGUAGE OverloadedLists #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE NoFieldSelectors #-}  module Main (main) where +import Algebra.Graph.AdjacencyMap import Cauldron+import Control.Monad import Control.Monad.IO.Class import Control.Monad.Trans.Writer import Data.Function ((&))+import Data.Functor.Identity import Data.IORef import Data.List.NonEmpty (NonEmpty) import Data.List.NonEmpty qualified@@ -17,7 +22,11 @@ import Data.Map qualified as Map import Data.Maybe (fromJust) import Data.Monoid+import Data.Proxy+import Data.Set qualified import Data.Text (Text)+import Data.Tree+import Data.Typeable (typeRep) import Test.Tasty import Test.Tasty.HUnit @@ -106,51 +115,81 @@  cauldron :: Cauldron M cauldron =-  mempty-    & insert @(Logger M) do makeBean do pack (Packer do fmap (\(reg, bean) -> regs1 reg bean)) do makeLogger-    & insert @(Repository M) do makeBean do pack (Packer do fmap (\(reg, bean) -> regs1 reg bean)) do makeRepository-    & insert @(Initializer, Repository M) do makeBean do pack value do \a b -> (a, b)+  fromRecipeList+    [ recipe @(Logger M) $ eff $ pure makeLogger,+      recipe @(Repository M) $ eff $ wire makeRepository,+      recipe @(Initializer, Repository M) $ val_ $ wire (,)+    ]  cauldronMissingDep :: Cauldron M cauldronMissingDep =   cauldron-    & delete @(Logger M)+    & delete (typeRep (Proxy @(Logger M)))  cauldronDoubleDutyBean :: Cauldron M cauldronDoubleDutyBean =   cauldron-    & insert @Initializer do makeBean do pack value do do (Initializer (pure ()))+    & insert @Initializer (val $ pure (Initializer (pure ())))  cauldronWithCycle :: Cauldron M cauldronWithCycle =   cauldron-    & insert @(Logger M) do makeBean do pack (Packer do fmap \(reg, bean) -> regs1 reg bean) do const @_ @(Repository M) makeLogger+    & insert @(Logger M)+      (eff $ wire \(_ :: Repository M) -> makeLogger)  cauldronNonEmpty :: NonEmpty (Cauldron M) cauldronNonEmpty =   Data.List.NonEmpty.fromList-    [ mempty-        & do-          let packer = Packer do fmap (\(reg, bean) -> regs1 reg bean)-          insert @(Logger M) do makeBean do pack packer do makeLogger-        & insert @(Weird M) do makeBean do pack effect makeWeird,-      mempty-        & insert @(Repository M) do makeBean do pack (Packer do fmap (\(reg, bean) -> regs1 reg bean)) do makeRepository-        & insert @(Weird M)-          Bean-            { constructor = pack effect makeSelfInvokingWeird,-              decos =-                fromConstructors-                  [ pack value do weirdDeco "inner",-                    pack value do weirdDeco "outer"-                  ]-            }-        & insert @(Initializer, Repository M, Weird M) do makeBean do pack value do \a b c -> (a, b, c)+    [ fromRecipeList+        [ recipe @(Logger M) $ eff $ pure makeLogger,+          recipe @(Weird M) $ eff $ wire makeWeird+        ],+      fromRecipeList+        [ recipe @(Repository M) $ eff $ do+            action <- wire makeRepository+            pure do+              (initializer, repo) <- action+              pure (initializer, repo),+          recipe @(Weird M)+            Recipe+              { bean = eff $ wire makeSelfInvokingWeird,+                decos =+                  fromDecoList+                    [ val $ wire (weirdDeco "inner"),+                      val $ wire (weirdDeco "outer")+                    ]+              },+          recipe @(Initializer, Repository M, Weird M) $ val_ do wire (,,)+        ]     ]  cauldronLonely :: Cauldron M-cauldronLonely = emptyCauldron & insert @(Lonely M) do makeBean do pack0 value makeLonely+cauldronLonely =+  fromRecipeList+    [ recipe @(Lonely M) $ val $ pure makeLonely+    ] +data A = A++makeA :: (Sum Int, A)+makeA = (Sum 1, A)++data B = B++makeB :: A -> (Sum Int, B)+makeB _ = (Sum 7, B)++data C = C++makeC :: A -> (Sum Int, C)+makeC _ = (Sum 11, C)++treeOfCauldrons :: Tree (Cauldron Identity)+treeOfCauldrons =+  Node+    [recipe $ val $ wire makeA]+    [Node [recipe $ val $ wire makeB] [], Node [recipe $ val $ wire makeC] []]+ tests :: TestTree tests =   testGroup@@ -158,7 +197,7 @@     [ testCase "value" do         (_, traces) <- case cook' cauldron of           Left _ -> assertFailure "could not wire"-          Right (_, beansAction) -> runWriterT do+          Right beansAction -> runWriterT do             boiledBeans <- beansAction             let (Initializer {runInitializer}, Repository {findById, store}) = fromJust . taste $ boiledBeans             runInitializer@@ -175,19 +214,20 @@           ]           traces,       testCase "value sequential" do-        (_, traces) <- case cookNonEmpty' cauldronNonEmpty of+        ((), traces) <- case cookNonEmpty' cauldronNonEmpty of           Left _ -> assertFailure "could not wire"-          Right (_, beansAction) -> runWriterT do-            _ Data.List.NonEmpty.:| [boiledBeans] <- beansAction-            let ( Initializer {runInitializer},-                  Repository {findById, store},-                  Weird {anotherWeirdOp}-                  ) = fromJust . taste $ boiledBeans-            runInitializer-            store 1 "foo"-            _ <- findById 1-            anotherWeirdOp-            pure ()+          Right beansAction -> do+            runWriterT do+              _ Data.List.NonEmpty.:| [boiledBeans] <- beansAction+              let ( Initializer {runInitializer},+                    Repository {findById, store},+                    Weird {anotherWeirdOp}+                    ) = fromJust . taste $ boiledBeans+              runInitializer+              store 1 "foo"+              _ <- findById 1+              anotherWeirdOp+              pure ()         assertEqual           "traces"           [ "logger constructor",@@ -206,11 +246,31 @@             -- note that the self-invocation used the method from 'makeSelfInvokingWeird'             "weirdOp 2"           ]-          traces,-      testCase "lonely beans get build" do+          traces+        case getDependencyGraph <$> cauldronNonEmpty of+          dg1 Data.List.NonEmpty.:| [dg2] -> do+            let _adj1 = toAdjacencyMap dg1+            let adj2 = toAdjacencyMap dg2+            unless (hasVertex (PrimaryBean (typeRep (Proxy @(Logger M)))) adj2) do+              assertFailure "cauldron 2 doesn't have the fully built logger from cauldron 1 in its dep graph"+            when (hasVertex (BarePrimaryBean (typeRep (Proxy @(Logger M)))) adj2) do+              assertFailure "cauldron 2 has the bare undecorated logger from cauldron 1 in its dep graph, despite not depending on it directly"+            pure ()+          _ -> assertFailure "should never happen, malformed test",+      testCase "tree of cauldrons" do+        case cookTree' treeOfCauldrons of+          Left err -> assertFailure $ "failed to build tree: " ++ show err+          Right (Identity beans) -> case beans of+            Node bbase [Node bbranch1 [], Node bbranch2 []] ->+              assertEqual+                "expected accs across brances"+                (Just (Sum 1), Just (Sum 8), Just (Sum 12))+                (taste @(Sum Int) bbase, taste @(Sum Int) bbranch1, taste @(Sum Int) bbranch2)+            _ -> assertFailure $ "tree has unexpected shape",+      testCase "lonely beans get built" do         (_, _) <- case cook' cauldronLonely of           Left _ -> assertFailure "could not wire"-          Right (_, beansAction) -> runWriterT do+          Right beansAction -> runWriterT do             boiledBeans <- beansAction             let Lonely {soLonely} = fromJust . taste $ boiledBeans             soLonely@@ -218,25 +278,25 @@         pure (),       testCase "cauldron missing dep" do         case cook' cauldronMissingDep of-          Left (MissingDependencies [] missingMap)-            | Map.size missingMap == 1 -> pure ()-            | otherwise -> assertFailure "missing dependency error has too many entries"+          Left (MissingDependenciesError (MissingDependencies _ tr missingSet))+            | tr == typeRep (Proxy @(Repository M)) && missingSet == Data.Set.singleton (typeRep (Proxy @(Logger M))) -> pure ()           _ -> assertFailure "missing dependency not detected"         pure (),       testCase "cauldron with double duty bean" do         case cook' cauldronDoubleDutyBean of-          Left (DoubleDutyBeans _) -> pure ()+          Left (DoubleDutyBeansError _) -> pure ()           _ -> assertFailure "double duty beans not detected"         pure (),       testCase "cauldron with cycle" do         case cook' cauldronWithCycle of-          Left (DependencyCycle _) -> pure ()+          Left (DependencyCycleError _) -> pure ()           _ -> assertFailure "dependency cycle not detected"         pure ()     ]   where     cook' = cook allowSelfDeps     cookNonEmpty' = cookNonEmpty . fmap (allowSelfDeps,)+    cookTree' = cookTree . fmap (allowSelfDeps,)  main :: IO () main = defaultMain tests