BSD-3-Clause licensed by
Maintained by Daniel Díaz
This version can be pinned in stack with:cauldron-0.9.0.1@sha256:4f7e2d1d1c55ee4fea9120f1ace156546fd901764af863e70a9efc5aa06ef626,2557

Module documentation for 0.9.0.1

Used by 1 package in lts-24.0(full list with versions):

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. 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. See here for a comparison of cauldron vs. manual in wiring a not-completely trivial app.

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. 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 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.

Aggregate beans

More complex constructors can return—besides a “primary” bean as seen in the previous section—one or more secondary “aggregate” 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 final aggregated value of an aggregate bean by taking the bean as a regular argument. Here, makeDebuggingServer receives the mappended value of all the Inspectors 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 (besides the decorated bean itself), perform effects, and register aggregate beans:

makeServerDecorator :: Logger -> Server -> IO (Initializer,Server)

Example code

See this example application with dummy components.

For a slightly more realistic example, see here.

Similarities with the Java Spring framework IoC container

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, 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 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 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 correspond to taking an “incomplete” set of constructors where not all constructor dependencies can be satisfied inside the set, and turning it into a single constructor which takes the missing dependencies as arguments, and can be made part of a wider set of constructors. The missing dependencies will then be read from that wider set.

  • injecting all the beans that implement a certain interface as a list roughly corresponds to a constructor that takes a aggregate bean as an argument.

Some features I’m not yet sure how to mimic:

  • bean scopes, like request scope. This Stack Overflow post 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.

See also

Acknowledgements

This package contains vendored code from Gabriella Gonzalez’s managed library.

Also vendored code from Andrey Mokhov’s algebraic-graphs (most of the cauldron:graph library).

Changes

Revision history for cauldron

0.9.0.1

  • compat with GHC 9.8.4.

0.9.0.0

  • breaking change: SomeRecipe type hidden.
  • breaking change: recipe now produces a Cauldron, not a SomeRecipe.
  • breaking change: withRecipe is now lookup and works on Cauldrons.
  • breaking change: withRecipeCallstack gone. Use lookup.
  • breaking change: IsList instance for Cauldron has changed its Item type.
  • breaking change: the bean field of Recipe is now called bare.
  • new (|=|) and (䷱) operators.

0.8.1.0

  • Add Cauldron.Managed.runManaged.

  • Monoid and Semigroup instances for Managed, like the ones from the managed library.

  • Export Cauldron.Args.Args from Cauldron. This should make some IDE messages clearer.

0.8.0.0

  • doc and test changes.

  • re-export arg from Cauldron.

  • breaking change: MissingDependenciesError now includes all the missing dependencies.

  • breaking change: DoubleDutyBeansError is now a NonEmpty instead of a Map.

  • Managed now has a MonadFail instance, like the one from the managed library.

0.7.0.0

  • Remove dependency on algebraic-graphs, copying those parts of the code that we used.
  • Remove cookTree and cookNonEmpty.
  • Added nest.
  • cook is now “typed”: we pass the type of the bean we want to extract.
  • RecipeError -> CookingError.
  • Renamed PrimaryBean to FinishedBean.
  • Renamed SecondaryBean to AggregateBean.
  • Now the Constructors don’t depend directly on SecondaryBean/AggregateBean. There is a PrimaryBean/FinishedBean that points to the SecondaryBean/AggregateBean, and Constructors depend on that.
  • Rename collapseToPrimaryBeans to collapseBeans.
  • Rename removeSecondaryBeans to removeAggregates.

0.6.1.0

  • ioEff added to Cauldron.
  • New module Cauldron.Builder.

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 Cauldrons.

  • BoiledBeans is now just Beans and has its own module.

  • A new Cauldron.Args module which defines the Args applicative.

  • The way of creating Constructors has been overhauled.

    Packer and pack are gone, along with value, eff and similar functions. The old Regs type is gone.

    To create Constructors, 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 Constructors 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 Constructors and Recipes 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.

0.3.1.0

  • Now the MissingDependencies only includes keys with actual missing dependencies.

0.3.0.0

  • Add cookNonEmpty and cookTree for cooking hierarchies of ‘Cauldron’s.
  • Rename addLast to addOuter and addFirst to addInner.
  • Add a copy of the Managed type from “managed”.
  • Change the nomenclature of the pack- related functions.
  • Add the Packer type.
  • Add Fire type to customize the handling of dependency cycles.

0.2.0.0

  • Decorators are no longer Endos. They just take the decorated entity as a regular parameter.

  • Remove the applicative wrappers.

  • Allow effectful constructors.

0.1.0.0 – YYYY-mm-dd

  • First version. Released on an unsuspecting world.