1{-# LANGUAGE BlockArguments #-}
2{-# LANGUAGE DataKinds #-}
3{-# LANGUAGE DeriveAnyClass #-}
4{-# LANGUAGE DerivingStrategies #-}
5{-# LANGUAGE DerivingVia #-}
6{-# LANGUAGE FunctionalDependencies #-}
7{-# LANGUAGE GADTs #-}
8{-# LANGUAGE LambdaCase #-}
9{-# LANGUAGE TypeFamilies #-}
10{-# LANGUAGE ViewPatterns #-}
11{-# LANGUAGE NoFieldSelectors #-}
12
13-- | This is a library for performing dependency injection. It's an alternative
14-- to manually wiring your functions and passing all required parameters
15-- explicitly. Instead of that, you throw your functions into a 'Cauldron', which wires
16-- them for you, guiding itself by the types.
17--
18-- Wiring errors are detected at runtime, not at compile time.
19--
20-- 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,
21-- and only there: the components we are wiring together need not be aware that the library exists.
22--
23-- These extensions, while not required, play well with the library:
24--
25-- @
26-- {-# LANGUAGE ApplicativeDo #-} -- For building complex values in the Args applicative.
27-- {-# LANGUAGE OverloadedLists #-} -- For avoiding explicit calls to fromRecipeList and fromDecoList
28-- @
29--
30-- An example of using a 'Cauldron' to wire the constructors of dummy @A@, @B@, @C@ datatypes:
31--
32-- >>> :{
33-- data A = A deriving Show
34-- data B = B deriving Show
35-- data C = C deriving Show
36-- makeA :: A
37-- makeA = A
38-- makeB :: A -> B
39-- makeB = \_ -> B
40-- makeC :: A -> B -> IO C
41-- makeC = \_ _ -> pure C
42-- :}
43--
44-- >>> :{
45-- do
46-- let cauldron :: Cauldron IO
47-- cauldron = [
48-- recipe @A $ val $ wire makeA,
49-- recipe @B $ val $ wire makeB,
50-- recipe @C $ eff $ wire makeC -- we use eff because the constructor has IO effects
51-- ]
52-- action <- either throwIO pure $ cook forbidDepCycles cauldron
53-- beans <- action
54-- pure $ taste @C beans
55-- :}
56-- Just C
57module Cauldron
58 ( -- * Filling the cauldron
59 Cauldron,
60 empty,
61 insert,
62 adjust,
63 delete,
64 keysSet,
65 restrictKeys,
66 fromRecipeList,
67 toRecipeMap,
68 hoistCauldron,
69 hoistCauldron',
70
71 -- * Recipes
72 Recipe (..),
73 ToRecipe,
74 fromDecoList,
75 (Data.Sequence.|>),
76 (Data.Sequence.<|),
77 hoistRecipe,
78 hoistRecipe',
79
80 -- ** How decorators work
81 -- $decos
82
83 -- ** Hiding a 'Recipe''s bean type
84 SomeRecipe,
85 recipe,
86 withRecipe,
87 getRecipeCallStack,
88
89 -- * Constructors
90 -- $constructors
91 Constructor,
92 val_,
93 val,
94 val',
95 eff_,
96 ioEff_,
97 eff,
98 ioEff,
99 eff',
100 wire,
101 getConstructorArgs,
102 getConstructorCallStack,
103 hoistConstructor,
104 hoistConstructor',
105
106 -- ** Registering secondary beans
107 -- $secondarybeans
108
109 -- * Cooking the beans
110 cook,
111 cookNonEmpty,
112 cookTree,
113
114 -- ** How loopy can we get?
115 Fire,
116 forbidDepCycles,
117 allowSelfDeps,
118 allowDepCycles,
119
120 -- ** Tasting the results
121 Beans,
122 taste,
123
124 -- ** When things go wrong
125 RecipeError (..),
126 MissingDependencies (..),
127 DoubleDutyBeans (..),
128 DependencyCycle (..),
129 prettyRecipeError,
130 prettyRecipeErrorLines,
131
132 -- ** Visualizing dependencies between beans.
133 getDependencyGraph,
134 DependencyGraph,
135 writeAsDot,
136 defaultStyle,
137 setVertexName,
138 BeanConstructionStep (..),
139 toAdjacencyMap,
140
141 -- *** Simplifying the dep graph
142 -- $simplifygraph
143 removeSecondaryBeans,
144 removeDecos,
145 collapseToPrimaryBeans,
146 )
147where
148
149import Algebra.Graph.AdjacencyMap (AdjacencyMap)
150import Algebra.Graph.AdjacencyMap qualified as Graph
151import Algebra.Graph.AdjacencyMap.Algorithm qualified as Graph
152import Algebra.Graph.Export.Dot qualified as Dot
153import Cauldron.Args
154import Cauldron.Beans (SomeMonoidTypeRep (..))
155import Cauldron.Beans qualified
156import Control.Exception (Exception (..))
157import Control.Monad.Fix
158import Control.Monad.IO.Class
159import Data.Bifunctor (first)
160import Data.ByteString qualified
161import Data.Dynamic
162import Data.Foldable qualified
163import Data.Function ((&))
164import Data.Functor ((<&>))
165import Data.Functor.Identity (Identity (..))
166import Data.Kind
167import Data.List qualified
168import Data.List.NonEmpty (NonEmpty)
169import Data.List.NonEmpty qualified
170import Data.Map.Strict (Map)
171import Data.Map.Strict qualified as Map
172import Data.Maybe (fromJust)
173import Data.Semigroup qualified
174import Data.Sequence (Seq)
175import Data.Sequence qualified
176import Data.Set (Set)
177import Data.Set qualified as Set
178import Data.Text qualified
179import Data.Text.Encoding qualified
180import Data.Tree
181import Data.Type.Equality (testEquality)
182import Data.Typeable
183import GHC.Exception (CallStack, prettyCallStackLines)
184import GHC.IsList
185import GHC.Stack (HasCallStack, callStack, withFrozenCallStack)
186import Type.Reflection qualified
187
188-- | A map of bean recipes, indexed by the 'TypeRep' of the bean each recipe
189-- ultimately produces. Only one recipe is allowed for each bean type.
190-- Parameterized by the monad @m@ in which the recipe 'Constructor's might have
191-- effects.
192type Cauldron :: (Type -> Type) -> Type
193newtype Cauldron m where
194 Cauldron :: {recipeMap :: Map TypeRep (SomeRecipe m)} -> Cauldron m
195
196empty :: Cauldron m
197empty = Cauldron Map.empty
198
199-- | Union of two 'Cauldron's, right-biased: prefers 'Recipe's from the /right/ cauldron when
200-- both contain the same key. (Note that 'Data.Map.Map' is left-biased.)
201instance Semigroup (Cauldron m) where
202 Cauldron {recipeMap = r1} <> Cauldron {recipeMap = r2} = Cauldron do Map.unionWith (flip const) r1 r2
203
204instance Monoid (Cauldron m) where
205 mempty = Cauldron Map.empty
206
207instance IsList (Cauldron m) where
208 type Item (Cauldron m) = SomeRecipe m
209 toList (Cauldron {recipeMap}) = Map.elems recipeMap
210 fromList = fromRecipeList
211
212-- | Change the monad used by the 'Recipe's in the 'Cauldron'.
213hoistCauldron :: (forall x. m x -> n x) -> Cauldron m -> Cauldron n
214hoistCauldron f (Cauldron {recipeMap}) = Cauldron {recipeMap = hoistSomeRecipe f <$> recipeMap}
215
216-- | More general form of 'hoistCauldron' that lets you modify the 'Args'
217-- inside all the 'Recipe's in the 'Cauldron'. See 'hoistRecipe''.
218hoistCauldron' ::
219 -- | Transformation to apply to the base constructor of each recipe.
220 (forall x. (Typeable x) => Args (m (Regs x)) -> Args (n (Regs x))) ->
221 -- | Transformation to apply to each decorator. Takes the decorator index as parameter.
222 (forall x. (Typeable x) => Int -> Args (m (Regs x)) -> Args (n (Regs x))) ->
223 Cauldron m ->
224 Cauldron n
225hoistCauldron' f fds Cauldron {recipeMap} =
226 Cauldron
227 { recipeMap = Map.map (hoistSomeRecipe' f fds) recipeMap
228 }
229
230-- | In order to put recipes producing different bean types into a container, we
231-- need to hide each recipe's bean type. This wrapper allows that.
232type SomeRecipe :: (Type -> Type) -> Type
233data SomeRecipe m where
234 SomeRecipe :: (Typeable bean) => {_recipeCallStack :: CallStack, _recipe :: Recipe m bean} -> SomeRecipe m
235
236-- | Build a 'SomeRecipe' from a 'Recipe' or a 'Constructor'. See 'ToRecipe'.
237--
238-- Useful in combination with 'fromRecipeList'.
239recipe ::
240 forall {recipelike} {m} bean.
241 (ToRecipe recipelike, Typeable bean, HasCallStack) =>
242 -- | A 'Recipe' or a 'Constructor'.
243 recipelike m bean ->
244 SomeRecipe m
245recipe theRecipe = withFrozenCallStack do
246 SomeRecipe callStack (toRecipe theRecipe)
247
248-- | Access the 'Recipe' inside a 'SomeRecipe'.
249withRecipe :: forall {m} r. (forall bean. (Typeable bean) => Recipe m bean -> r) -> SomeRecipe m -> r
250withRecipe f (SomeRecipe {_recipe}) = f _recipe
251
252getRecipeRep :: SomeRecipe m -> TypeRep
253getRecipeRep = withRecipe go
254 where
255 go :: forall bean m. (Typeable bean) => Recipe m bean -> TypeRep
256 go _ = typeRep (Proxy @bean)
257
258fromRecipeList :: [SomeRecipe m] -> Cauldron m
259fromRecipeList =
260 foldMap \sr -> Cauldron {recipeMap = Map.singleton (getRecipeRep sr) sr}
261
262toRecipeMap :: Cauldron m -> Map TypeRep (SomeRecipe m)
263toRecipeMap Cauldron {recipeMap} = recipeMap
264
265hoistSomeRecipe :: (forall x. m x -> n x) -> SomeRecipe m -> SomeRecipe n
266hoistSomeRecipe f r@SomeRecipe {_recipe} = r {_recipe = hoistRecipe f _recipe}
267
268hoistSomeRecipe' ::
269 forall m n.
270 (forall x. (Typeable x) => Args (m (Regs x)) -> Args (n (Regs x))) ->
271 (forall x. (Typeable x) => Int -> Args (m (Regs x)) -> Args (n (Regs x))) ->
272 SomeRecipe m ->
273 SomeRecipe n
274hoistSomeRecipe' f fds sr = withRecipe go sr
275 where
276 go :: forall bean. (Typeable bean) => Recipe m bean -> SomeRecipe n
277 go r = sr {_recipe = hoistRecipe' (f @bean) (fds @bean) r}
278
279-- | Instructions for how to build a value of type @bean@ while possibly
280-- performing actions in the monad @m@.
281--
282-- Because the instructions aren't really run until the 'Cauldron' is 'cook'ed,
283-- they can be modified with functions like 'adjust', in order to change the
284-- base bean 'Constructor', or add or remove decorators.
285type Recipe :: (Type -> Type) -> Type -> Type
286data Recipe m bean = Recipe
287 { -- | How to build the bean itself.
288 bean :: Constructor m bean,
289 -- | A 'Data.Sequence.Sequence' of decorators that will wrap the bean. There might be no decorators.
290 --
291 -- See 'fromDecoList', 'Data.Sequence.|>' and 'Data.Sequence.<|'.
292 decos :: Seq (Constructor m bean)
293 }
294
295fromDecoList :: [Constructor m bean] -> Seq (Constructor m bean)
296fromDecoList = Data.Sequence.fromList
297
298-- | Convenience typeclass that allows passing either 'Recipe's or 'Constructor's
299-- to the 'insert' and 'recipe' functions.
300type ToRecipe :: ((Type -> Type) -> Type -> Type) -> Constraint
301class ToRecipe recipelike where
302 toRecipe :: recipelike m bean -> Recipe m bean
303
304-- | Simply identity.
305instance ToRecipe Recipe where
306 toRecipe = id
307
308-- | 'Constructor' is converted to a 'Recipe' without decorators.
309instance ToRecipe Constructor where
310 toRecipe bean = Recipe {bean, decos = Data.Sequence.empty}
311
312-- | Change the monad used by the bean\'s main 'Constructor' and its decos.
313hoistRecipe :: (forall x. m x -> n x) -> Recipe m bean -> Recipe n bean
314hoistRecipe f (Recipe {bean, decos}) =
315 Recipe
316 { bean = hoistConstructor f bean,
317 decos = hoistConstructor f <$> decos
318 }
319
320-- | More general form of 'hoistRecipe' that enables precise control over the inner `Args`
321-- of each constructor in the 'Recipe'.
322hoistRecipe' ::
323 -- | Transformation to apply to the base constructor.
324 (Args (m (Regs bean)) -> Args (n (Regs bean))) ->
325 -- | Transformation to apply to each decorator. Takes the decorator index as parameter.
326 (Int -> Args (m (Regs bean)) -> Args (n (Regs bean))) ->
327 Recipe m bean ->
328 Recipe n bean
329hoistRecipe' f fds (Recipe {bean, decos}) =
330 Recipe
331 { bean = hoistConstructor' f bean,
332 decos = Data.Sequence.mapWithIndex (\i deco -> hoistConstructor' (fds i) deco) decos
333 }
334
335-- $decos
336--
337-- Decorators are 'Constructor's which, instead constructing the original
338-- version of a bean, they modify it in some way (but without changing its
339-- type). Because they modify the bean, typically decorators will take the bean
340-- as an argument.
341--
342-- Decorators can have other dependencies beyond the modified bean.
343--
344-- When the bean is a record-of-functions, decorators can be used to
345-- add behaviors like caching and logging to the functions.
346--
347-- The order of the decorators in the sequence is the order in which they modify
348-- the underlying bean. First decorator wraps first, last decorator wraps last.
349--
350-- >>> :{
351-- newtype Foo = Foo { sayFoo :: IO () }
352-- makeFoo :: Foo
353-- makeFoo = Foo { sayFoo = putStrLn "foo" }
354-- makeFooDeco1 :: Foo -> Foo
355-- makeFooDeco1 Foo { sayFoo } = Foo { sayFoo = putStrLn "deco1 enter" >> sayFoo >> putStrLn "deco1 exit" }
356-- makeFooDeco2 :: Foo -> IO Foo
357-- makeFooDeco2 Foo { sayFoo } = putStrLn "deco2 init" >> pure Foo { sayFoo = putStrLn "deco2 enter" >> sayFoo >> putStrLn "deco2 exit" }
358-- :}
359--
360-- >>> :{
361-- do
362-- let cauldron :: Cauldron IO
363-- cauldron = [
364-- recipe @Foo $ Recipe {
365-- bean = val $ wire makeFoo,
366-- decos = [
367-- val $ wire makeFooDeco1,
368-- eff $ wire makeFooDeco2
369-- ]
370-- }
371-- ]
372-- action <- either throwIO pure $ cook forbidDepCycles cauldron
373-- beans <- action
374-- let Just Foo {sayFoo} = taste beans
375-- sayFoo
376-- :}
377-- deco2 init
378-- deco2 enter
379-- deco1 enter
380-- foo
381-- deco1 exit
382-- deco2 exit
383
384-- $constructors
385--
386-- Bean-producing and bean-decorating functions need to be coaxed into 'Constructor's in order to be used in 'Cauldron's.
387
388data ConstructorReps where
389 ConstructorReps ::
390 { beanRep :: TypeRep,
391 argReps :: Set TypeRep,
392 regReps :: Map TypeRep Dynamic
393 } ->
394 ConstructorReps
395
396-- | Put a 'Recipe' into the 'Cauldron'.
397--
398-- Only one recipe is allowed for each bean type, so 'insert' for a
399-- bean will overwrite any previous recipe for that bean.
400insert ::
401 forall {recipelike} {m} (bean :: Type).
402 (Typeable bean, ToRecipe recipelike, HasCallStack) =>
403 -- | A 'Recipe' or a 'Constructor'.
404 recipelike m bean ->
405 Cauldron m ->
406 Cauldron m
407insert recipelike Cauldron {recipeMap} = withFrozenCallStack do
408 let rep = typeRep (Proxy @bean)
409 Cauldron {recipeMap = Map.insert rep (SomeRecipe callStack (toRecipe recipelike)) recipeMap}
410
411-- | Tweak a 'Recipe' inside the 'Cauldron', if the recipe exists.
412adjust ::
413 forall {m} bean.
414 (Typeable bean) =>
415 (Recipe m bean -> Recipe m bean) ->
416 Cauldron m ->
417 Cauldron m
418adjust f (Cauldron {recipeMap}) = withFrozenCallStack do
419 let rep = typeRep (Proxy @bean)
420 Cauldron
421 { recipeMap =
422 recipeMap
423 & Map.adjust
424 do
425 \r@SomeRecipe {_recipe = _recipe :: Recipe m a} ->
426 case testEquality (Type.Reflection.typeRep @bean) (Type.Reflection.typeRep @a) of
427 Nothing -> error "should never happen"
428 Just Refl -> r {_recipe = f _recipe}
429 rep
430 }
431
432delete ::
433 forall m.
434 TypeRep ->
435 Cauldron m ->
436 Cauldron m
437delete tr Cauldron {recipeMap} =
438 Cauldron {recipeMap = Map.delete tr recipeMap}
439
440-- | Strategy for dealing with dependency cycles.
441--
442-- (The name is admittedly uninformative; the culinary metaphor was stretched too far.)
443data Fire m = Fire
444 { shouldOmitDependency :: (BeanConstructionStep, BeanConstructionStep) -> Bool,
445 followPlanCauldron ::
446 Cauldron m ->
447 Set TypeRep ->
448 Beans ->
449 Plan ->
450 m Beans
451 }
452
453removeBeanFromArgs :: ConstructorReps -> ConstructorReps
454removeBeanFromArgs ConstructorReps {argReps, regReps, beanRep} =
455 ConstructorReps {argReps = Set.delete beanRep argReps, regReps, beanRep}
456
457-- | Forbid any kind of cyclic dependencies between beans. This is probably what you want.
458forbidDepCycles :: (Monad m) => Fire m
459forbidDepCycles =
460 Fire
461 { shouldOmitDependency = \_ -> False,
462 followPlanCauldron = \cauldron _secondaryBeanReps initial plan ->
463 Data.Foldable.foldlM
464 do followPlanStep (\_ -> id) (\_ -> id) cauldron mempty
465 initial
466 plan
467 }
468
469-- | Allow /direct/ self-dependencies.
470--
471-- A bean constructor might depend on itself. This can be useful for having
472-- decorated self-invocations, because the version of the bean received as
473-- argument comes \"from the future\" and is already decorated.
474--
475-- Note that a 'MonadFix' instance is required of the initialization monad.
476--
477-- __BEWARE__: Pattern-matching too eagerly on a \"bean from the future\" during
478-- construction will cause infinite loops or, if you are lucky, throw
479-- 'Control.Exception.FixIOException's.
480allowSelfDeps :: (MonadFix m) => Fire m
481allowSelfDeps =
482 Fire
483 { shouldOmitDependency = \case
484 (BarePrimaryBean bean, PrimaryBean anotherBean) | bean == anotherBean -> True
485 _ -> False,
486 followPlanCauldron = \cauldron _secondaryBeanReps initial plan ->
487 mfix do
488 \final ->
489 Data.Foldable.foldlM
490 do followPlanStep Cauldron.Beans.delete (\_ -> id) cauldron final
491 initial
492 plan
493 }
494
495-- | Allow /any/ kind of dependency cycles.
496--
497-- Usually comes in handy for creating serializers / deserializers for mutually
498-- dependent types.
499--
500-- Note that a 'MonadFix' instance is required of the initialization monad.
501--
502-- __BEWARE__: Pattern-matching too eagerly on argument beans during
503-- construction will cause infinite loops or, if you are lucky, throw
504-- 'Control.Exception.FixIOException's.
505allowDepCycles :: (MonadFix m) => Fire m
506allowDepCycles =
507 Fire
508 { shouldOmitDependency = \case
509 (BarePrimaryBean _, PrimaryBean _) -> True
510 (PrimaryBeanDeco _ _, PrimaryBean _) -> True
511 _ -> False,
512 followPlanCauldron = \cauldron secondaryBeanReps initial plan -> do
513 let makeBareView _ = (`Cauldron.Beans.restrictKeys` secondaryBeanReps)
514 let makeDecoView tr = (`Cauldron.Beans.restrictKeys` (Set.insert tr secondaryBeanReps))
515 mfix do
516 \final ->
517 Data.Foldable.foldlM
518 do followPlanStep makeBareView makeDecoView cauldron final
519 initial
520 plan
521 }
522
523-- https://discord.com/channels/280033776820813825/280036215477239809/1147832555828162594
524-- https://github.com/ghc-proposals/ghc-proposals/pull/126#issuecomment-1363403330
525
526-- | This function DOESN'T return the bean rep itself in the argreps.
527constructorReps :: forall {m} bean. (Typeable bean) => Constructor m bean -> ConstructorReps
528constructorReps (getConstructorArgs -> c) =
529 ConstructorReps
530 { beanRep = typeRep (Proxy @bean),
531 argReps = getArgsReps c,
532 regReps =
533 c
534 & getRegsReps
535 & Set.map (\mtr@(SomeMonoidTypeRep tr) -> Data.Semigroup.Arg (Type.Reflection.SomeTypeRep tr) (toDyn (Cauldron.Beans.someMonoidTypeRepMempty mtr)))
536 & Map.fromArgSet
537 }
538
539type Plan = [BeanConstructionStep]
540
541-- | A step in the construction of a bean value.
542data BeanConstructionStep
543 = -- | Undecorated bean.
544 BarePrimaryBean TypeRep
545 | -- | Apply the decorator with the given index. Comes after the 'BarePrimaryBean' and all 'PrimaryBeanDeco's with a lower index value.
546 PrimaryBeanDeco TypeRep Int
547 | -- | Final, fully decorated version of a bean. If there are no decorators, comes directly after 'BarePrimaryBean'.
548 PrimaryBean TypeRep
549 | -- | Beans that are secondary registrations of a 'Constructor' and which are aggregated monoidally.
550 SecondaryBean TypeRep
551 deriving stock (Show, Eq, Ord)
552
553-- | Build the beans using the recipeMap stored in the 'Cauldron'.
554--
555-- Any secondary beans that are registered by constructors are aggregated
556-- monoidally.
557cook ::
558 forall m.
559 (Monad m) =>
560 Fire m ->
561 Cauldron m ->
562 Either RecipeError (m Beans)
563cook fire cauldron =
564 fmap @(Either RecipeError) (fmap @m rootLabel) $
565 cookTree (Node (fire, cauldron) [])
566
567-- | Cook a nonempty list of 'Cauldron's.
568--
569-- 'Cauldron's later in the list can see the beans in all previous 'Cauldron's,
570-- but not vice versa.
571--
572-- Beans in a 'Cauldron' have priority over the same beans in previous 'Cauldron's.
573cookNonEmpty ::
574 forall m.
575 (Monad m) =>
576 NonEmpty (Fire m, Cauldron m) ->
577 Either RecipeError (m (NonEmpty Beans))
578cookNonEmpty nonemptyCauldronList = do
579 fmap @(Either RecipeError) (fmap @m unsafeTreeToNonEmpty) $
580 cookTree (nonEmptyToTree nonemptyCauldronList)
581
582-- | Cook a hierarchy of 'Cauldron's.
583--
584-- 'Cauldron's down in the branches can see the beans of their ancestor
585-- 'Cauldron's, but not vice versa.
586--
587-- Beans in a 'Cauldron' have priority over the same beans in ancestor 'Cauldron's.
588cookTree ::
589 forall m.
590 (Monad m) =>
591 Tree (Fire m, Cauldron m) ->
592 Either RecipeError (m (Tree Beans))
593cookTree (treecipes) = do
594 accumMap <- first DoubleDutyBeansError do checkNoDoubleDutyBeans (snd <$> treecipes)
595 () <- first MissingDependenciesError do checkMissingDeps (Map.keysSet accumMap) (snd <$> treecipes)
596 treeplan <- first DependencyCycleError do buildPlans (Map.keysSet accumMap) treecipes
597 Right $ followPlan (fromDynList (Data.Foldable.toList accumMap)) (treeplan)
598
599newtype DoubleDutyBeans = DoubleDutyBeans (Map TypeRep (CallStack, CallStack))
600 deriving stock (Show)
601
602-- | Get a graph of dependencies between 'BeanConstructionStep's. The graph can
603-- be obtained even if the 'Cauldron' can't be 'cook'ed successfully.
604getDependencyGraph :: Cauldron m -> DependencyGraph
605getDependencyGraph cauldron =
606 let (accumMap, _) = cauldronRegs cauldron
607 (_, deps) = buildDepsCauldron (Map.keysSet accumMap) cauldron
608 in DependencyGraph {graph = Graph.edges deps}
609
610checkNoDoubleDutyBeans ::
611 Tree (Cauldron m) ->
612 Either DoubleDutyBeans (Map TypeRep Dynamic)
613checkNoDoubleDutyBeans treecipes = do
614 let (accumMap, beanSet) = cauldronTreeRegs treecipes
615 let common = Map.intersectionWith (,) (fst <$> accumMap) beanSet
616 if not (Map.null common)
617 then Left $ DoubleDutyBeans common
618 else Right $ snd <$> accumMap
619
620cauldronTreeRegs :: Tree (Cauldron m) -> (Map TypeRep (CallStack, Dynamic), Map TypeRep CallStack)
621cauldronTreeRegs = foldMap cauldronRegs
622
623cauldronRegs :: Cauldron m -> (Map TypeRep (CallStack, Dynamic), Map TypeRep CallStack)
624cauldronRegs Cauldron {recipeMap} =
625 Map.foldMapWithKey
626 do \rep aRecipe -> (recipeRegs aRecipe, Map.singleton rep (getRecipeCallStack aRecipe))
627 recipeMap
628
629-- | Returns the accumulators, not the main bean
630recipeRegs :: SomeRecipe m -> Map TypeRep (CallStack, Dynamic)
631recipeRegs (SomeRecipe _ (Recipe {bean, decos})) = do
632 let extractRegReps c = (getConstructorCallStack c,) <$> (\ConstructorReps {regReps} -> regReps) (constructorReps c)
633 extractRegReps bean
634 <> foldMap extractRegReps decos
635
636data MissingDependencies = MissingDependencies CallStack TypeRep (Set TypeRep)
637 deriving stock (Show)
638
639checkMissingDeps ::
640 -- | accums
641 Set TypeRep ->
642 Tree (Cauldron m) ->
643 Either MissingDependencies ()
644checkMissingDeps accums treecipes = do
645 let decoratedTreecipes = decorate (Map.empty, treecipes)
646 missing =
647 decoratedTreecipes <&> \(available, requested) ->
648 do checkMissingDepsCauldron accums (Map.keysSet available) requested
649 sequence_ missing
650 where
651 decorate ::
652 (Map TypeRep (SomeRecipe m), Tree (Cauldron m)) ->
653 Tree (Map TypeRep (SomeRecipe m), Cauldron m)
654 decorate = unfoldTree
655 do
656 \(acc, Node (current@Cauldron {recipeMap}) rest) ->
657 let -- current level has priority
658 newAcc = recipeMap `Map.union` acc
659 newSeeds = do
660 z <- rest
661 [(newAcc, z)]
662 in ((newAcc, current), newSeeds)
663
664checkMissingDepsCauldron ::
665 -- | accums
666 Set TypeRep ->
667 -- | available at this level
668 Set TypeRep ->
669 Cauldron m ->
670 Either MissingDependencies ()
671checkMissingDepsCauldron accums available cauldron =
672 Data.Foldable.for_ (demandsByConstructorsInCauldron cauldron) \(stack, tr, demanded) ->
673 let missing = Set.filter (`Set.notMember` (available `Set.union` accums)) demanded
674 in if Set.null missing
675 then Right ()
676 else Left $ MissingDependencies stack tr missing
677
678demandsByConstructorsInCauldron :: Cauldron m -> [(CallStack, TypeRep, Set TypeRep)]
679demandsByConstructorsInCauldron Cauldron {recipeMap} = do
680 (tr, SomeRecipe _ (Recipe {bean, decos})) <- Map.toList recipeMap
681 ( let ConstructorReps {argReps = beanArgReps} = constructorReps bean
682 in [(getConstructorCallStack bean, tr, beanArgReps)]
683 )
684 ++ do
685 decoCon <- Data.Foldable.toList decos
686 let ConstructorReps {argReps = decoArgReps} = constructorReps decoCon
687 in [(getConstructorCallStack decoCon, tr, decoArgReps)]
688
689newtype DependencyCycle = DependencyCycle (NonEmpty (BeanConstructionStep, Maybe CallStack))
690 deriving stock (Show)
691
692buildPlans :: Set TypeRep -> Tree (Fire m, Cauldron m) -> Either DependencyCycle (Tree (Plan, Fire m, Cauldron m))
693buildPlans secondary = traverse \(fire@Fire {shouldOmitDependency}, cauldron) -> do
694 let (locations, deps) = buildDepsCauldron secondary cauldron
695 -- We may omit some dependency edges to allow for cyclic dependencies.
696 let graph = Graph.edges $ filter (not . shouldOmitDependency) deps
697 case Graph.topSort graph of
698 Left recipeCycle ->
699 Left $ DependencyCycle $ recipeCycle <&> \step -> (step, Map.lookup step locations)
700 Right (reverse -> plan) -> do
701 Right (plan, fire, cauldron)
702
703buildDepsCauldron :: Set TypeRep -> Cauldron m -> (Map BeanConstructionStep CallStack, [(BeanConstructionStep, BeanConstructionStep)])
704buildDepsCauldron secondary Cauldron {recipeMap} = do
705 -- Are we depending on a primary bean, or on a monoidally aggregated secondary bean?
706 -- I wonder if we could make this more uniform, it's kind of annoying to have to make this decision here...
707 let makeTargetStep :: TypeRep -> BeanConstructionStep
708 makeTargetStep rep =
709 if rep `Set.member` secondary
710 then SecondaryBean rep
711 else PrimaryBean rep
712 recipeMap
713 & Map.foldMapWithKey
714 \beanRep
715 SomeRecipe
716 { _recipeCallStack,
717 _recipe =
718 Recipe
719 { bean = bean :: Constructor m bean,
720 decos
721 }
722 } ->
723 do
724 let bareBean = BarePrimaryBean beanRep
725 boiledBean = PrimaryBean beanRep
726 decoSteps = do
727 (decoIndex, decoCon) <- zip [0 :: Int ..] (Data.Foldable.toList decos)
728 [(PrimaryBeanDeco beanRep decoIndex, decoCon)]
729 beanDeps = do
730 constructorEdges makeTargetStep bareBean (constructorReps bean)
731 decoDeps = do
732 (decoStep, decoCon) <- decoSteps
733 -- We remove the bean because from the args becase, in the
734 -- case of decos, we want to depend on the in-the-making
735 -- version of the bean, not the completed bean.
736 constructorEdges makeTargetStep decoStep (removeBeanFromArgs do constructorReps decoCon)
737 innerSteps = bareBean Data.List.NonEmpty.:| (fst <$> decoSteps) ++ [boiledBean]
738 innerDeps =
739 -- This explicit dependency between the completed bean and its
740 -- "bare" undecorated form is not strictly required. It will
741 -- always exist in an indirect manner, through the decorators.
742 -- But it might be useful when rendering the dep graph.
743 (PrimaryBean beanRep, BarePrimaryBean beanRep)
744 :
745 -- The dep chain of completed bean -> decorators -> bare bean.
746 zip (Data.List.NonEmpty.tail innerSteps) (Data.List.NonEmpty.toList innerSteps)
747 ( Map.fromList $
748 [ (bareBean, getConstructorCallStack bean),
749 (boiledBean, _recipeCallStack)
750 ]
751 ++ do
752 (decoStep, decoCon) <- decoSteps
753 [(decoStep, getConstructorCallStack decoCon)],
754 beanDeps ++ decoDeps ++ innerDeps
755 )
756
757constructorEdges ::
758 (TypeRep -> BeanConstructionStep) ->
759 BeanConstructionStep ->
760 ConstructorReps ->
761 [(BeanConstructionStep, BeanConstructionStep)]
762constructorEdges makeTargetStep item (ConstructorReps {argReps, regReps}) =
763 -- consumers depend on their args
764 ( do
765 argRep <- Set.toList argReps
766 let argStep = makeTargetStep argRep
767 [(item, argStep)]
768 )
769 ++
770 -- secondary beans depend on their producers
771 ( do
772 (regRep, _) <- Map.toList regReps
773 let repStep = SecondaryBean regRep
774 [(repStep, item)]
775 )
776
777followPlan ::
778 (Monad m) =>
779 Beans ->
780 (Tree (Plan, Fire m, Cauldron m)) ->
781 m (Tree Beans)
782followPlan initialBeans treecipes =
783 let secondaryBeanReps = Cauldron.Beans.keysSet initialBeans
784 in unfoldTreeM
785 ( \(previousStageBeans, Node (plan, Fire {followPlanCauldron}, cauldron) rest) -> do
786 currentStageBeans <- followPlanCauldron cauldron secondaryBeanReps previousStageBeans plan
787 pure (currentStageBeans, (,) currentStageBeans <$> rest)
788 )
789 (initialBeans, treecipes)
790
791followPlanStep ::
792 (Monad m) =>
793 (TypeRep -> Beans -> Beans) ->
794 (TypeRep -> Beans -> Beans) ->
795 Cauldron m ->
796 Beans ->
797 Beans ->
798 BeanConstructionStep ->
799 m Beans
800followPlanStep makeBareView makeDecoView Cauldron {recipeMap} final super item =
801 case item of
802 BarePrimaryBean rep -> case fromJust do Map.lookup rep recipeMap of
803 SomeRecipe {_recipe = Recipe {bean}} -> do
804 let ConstructorReps {beanRep} = constructorReps bean
805 -- We delete the beanRep before running the bean,
806 -- because if we have a self-dependency, we don't want to use the bean
807 -- from a previous context (if it exists) we want the bean from final.
808 -- There is a test for this.
809 inserter <- followConstructor bean final (makeBareView beanRep super)
810 pure do inserter super
811 PrimaryBeanDeco rep index -> case fromJust do Map.lookup rep recipeMap of
812 SomeRecipe {_recipe = Recipe {decos}} -> do
813 let deco = decos `Data.Sequence.index` index
814 let ConstructorReps {beanRep} = constructorReps deco
815 -- Unlike before, we don't delete the beanRep before running the constructor.
816 inserter <- followConstructor deco final (makeDecoView beanRep super)
817 pure do inserter super
818 -- \| We do nothing here, the work has been done in previous 'BarePrimaryBean' and
819 -- 'PrimaryBeanDeco' steps.
820 PrimaryBean {} -> pure super
821 -- \| We do nothing here, secondary beans are built as a byproduct
822 -- of primary beans and decorators.
823 SecondaryBean {} -> pure super
824
825-- | Build a bean out of already built beans.
826-- This can only work without blowing up if there aren't dependecy cycles
827-- and the order of construction respects the depedencies!
828followConstructor ::
829 (Monad m, Typeable bean) =>
830 Constructor m bean ->
831 Beans ->
832 Beans ->
833 m (Beans -> Beans)
834followConstructor c final super = do
835 (regs, bean) <- runConstructor [super, final] c
836 pure \bs ->
837 Cauldron.Beans.unionBeansMonoidally (getRegsReps (getConstructorArgs c)) bs regs
838 & Cauldron.Beans.insert bean
839
840-- | Sometimes the 'cook'ing process goes wrong.
841data RecipeError
842 = -- | A 'Constructor' depends on beans that can't be found either in the current 'Cauldron' or its ancestors.
843 MissingDependenciesError MissingDependencies
844 | -- | Beans that work both as primary beans and as secondary beans
845 -- are disallowed.
846 DoubleDutyBeansError DoubleDutyBeans
847 | -- | Dependency cycles are disallowed by some 'Fire's.
848 DependencyCycleError DependencyCycle
849 deriving stock (Show)
850
851instance Exception RecipeError where
852 displayException = prettyRecipeError
853
854prettyRecipeError :: RecipeError -> String
855prettyRecipeError = Data.List.intercalate "\n" . prettyRecipeErrorLines
856
857prettyRecipeErrorLines :: RecipeError -> [String]
858prettyRecipeErrorLines = \case
859 MissingDependenciesError
860 (MissingDependencies constructorCallStack constructorResultRep missingDependenciesReps) ->
861 [ "This constructor for a value of type "
862 ++ show constructorResultRep
863 ++ ":"
864 ]
865 ++ (("\t" ++) <$> prettyCallStackLines constructorCallStack)
866 ++ [ "is missing the following dependencies:"
867 ]
868 ++ do
869 rep <- Data.Foldable.toList missingDependenciesReps
870 ["- " ++ show rep]
871 DoubleDutyBeansError (DoubleDutyBeans doubleDutyMap) ->
872 [ "The following beans work both as primary beans and secondary beans:"
873 ]
874 ++ ( flip Map.foldMapWithKey doubleDutyMap \rep (secCS, primCS) ->
875 [ "- " ++ show rep ++ " is a secondary bean in this constructor:"
876 ]
877 ++ (("\t" ++) <$> prettyCallStackLines secCS)
878 ++ [ " and a primary bean in this recipe:"
879 ]
880 ++ (("\t" ++) <$> prettyCallStackLines primCS)
881 )
882 DependencyCycleError (DependencyCycle theCycle) ->
883 [ "Forbidden dependency cycle between bean construction steps:"
884 ]
885 ++ ( flip foldMap theCycle \(step, mstack) ->
886 [ "- " ++ case step of
887 BarePrimaryBean rep -> "Bare bean " ++ show rep
888 PrimaryBeanDeco rep i -> "Decorator " ++ show i ++ " for bean " ++ show rep
889 PrimaryBean rep -> "Complete bean " ++ show rep
890 SecondaryBean rep -> "Secondary bean " ++ show rep
891 ]
892 ++ case mstack of
893 Nothing -> []
894 Just stack -> (("\t" ++) <$> prettyCallStackLines stack)
895 )
896
897-- | An edge means that the source depends on the target.
898--
899-- The dependencies of each bean are given separatedly from its decorators.
900newtype DependencyGraph = DependencyGraph {graph :: AdjacencyMap BeanConstructionStep}
901 deriving newtype (Show, Eq, Ord, Semigroup, Monoid)
902
903-- | Conversion to a graph type
904-- from the
905-- [algebraic-graphs](https://hackage.haskell.org/package/algebraic-graphs-0.7/docs/Algebra-Graph-AdjacencyMap.html)
906-- library for further processing.
907toAdjacencyMap :: DependencyGraph -> AdjacencyMap BeanConstructionStep
908toAdjacencyMap DependencyGraph {graph} = graph
909
910-- | Remove all vertices and edges related to secondary beans.
911removeSecondaryBeans :: DependencyGraph -> DependencyGraph
912removeSecondaryBeans DependencyGraph {graph} =
913 DependencyGraph {graph = Graph.induce (\case SecondaryBean {} -> False; _ -> True) graph}
914
915-- | Remove all vertices and edges related to bean decorators.
916removeDecos :: DependencyGraph -> DependencyGraph
917removeDecos DependencyGraph {graph} =
918 DependencyGraph {graph = Graph.induce (\case PrimaryBeanDeco {} -> False; _ -> True) graph}
919
920-- | Unifies 'PrimaryBean's with their respective 'BarePrimaryBean's and 'PrimaryBeanDeco's.
921--
922-- Also removes any self-loops.
923collapseToPrimaryBeans :: DependencyGraph -> DependencyGraph
924collapseToPrimaryBeans DependencyGraph {graph} = do
925 let simplified =
926 Graph.gmap
927 ( \case
928 BarePrimaryBean rep -> PrimaryBean rep
929 PrimaryBeanDeco rep _ -> PrimaryBean rep
930 other -> other
931 )
932 graph
933 -- Is there a simpler way to removoe self-loops?
934 vertices = Graph.vertexList simplified
935 edges = Graph.edgeList simplified
936 edgesWithoutSelfLoops =
937 filter
938 ( \case
939 (PrimaryBean source, PrimaryBean target) -> if source == target then False else True
940 _ -> True
941 )
942 edges
943 DependencyGraph {graph = Graph.vertices vertices `Graph.overlay` Graph.edges edgesWithoutSelfLoops}
944
945-- | See the [DOT format](https://graphviz.org/doc/info/lang.html).
946writeAsDot :: Dot.Style BeanConstructionStep Data.Text.Text -> FilePath -> DependencyGraph -> IO ()
947writeAsDot style filepath DependencyGraph {graph} = do
948 let dot = Dot.export style graph
949 Data.ByteString.writeFile filepath (Data.Text.Encoding.encodeUtf8 dot)
950
951-- | Default DOT rendering style to use with 'writeAsDot'.
952-- When a 'RecipeError' exists, is highlights the problematic 'BeanConstructionStep's.
953defaultStyle :: Maybe RecipeError -> Dot.Style BeanConstructionStep Data.Text.Text
954defaultStyle merr =
955 -- https://graphviz.org/docs/attr-types/style/
956 -- https://hackage.haskell.org/package/algebraic-graphs-0.7/docs/Algebra-Graph-Export-Dot.html
957 (Dot.defaultStyle defaultStepToText)
958 { Dot.vertexAttributes = \step -> case merr of
959 Nothing -> []
960 Just (MissingDependenciesError (MissingDependencies _ _ missing)) ->
961 case step of
962 PrimaryBean rep
963 | Set.member rep missing ->
964 [ Data.Text.pack "style" Dot.:= Data.Text.pack "dashed",
965 Data.Text.pack "color" Dot.:= Data.Text.pack "red"
966 ]
967 _ -> []
968 Just (DoubleDutyBeansError (DoubleDutyBeans (Map.keysSet -> bs))) ->
969 case step of
970 PrimaryBean rep
971 | Set.member rep bs ->
972 [ Data.Text.pack "style" Dot.:= Data.Text.pack "bold",
973 Data.Text.pack "color" Dot.:= Data.Text.pack "green"
974 ]
975 SecondaryBean rep
976 | Set.member rep bs ->
977 [ Data.Text.pack "style" Dot.:= Data.Text.pack "bold",
978 Data.Text.pack "color" Dot.:= Data.Text.pack "green"
979 ]
980 _ -> []
981 Just (DependencyCycleError (DependencyCycle (Set.fromList . Data.Foldable.toList . fmap fst -> cycleStepSet))) ->
982 if Set.member step cycleStepSet
983 then
984 [ Data.Text.pack "style" Dot.:= Data.Text.pack "bold",
985 Data.Text.pack "color" Dot.:= Data.Text.pack "blue"
986 ]
987 else []
988 }
989
990-- | Change the default way of how 'BeanConstructionStep's are rendered to text.
991setVertexName :: (BeanConstructionStep -> Data.Text.Text) -> Dot.Style BeanConstructionStep Data.Text.Text -> Dot.Style BeanConstructionStep Data.Text.Text
992setVertexName vertexName style = style {Dot.vertexName}
993
994defaultStepToText :: BeanConstructionStep -> Data.Text.Text
995defaultStepToText =
996 let p rep = Data.Text.pack do show rep
997 in \case
998 BarePrimaryBean rep -> p rep <> Data.Text.pack "#bare"
999 PrimaryBeanDeco rep index -> p rep <> Data.Text.pack ("#deco#" ++ show index)
1000 PrimaryBean rep -> p rep
1001 SecondaryBean rep -> p rep <> Data.Text.pack "#agg"
1002
1003nonEmptyToTree :: NonEmpty a -> Tree a
1004nonEmptyToTree = \case
1005 a Data.List.NonEmpty.:| [] -> Node a []
1006 a Data.List.NonEmpty.:| (b : rest) -> Node a [nonEmptyToTree (b Data.List.NonEmpty.:| rest)]
1007
1008unsafeTreeToNonEmpty :: Tree a -> NonEmpty a
1009unsafeTreeToNonEmpty = \case
1010 Node a [] -> a Data.List.NonEmpty.:| []
1011 Node a [b] -> Data.List.NonEmpty.cons a (unsafeTreeToNonEmpty b)
1012 _ -> error "tree not list-shaped"
1013
1014-- | A way of building value of type @bean@, potentially requiring some
1015-- dependencies, potentially returning some secondary beans
1016-- along the primary @bean@ result, and also potentially requiring some
1017-- initialization effect in a monad @m@.
1018--
1019-- Note that only the type of the primary @bean@ is reflected in the
1020-- 'Constructor' type. Those of the dependencies and secondary beans are not.
1021--
1022-- A typical initialization monad will be 'IO', used for example to create
1023-- mutable references that the bean will use internally. Sometimes the
1024-- constructor will allocate resources with bracket-like operations, and in that
1025-- case a monad like 'Cauldron.Managed.Managed' might be needed instead.
1026data Constructor m bean = Constructor
1027 { _constructorCallStack :: CallStack,
1028 _args :: Args (m (Regs bean))
1029 }
1030
1031-- | Create a 'Constructor' from an 'Args' value that returns a 'bean'.
1032--
1033-- Usually, the 'Args' value will be created by 'wire'ing a constructor function.
1034val_ :: forall bean m. (Applicative m, HasCallStack) => Args bean -> Constructor m bean
1035val_ x = Constructor callStack $ fmap (pure . pure) x
1036
1037-- | Like 'val_', but examines the @nested@ value returned by the 'Args' looking
1038-- for (potentially nested) tuples. All tuple components except the
1039-- rightmost-innermost one are registered as secondary beans (if they have
1040-- 'Monoid' instances, otherwise 'val' won't compile).
1041val :: forall {nested} bean m. (Registrable nested bean, Applicative m, HasCallStack) => Args nested -> Constructor m bean
1042val x = withFrozenCallStack (val' $ fmap runIdentity $ register $ fmap Identity x)
1043
1044-- | Like 'val', but uses an alternative form of registering secondary beans.
1045-- Less 'Registrable' typeclass magic, but more verbose. Likely not what you want.
1046val' :: forall bean m. (Applicative m, HasCallStack) => Args (Regs bean) -> Constructor m bean
1047val' x = Constructor callStack $ fmap pure x
1048
1049-- | Create a 'Constructor' from an 'Args' value that returns an initialization
1050-- effect that produces 'bean'.
1051--
1052-- Usually, the 'Args' value will be created by 'wire'ing an effectul constructor function.
1053eff_ :: forall bean m. (Functor m, HasCallStack) => Args (m bean) -> Constructor m bean
1054eff_ x = Constructor callStack $ fmap (fmap pure) x
1055
1056-- | Like 'eff_', but lifts 'IO' constructor effects into a general 'MonadIO'.
1057ioEff_ :: forall bean m. (MonadIO m, HasCallStack) => Args (IO bean) -> Constructor m bean
1058ioEff_ args = withFrozenCallStack (hoistConstructor liftIO (eff_ args))
1059
1060-- | Like 'eff_', but examines the @nested@ value produced by the action
1061-- returned by the 'Args' looking for (potentially nested) tuples. All tuple
1062-- components except the rightmost-innermost one are registered as secondary
1063-- beans (if they have 'Monoid' instances, otherwise 'eff' won't compile).
1064eff :: forall {nested} bean m. (Registrable nested bean, Monad m, HasCallStack) => Args (m nested) -> Constructor m bean
1065eff x = withFrozenCallStack (eff' $ register x)
1066
1067-- | Like 'eff', but lifts 'IO' constructor effects into a general 'MonadIO'.
1068ioEff :: forall {nested} bean m. (Registrable nested bean, MonadIO m, HasCallStack) => Args (IO nested) -> Constructor m bean
1069ioEff args = withFrozenCallStack (hoistConstructor liftIO (eff args))
1070
1071-- | Like 'eff', but uses an alternative form of registering secondary beans.
1072-- Less 'Registrable' typeclass magic, but more verbose. Likely not what you want.
1073eff' :: forall bean m. (HasCallStack) => Args (m (Regs bean)) -> Constructor m bean
1074eff' = Constructor callStack
1075
1076runConstructor :: (Monad m) => [Beans] -> Constructor m bean -> m (Beans, bean)
1077runConstructor bss (Constructor {_args}) = do
1078 regs <- _args & runArgs (Data.Foldable.asum (taste <$> bss))
1079 pure (runRegs (getRegsReps _args) regs)
1080
1081-- | Change the monad in which the 'Constructor'\'s effects take place.
1082hoistConstructor :: (forall x. m x -> n x) -> Constructor m bean -> Constructor n bean
1083hoistConstructor f c@Constructor {_args} = c {_args = fmap f _args}
1084
1085-- | More general form of 'hoistConstructor' that enables precise control over the inner `Args`.
1086hoistConstructor' :: (Args (m (Regs bean)) -> Args (n (Regs bean))) -> Constructor m bean -> Constructor n bean
1087hoistConstructor' f c@Constructor {_args} = c {_args = f _args}
1088
1089-- | Get the inner 'Args' value for the 'Constructor', typically for inspecting
1090-- 'TypeRep's of its arguments/registrations.
1091getConstructorArgs :: Constructor m bean -> Args (m (Regs bean))
1092getConstructorArgs (Constructor {_args}) = _args
1093
1094-- | For debugging purposes, 'Constructor's remember the 'CallStack'
1095-- of when they were created.
1096getConstructorCallStack :: Constructor m bean -> CallStack
1097getConstructorCallStack (Constructor {_constructorCallStack}) = _constructorCallStack
1098
1099-- | For debugging purposes, 'SomeRecipe's remember the 'CallStack'
1100-- of when they were created.
1101getRecipeCallStack :: SomeRecipe m -> CallStack
1102getRecipeCallStack (SomeRecipe {_recipeCallStack}) = _recipeCallStack
1103
1104-- | The set of all 'TypeRep' keys of the map.
1105keysSet :: Cauldron m -> Set TypeRep
1106keysSet Cauldron {recipeMap} = Map.keysSet recipeMap
1107
1108-- | Restrict a 'Cauldron' to only those 'TypeRep's found in a 'Set'.
1109restrictKeys :: Cauldron m -> Set TypeRep -> Cauldron m
1110restrictKeys Cauldron {recipeMap} trs = Cauldron {recipeMap = Map.restrictKeys recipeMap trs}
1111
1112-- $simplifygraph
1113--
1114-- 'DependencyGraph's can get complex and difficult to intepret because they
1115-- include bean decorators and secondary beans, details in which we many not be
1116-- interested.
1117--
1118-- These functions help simplify 'DependencyGraph's before passing them to
1119-- 'writeAsDot'. They can be composed between themselves.
1120
1121-- $secondarybeans
1122--
1123-- There is an exception to the 'Cauldron' rule that each bean type can only
1124-- be produced by a single 'Recipe' in the 'Cauldron'.
1125--
1126-- 'Constructor's can produce, besides their \"primary\" bean result,
1127-- \"secondary\" beans that are not reflected in the 'Constructor' signature.
1128-- Multiple constructors across different 'Recipe's can produce secondary beans of the
1129-- same type.
1130--
1131-- Secondary beans are a bit special, in that:
1132--
1133-- * The value that is \"seen"\ by a 'Constructor' that depends on a secondary bean
1134-- is the aggregation of /all/ values produced for that bean in the 'Cauldron'. This
1135-- means that secondary beans must have 'Monoid' instances, to enable aggregation.
1136--
1137-- * When calculating build plan steps for a 'Cauldron', 'Constructor's that depend on a
1138-- secondary bean come after /all/ of the 'Constructor's that produce that secondary bean.
1139--
1140-- * Secondary beans can't be decorated.
1141--
1142-- * A bean type can't be primary and secondary at the same time. See 'DoubleDutyBeansError'.
1143--
1144-- What are secondary beans useful for?
1145--
1146-- * Exposing some uniform control or inspection interface for certain beans.
1147--
1148-- * Registering tasks or workers that must be run after application initialization.
1149--
1150-- The simplest way of registering secondary beans is to pass an 'Args' value returning a tuple
1151-- to the 'val' (for pure constructors) or 'eff' (for effectful constructors) functions. Components
1152-- of the tuple other than the rightmost component are considered secondary beans:
1153--
1154-- >>> :{
1155-- con :: Constructor Identity String
1156-- con = val $ pure (Sum @Int, All False, "foo")
1157-- effCon :: Constructor IO String
1158-- effCon = eff $ pure $ pure @IO (Sum @Int, All False, "foo")
1159-- :}
1160--
1161-- Example of how secondary bean values are accumulated:
1162--
1163-- >>> :{
1164-- data U = U deriving Show
1165-- data V = V deriving Show
1166-- makeU :: (Sum Int, U)
1167-- makeU = (Sum 1, U)
1168-- makeV :: U -> (Sum Int, V)
1169-- makeV = \_ -> (Sum 7, V)
1170-- newtype W = W (Sum Int) deriving Show -- depends on the secondary bean
1171-- :}
1172--
1173-- >>> :{
1174-- do
1175-- let cauldron :: Cauldron Identity
1176-- cauldron = [
1177-- recipe @U $ val $ wire makeU,
1178-- recipe @V $ val $ wire makeV,
1179-- recipe @W $ val $ wire W
1180-- ]
1181-- Identity beans <- either throwIO pure $ cook forbidDepCycles cauldron
1182-- pure $ taste @W beans
1183-- :}
1184-- Just (W (Sum {getSum = 8}))
1185
1186-- $setup
1187-- >>> :set -XBlockArguments
1188-- >>> :set -XOverloadedLists
1189-- >>> :set -Wno-incomplete-uni-patterns
1190-- >>> import Data.Functor.Identity
1191-- >>> import Data.Function ((&))
1192-- >>> import Data.Monoid
1193-- >>> import Data.Either (either)
1194-- >>> import Control.Exception (throwIO)