packages feed

cauldron 0.8.0.0 → 0.9.0.0

raw patch · 14 files changed

+449/−310 lines, 14 filesPVP ok

version bump matches the API change (PVP)

API changes (from Hackage documentation)

- Cauldron: [bean] :: Recipe (m :: Type -> Type) bean -> Constructor m bean
- Cauldron: data SomeRecipe (m :: Type -> Type)
- Cauldron: fromRecipeList :: forall (m :: Type -> Type). [SomeRecipe m] -> Cauldron m
- Cauldron: getRecipeCallStack :: forall (m :: Type -> Type). SomeRecipe m -> CallStack
- Cauldron: instance GHC.Base.Monoid (Cauldron.Cauldron m)
- Cauldron: instance GHC.Base.Monoid Cauldron.BeanGetter
- Cauldron: instance GHC.Base.Monoid Cauldron.DependencyGraph
- Cauldron: instance GHC.Base.Semigroup (Cauldron.Cauldron m)
- Cauldron: instance GHC.Base.Semigroup Cauldron.BeanGetter
- Cauldron: instance GHC.Base.Semigroup Cauldron.DependencyGraph
- Cauldron: instance GHC.Exception.Type.Exception Cauldron.CookingError
- Cauldron: instance GHC.IsList.IsList (Cauldron.Cauldron m)
- Cauldron: instance GHC.Show.Show Cauldron.BeanConstructionStep
- Cauldron: instance GHC.Show.Show Cauldron.CookingError
- Cauldron: instance GHC.Show.Show Cauldron.DependencyCycle
- Cauldron: instance GHC.Show.Show Cauldron.DependencyGraph
- Cauldron: instance GHC.Show.Show Cauldron.DoubleDutyBean
- Cauldron: instance GHC.Show.Show Cauldron.MissingDependencies
- Cauldron: toRecipeMap :: forall (m :: Type -> Type). Cauldron m -> Map TypeRep (SomeRecipe m)
- Cauldron: withRecipe :: forall {m :: Type -> Type} r. (forall bean. Typeable bean => Recipe m bean -> r) -> SomeRecipe m -> r
- Cauldron.Beans: instance GHC.Base.Monoid Cauldron.Beans.Beans
- Cauldron.Beans: instance GHC.Base.Semigroup Cauldron.Beans.Beans
- Cauldron.Beans: instance GHC.IsList.IsList Cauldron.Beans.Beans
- Cauldron.Beans: instance GHC.Show.Show Cauldron.Beans.Beans
- Cauldron.Beans: instance GHC.Show.Show Cauldron.Beans.SomeMonoidTypeRep
- Cauldron.Builder: instance Control.Monad.Fix.MonadFix (Cauldron.Builder.Builder m)
- Cauldron.Builder: instance GHC.Base.Applicative (Cauldron.Builder.Builder m)
- Cauldron.Builder: instance GHC.Base.Functor (Cauldron.Builder.Builder m)
- Cauldron.Builder: instance GHC.Base.Monad (Cauldron.Builder.Builder m)
- Cauldron.Builder: instance GHC.Base.Monad m => Cauldron.Builder.MonadWiring (Cauldron.Builder.Builder m)
- Cauldron.Builder: instance GHC.Exception.Type.Exception Cauldron.Builder.DuplicateBeans
- Cauldron.Builder: instance GHC.Show.Show Cauldron.Builder.DuplicateBeans
- Cauldron.Managed: instance Control.Monad.Fail.MonadFail Cauldron.Managed.Managed
- Cauldron.Managed: instance Control.Monad.Fix.MonadFix Cauldron.Managed.Managed
- Cauldron.Managed: instance Control.Monad.IO.Class.MonadIO Cauldron.Managed.Managed
- Cauldron.Managed: instance GHC.Base.Applicative Cauldron.Managed.Managed
- Cauldron.Managed: instance GHC.Base.Functor Cauldron.Managed.Managed
- Cauldron.Managed: instance GHC.Base.Monad Cauldron.Managed.Managed
+ Cauldron: (|=|) :: forall {recipelike} {m :: Type -> Type}. (ToRecipe recipelike, HasCallStack) => forall bean -> Typeable bean => recipelike m bean -> Cauldron m
+ Cauldron: (䷱) :: forall {recipelike} {m :: Type -> Type}. (ToRecipe recipelike, HasCallStack) => forall bean -> Typeable bean => recipelike m bean -> Cauldron m
+ Cauldron: [bare] :: Recipe (m :: Type -> Type) bean -> Constructor m bean
+ Cauldron: data Args a
+ Cauldron: infixr 0 ䷱
+ Cauldron: instance GHC.Internal.Base.Monoid (Cauldron.Cauldron m)
+ Cauldron: instance GHC.Internal.Base.Monoid Cauldron.BeanGetter
+ Cauldron: instance GHC.Internal.Base.Monoid Cauldron.DependencyGraph
+ Cauldron: instance GHC.Internal.Base.Semigroup (Cauldron.Cauldron m)
+ Cauldron: instance GHC.Internal.Base.Semigroup Cauldron.BeanGetter
+ Cauldron: instance GHC.Internal.Base.Semigroup Cauldron.DependencyGraph
+ Cauldron: instance GHC.Internal.Exception.Type.Exception Cauldron.CookingError
+ Cauldron: instance GHC.Internal.IsList.IsList (Cauldron.Cauldron m)
+ Cauldron: instance GHC.Internal.Show.Show Cauldron.BeanConstructionStep
+ Cauldron: instance GHC.Internal.Show.Show Cauldron.CookingError
+ Cauldron: instance GHC.Internal.Show.Show Cauldron.DependencyCycle
+ Cauldron: instance GHC.Internal.Show.Show Cauldron.DependencyGraph
+ Cauldron: instance GHC.Internal.Show.Show Cauldron.DoubleDutyBean
+ Cauldron: instance GHC.Internal.Show.Show Cauldron.MissingDependencies
+ Cauldron: lookup :: forall {m :: Type -> Type} bean r. Typeable bean => (NonEmpty CallStack -> Recipe m bean -> r) -> Cauldron m -> Maybe r
+ Cauldron: singleton :: forall {recipelike} {m :: Type -> Type} bean. (Typeable bean, ToRecipe recipelike, HasCallStack) => recipelike m bean -> Cauldron m
+ 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.Builder: instance GHC.Internal.Base.Applicative (Cauldron.Builder.Builder m)
+ Cauldron.Builder: instance GHC.Internal.Base.Functor (Cauldron.Builder.Builder m)
+ Cauldron.Builder: instance GHC.Internal.Base.Monad (Cauldron.Builder.Builder m)
+ Cauldron.Builder: instance GHC.Internal.Base.Monad m => Cauldron.Builder.MonadWiring (Cauldron.Builder.Builder m)
+ Cauldron.Builder: instance GHC.Internal.Control.Monad.Fix.MonadFix (Cauldron.Builder.Builder m)
+ Cauldron.Builder: instance GHC.Internal.Exception.Type.Exception Cauldron.Builder.DuplicateBeans
+ Cauldron.Builder: instance GHC.Internal.Show.Show Cauldron.Builder.DuplicateBeans
+ Cauldron.Managed: instance GHC.Internal.Base.Applicative Cauldron.Managed.Managed
+ Cauldron.Managed: instance GHC.Internal.Base.Functor Cauldron.Managed.Managed
+ Cauldron.Managed: instance GHC.Internal.Base.Monad Cauldron.Managed.Managed
+ Cauldron.Managed: instance GHC.Internal.Base.Monoid a => GHC.Internal.Base.Monoid (Cauldron.Managed.Managed a)
+ Cauldron.Managed: instance GHC.Internal.Base.Semigroup a => GHC.Internal.Base.Semigroup (Cauldron.Managed.Managed a)
+ Cauldron.Managed: instance GHC.Internal.Control.Monad.Fail.MonadFail Cauldron.Managed.Managed
+ Cauldron.Managed: instance GHC.Internal.Control.Monad.Fix.MonadFix Cauldron.Managed.Managed
+ Cauldron.Managed: instance GHC.Internal.Control.Monad.IO.Class.MonadIO Cauldron.Managed.Managed
+ Cauldron.Managed: runManaged :: Managed () -> IO ()
- Cauldron: adjust :: forall {m :: Type -> Type} bean. Typeable bean => (Recipe m bean -> Recipe m bean) -> Cauldron m -> Cauldron m
+ Cauldron: adjust :: forall {m :: Type -> Type} bean. (Typeable bean, HasCallStack) => (Recipe m bean -> Recipe m bean) -> Cauldron m -> Cauldron m
- Cauldron: hoistConstructor' :: (Args (m (Regs bean)) -> Args (n (Regs bean))) -> Constructor m bean -> Constructor n bean
+ Cauldron: hoistConstructor' :: forall m n bean. (Args (m (Regs bean)) -> Args (n (Regs bean))) -> Constructor m bean -> Constructor 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: hoistRecipe' :: forall m n bean. (Args (m (Regs bean)) -> Args (n (Regs bean))) -> (Int -> Args (m (Regs bean)) -> Args (n (Regs bean))) -> Recipe m bean -> Recipe n bean
- Cauldron: recipe :: forall {recipelike} {m :: Type -> Type} bean. (ToRecipe recipelike, Typeable bean, HasCallStack) => recipelike m bean -> SomeRecipe m
+ Cauldron: recipe :: forall {recipelike} {m :: Type -> Type} bean. (Typeable bean, ToRecipe recipelike, HasCallStack) => recipelike m bean -> Cauldron m

Files

CHANGELOG.md view
@@ -1,5 +1,25 @@ # Revision history for cauldron +## 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 `Cauldron`s.+* 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](https://hackage.haskell.org/package/managed) library.++* Export `Cauldron.Args.Args` from `Cauldron`. This should make some IDE+  messages clearer.+ ## 0.8.0.0  * doc and test changes.
README.md view
@@ -169,7 +169,7 @@  # Acknowledgements -This package contains vendored code from Grabriella Gonzalez's+This package contains vendored code from Gabriella Gonzalez's [managed](https://hackage.haskell.org/package/managed) library.  Also vendored code from Andrey Mokhov's
app/Main.hs view
@@ -194,30 +194,30 @@  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 @Result $ val $ wire Result-  ]+    [ 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+          { bare = val $ wire makeG,+            decos =+              [ val $ wire makeGDeco1+              ]+          },+      recipe @H $ val $ wire makeH,+      recipe @Z+        Recipe+          { bare = val $ wire makeZ,+            decos =+              [ val $ wire makeZDeco1,+                val $ wire makeZDeco2+              ]+          },+      recipe @Result $ val $ wire Result+    ]  main :: IO () main = do@@ -240,7 +240,7 @@       print z       runInitializer       pure $ Nothing-  let depGraph = getDependencyGraph cauldron+  let depGraph = getDependencyGraph [cauldron]   writeAsDot (defaultStyle merr) "beans.dot" depGraph   writeAsDot (defaultStyle merr) "beans-no-agg.dot" $ removeAggregates $ depGraph   writeAsDot (defaultStyle merr) "beans-no-agg-no-decos.dot" $ removeDecos $ removeAggregates $ depGraph
cauldron.cabal view
@@ -1,6 +1,6 @@ cabal-version:      3.4 name:               cauldron-version:            0.8.0.0+version:            0.9.0.0 synopsis:           Dependency injection library description:        Dependency injection library that wires things at runtime. license:            BSD-3-Clause@@ -12,7 +12,7 @@ extra-doc-files:    README.md, CHANGELOG.md -- extra-source-files: category: Dependency Injection-tested-with: GHC ==9.8.2+tested-with: GHC ==9.10.1, GHC==9.12.2 source-repository    head     type:     git     location: https://github.com/danidiaz/cauldron.git
lib-graph/Cauldron/Graph.hs view
@@ -96,7 +96,7 @@ import Data.Set (Set) import Data.Set qualified as Set import Data.String-import Data.Tree (Tree(..), Forest)+import Data.Tree (Forest, Tree (..)) import GHC.Generics  -- | The 'AdjacencyMap' data type represents a graph by a map of vertices to
lib/Cauldron.hs view
@@ -1,12 +1,10 @@ {-# LANGUAGE BlockArguments #-} {-# LANGUAGE DataKinds #-}-{-# LANGUAGE DeriveAnyClass #-}-{-# LANGUAGE DerivingStrategies #-} {-# LANGUAGE DerivingVia #-} {-# LANGUAGE DuplicateRecordFields #-}-{-# LANGUAGE FunctionalDependencies #-} {-# LANGUAGE GADTs #-} {-# LANGUAGE LambdaCase #-}+{-# LANGUAGE RequiredTypeArguments #-} {-# LANGUAGE TypeFamilies #-} {-# LANGUAGE ViewPatterns #-} {-# LANGUAGE NoFieldSelectors #-}@@ -21,12 +19,11 @@ -- 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:+-- These extensions, while not required, can 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--- @+-- * @ApplicativeDo@ For advanced fiddling in the 'Args' applicative.+-- * @OverloadedLists@ For avoiding explicit calls to 'mconcat' when building+--   a 'Cauldron' from a list of 'Cauldron's, and for avoiding explicit calls to 'fromDecoList'. -- -- An example of using a 'Cauldron' to wire the constructors of dummy @A@, @B@, @C@ datatypes: --@@ -42,10 +39,10 @@ -- makeC = \_ _ -> pure C -- :} ----- The basic idea is to fill up the 'Cauldron' with 'recipe's. 'Recipe's are+-- The basic idea is to fill the 'Cauldron' with 'recipe's. 'Recipe's are -- built by 'wire'ing the arguments of a constructor function, and then using--- functions like 'val' or 'eff' depending on whether the constructor is--- effectful or not. More sophisticated 'Recipe's can also have decorators.+-- functions like 'val_' or 'eff_' depending on whether the constructor is+-- effectful or not. More complex 'Recipe's can also have decorators. -- -- The we 'cook' the 'Cauldron' passing as a type argument the type of the bean -- that we want to extract, along with a 'Fire' argument that regulates what@@ -54,31 +51,33 @@ -- >>> :{ -- 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+--       cauldron = mconcat [+--           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 <- cook @C forbidDepCycles cauldron & either throwIO pure+--   action <- cauldron & cook @C forbidDepCycles & either throwIO pure --   action -- :} -- C -- -- __Note__: It's better to avoid having beans whose types are functions or--- tuples, because those types are given special treatment. See the docs for--- 'wire', 'val', and 'eff'.---+-- tuples, because the library gives those types special treatment. See the docs+-- for 'wire', 'val', and 'eff'. module Cauldron   ( -- * Filling the cauldron     Cauldron,     empty,+    recipe,+    singleton,+    (|=|),+    (䷱),     insert,     adjust,+    Cauldron.lookup,     delete,     keysSet,     restrictKeys,-    fromRecipeList,-    toRecipeMap,     hoistCauldron,     hoistCauldron', @@ -94,33 +93,33 @@     -- ** How decorators work     -- $decos -    -- ** Hiding a 'Recipe''s bean type-    SomeRecipe,-    recipe,-    withRecipe,-    getRecipeCallStack,-     -- * Constructors     -- $constructors     Constructor,+    Args,     arg,     wire,     val_,-    val,-    val',     eff_,     ioEff_,++    -- ** Registering aggregate beans+    -- $secondarybeans+    val,+    val',     eff,-    ioEff,     eff',+    ioEff,++    -- ** "with"-like constructors+    -- $managedconstructors++    -- ** Sundry constructor helpers     getConstructorArgs,     getConstructorCallStack,     hoistConstructor,     hoistConstructor', -    -- ** Registering aggregate beans-    -- $secondarybeans-     -- * Cooking the beans     cook,     nest,@@ -166,6 +165,7 @@ import Cauldron.Graph.Export.Dot qualified as Dot import Control.Applicative ((<|>)) import Control.Exception (Exception (..))+import Control.Monad (guard) import Control.Monad.Fix import Control.Monad.IO.Class import Data.Bifunctor (first)@@ -214,11 +214,6 @@ 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}@@ -241,36 +236,35 @@ -- 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+  SomeRecipe :: (Typeable bean) => {_recipeCallStacks :: NonEmpty CallStack, _recipe :: Recipe m bean} -> SomeRecipe m --- | Build a 'SomeRecipe' from a 'Recipe' or a 'Constructor'. See 'ToRecipe'.+-- | Access a 'Recipe' inside a 'Cauldron', if it exists there. ----- 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)+-- A callback is used because 'Cauldron's are heterogenous containers.+--+-- Besides the 'Recipe', the callback also receives a 'NonEmpty' list of+-- 'CallStack's conveying the locations at which the the 'Recipe' was added and+-- 'adjust'ed.+lookup :: forall {m} (bean :: Type) r. (Typeable bean) => (NonEmpty CallStack -> Recipe m bean -> r) -> Cauldron m -> Maybe r+lookup f (Cauldron {recipeMap}) = withFrozenCallStack do+  let rep = typeRep (Proxy @bean)+  case recipeMap & Map.lookup rep of+    Nothing -> Nothing+    Just SomeRecipe {_recipeCallStacks, _recipe} ->+      case Wrap1 _recipe of+        Wrap1 @_ @ta _ ->+          case testEquality (Type.Reflection.typeRep @bean) (Type.Reflection.typeRep @ta) of+            Nothing -> error "should never happen"+            Just Refl -> Just $ f _recipeCallStacks _recipe -fromRecipeList :: [SomeRecipe m] -> Cauldron m-fromRecipeList =-  foldMap \sr -> Cauldron {recipeMap = Map.singleton (getRecipeRep sr) sr}+-- -- The alternative would be to introduce @_recipe = _recipe :: Recipe m a@+-- -- "The type abstraction syntax can be used in patterns that match a data constructor. The syntax can’t be used with record patterns or infix patterns."+-- -- [Type Abstractions in Patterns](https://ghc.gitlab.haskell.org/ghc/doc/users_guide/exts/type_abstractions.html#type-abstractions-in-patterns)+newtype Wrap1 f a = Wrap1 (f a) -toRecipeMap :: Cauldron m -> Map TypeRep (SomeRecipe m)-toRecipeMap Cauldron {recipeMap} = recipeMap+-- | Access the 'Recipe' inside a 'SomeRecipe'.+withRecipe' :: forall {m} r. (forall bean. (Typeable bean) => NonEmpty CallStack -> Recipe m bean -> r) -> SomeRecipe m -> r+withRecipe' f (SomeRecipe {_recipeCallStacks, _recipe}) = f _recipeCallStacks _recipe  hoistSomeRecipe :: (forall x. m x -> n x) -> SomeRecipe m -> SomeRecipe n hoistSomeRecipe f r@SomeRecipe {_recipe} = r {_recipe = hoistRecipe f _recipe}@@ -281,35 +275,45 @@   (forall x. (Typeable x) => Int -> Args (m (Regs x)) -> Args (n (Regs x))) ->   SomeRecipe m ->   SomeRecipe n-hoistSomeRecipe' f fds sr = withRecipe go sr+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}+    go :: forall bean. (Typeable bean) => NonEmpty CallStack -> Recipe m bean -> SomeRecipe n+    go _ r = sr {_recipe = hoistRecipe' (f @bean) (fds @bean) r} +-- | Somewhat unusual instance, in that the 'Cauldron' 'Item' is again a 'Cauldron'. Mostly+-- useful to avoid explicit 'mconcat's when building 'Cauldron's from lists.+instance IsList (Cauldron m) where+  type Item (Cauldron m) = Cauldron m++  toList (Cauldron {recipeMap}) = do+    (k, v) <- Map.toList recipeMap+    [Cauldron {recipeMap = Map.singleton k v}]+  fromList = mconcat+ -- | 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.+-- 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,+    bare :: 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 :: forall m bean. [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.+-- to the 'recipe' and 'insert' functions. type ToRecipe :: ((Type -> Type) -> Type -> Type) -> Constraint class ToRecipe recipelike where-  toRecipe :: recipelike m bean -> Recipe m bean+  toRecipe :: forall m bean. recipelike m bean -> Recipe m bean  -- | Simply identity. instance ToRecipe Recipe where@@ -317,35 +321,36 @@  -- | 'Constructor' is converted to a 'Recipe' without decorators. instance ToRecipe Constructor where-  toRecipe bean = Recipe {bean, decos = Data.Sequence.empty}+  toRecipe bare = Recipe {bare, 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}) =+hoistRecipe :: forall m n bean. (forall x. m x -> n x) -> Recipe m bean -> Recipe n bean+hoistRecipe f (Recipe {bare, decos}) =   Recipe-    { bean = hoistConstructor f bean,+    { bare = hoistConstructor f bare,       decos = hoistConstructor f <$> decos     }  -- | More general form of 'hoistRecipe' that enables precise control over the inner `Args` -- of each constructor in the 'Recipe'. hoistRecipe' ::+  forall m n bean.   -- | 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}) =+hoistRecipe' f fds (Recipe {bare, decos}) =   Recipe-    { bean = hoistConstructor' f bean,+    { bare = hoistConstructor' f bare,       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+-- Decorators are 'Constructor's which, instead of constructing the original+-- version of a bean, modify it in some way (but without changing its -- type). Because they modify the bean, typically decorators will take the bean -- as an argument. --@@ -371,17 +376,18 @@ -- -- >>> :{ -- do---   let cauldron :: Cauldron IO---       cauldron = [---           recipe @Foo $ Recipe {---             bean = val $ wire makeFoo,---             decos = [---                  val $ wire makeFooDeco1,---                  eff $ wire makeFooDeco2---               ]---           }---         ]---   action <- cook @Foo forbidDepCycles cauldron & either throwIO pure+--   action <-+--     mconcat [+--       recipe @Foo $ Recipe {+--         bare = val $ wire makeFoo,+--         decos = [+--              val $ wire makeFooDeco1,+--              eff $ wire makeFooDeco2+--           ]+--       }+--     ]+--     & cook @Foo forbidDepCycles+--     & either throwIO pure --   Foo {sayFoo} <- action --   sayFoo -- :}@@ -396,15 +402,37 @@ -- -- Bean-producing and bean-decorating functions need to be coaxed into -- 'Constructor's in order to be used in 'Cauldron's.--- +-- -- First we fill the arguments of the function in an 'Args' context, either one -- by one using 'arg's and 'Applicative' operators, or all in a single swoop, -- using 'wire'. -- -- Then, depending on whether the function produces the desired bean directly,--- or through an effect, we use functions like 'val', 'val_', 'eff' or 'eff_' on+-- or through an effect, we use functions like 'val_', 'val', 'eff_' or 'eff' on -- the 'Args' value.++-- $managedconstructors --+-- Some effectful constructor functions, in order to manage the acquisition and+-- release of the bean they produce, use the common idiom or returning a+-- higher-order function that takes a callback. A typical example is+-- 'System.IO.withFile'.+--+-- These effecful constructor functions can be coaxed into 'Constructor's that+-- have their effects in a monad like 'Cauldron.Managed.Managed'.+--+-- We need to wrap the callback-accepting part in 'Cauldron.Managed.managed',+-- before we lift the function to 'Cauldron.Args.Args' using 'wire':+--+-- >>> :{+-- -- We treat the 'IOMode' as if it were a bean dependency.+-- handleBean :: Constructor Managed Handle+-- handleBean = eff_ $ wire $ \mode -> managed $ withFile "/tmp/foo.txt" mode+-- :}+--+-- Annoyingly, this forces us to be a bit more verbose and explicitly mention+-- the constructor parameters (in the example, @mode@) in order to reach the+-- part that we wrap in 'Cauldron.Managed.Managed'.  data ConstructorReps where   ConstructorReps ::@@ -414,6 +442,63 @@     } ->     ConstructorReps +-- | Create a 'Cauldron' consisting of a single 'Recipe'.+--+-- 'recipe' and 'singleton' are the same function.+--+-- For readability, the @bean@ type is often passed as a type application, despite+-- not being strictly required:+--+-- >>> :{+-- oneRecipe :: Cauldron IO+-- oneRecipe = recipe @Bool $ val_ $ pure $ False+-- :}+--+-- Typical usage involves putting singleton 'Cauldron's in a list and 'mconcat'ting them:+--+-- >>> :{+-- twoRecipes :: Cauldron IO+-- twoRecipes = mconcat [+--      recipe $ val_ $ pure $ False,+--      recipe @Char $ val_ $ wire $ \(_ :: Bool) -> 'b'+--    ]+-- :}+recipe,+  singleton ::+    forall {recipelike} {m} bean.+    (Typeable bean, ToRecipe recipelike, HasCallStack) =>+    -- | A 'Recipe' or a 'Constructor'.+    recipelike m bean ->+    Cauldron m+singleton theRecipe = withFrozenCallStack do+  mempty & insert theRecipe+recipe theRecipe = withFrozenCallStack do+  mempty & insert theRecipe++-- | Operator variant of 'recipe' where the @bean@ type is a [required type argument](https://ghc.gitlab.haskell.org/ghc/doc/users_guide/exts/required_type_arguments.html).+--+-- '(|=|)' and '(䷱)' are the same function.+--+-- >>> :{+-- oneRecipe, oneRecipe' :: Cauldron IO+-- oneRecipe = Bool |=| val $ pure $ False+-- oneRecipe' = Bool ䷱ val $ pure $ False+-- :}+(|=|),+  (䷱) ::+    forall {recipelike} {m}.+    (ToRecipe recipelike, HasCallStack) =>+    forall bean ->+    (Typeable bean) =>+    recipelike m bean ->+    Cauldron m+(|=|) _ recipelike = withFrozenCallStack do singleton recipelike+(䷱) _ recipelike = withFrozenCallStack do singleton recipelike++infixr 0 |=|++infixr 0 ䷱+ -- | Put a 'Recipe' into the 'Cauldron'. -- -- Only one recipe is allowed for each bean type, so 'insert' for a@@ -427,12 +512,12 @@   Cauldron m insert recipelike Cauldron {recipeMap} = withFrozenCallStack do   let rep = typeRep (Proxy @bean)-  Cauldron {recipeMap = Map.insert rep (SomeRecipe callStack (toRecipe recipelike)) recipeMap}+  Cauldron {recipeMap = Map.insert rep (SomeRecipe (Data.List.NonEmpty.singleton callStack) (toRecipe recipelike)) recipeMap} --- | Tweak a 'Recipe' inside the 'Cauldron', if the recipe exists.+-- | Tweak a 'Recipe' inside the 'Cauldron', if it exists there. adjust ::   forall {m} bean.-  (Typeable bean) =>+  (Typeable bean, HasCallStack) =>   (Recipe m bean -> Recipe m bean) ->   Cauldron m ->   Cauldron m@@ -443,10 +528,14 @@         recipeMap           & Map.adjust             do-              \r@SomeRecipe {_recipe = _recipe :: Recipe m a} ->+              \SomeRecipe {_recipeCallStacks, _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}+                  Just Refl ->+                    SomeRecipe+                      { _recipeCallStacks = _recipeCallStacks `Data.List.NonEmpty.appendList` [callStack],+                        _recipe = f _recipe+                      }             rep     } @@ -484,10 +573,9 @@ -- :} -- -- >>> :{---   cook @A forbidDepCycles ([---       recipe @A $ val $ wire loopyA---       ] :: Cauldron IO)---       & \case Left (DependencyCycleError _) -> "self dep is forbidden"; _ -> "oops"+--   (recipe @A $ val $ wire loopyA :: Cauldron IO)+--      & cook @A forbidDepCycles+--      & \case Left (DependencyCycleError _) -> "self dep is forbidden"; _ -> "oops" -- :} -- "self dep is forbidden" forbidDepCycles :: (Monad m) => Fire m@@ -523,9 +611,8 @@ -- :} -- -- >>> :{---   cook @A allowSelfDeps ([---       recipe @A $ val $ wire loopyA---       ] :: Cauldron IO)+--   (recipe @A $ val $ wire loopyA :: Cauldron IO)+--       & cook @A allowSelfDeps --       & \case Left (DependencyCycleError _) -> "oops"; _ -> "self dep is ok" -- :} -- "self dep is ok"@@ -540,11 +627,12 @@ -- :} -- -- >>> :{---   cook @U allowSelfDeps ([+--   mconcat [ --       recipe @U $ val $ wire loopyU,---       recipe @V $ val $ wire loopyV---       ] :: Cauldron IO)---       & \case Left (DependencyCycleError _) -> "cycle between 2 deps"; _ -> "oops"+--       recipe @V $ val $ wire loopyV :: Cauldron IO+--   ]+--    & cook @U allowSelfDeps+--    & \case Left (DependencyCycleError _) -> "cycle between 2 deps"; _ -> "oops" -- :} -- "cycle between 2 deps" allowSelfDeps :: (MonadFix m) => Fire m@@ -577,11 +665,12 @@ -- :} -- -- >>> :{---   cook @U allowDepCycles ([+--   mconcat [ --       recipe @U $ val $ wire loopyU,---       recipe @V $ val $ wire loopyV---       ] :: Cauldron IO)---       & \case Left (DependencyCycleError _) -> "oops"; _ -> "cycles are ok"+--       recipe @V $ val $ wire loopyV :: Cauldron IO+--   ]+--     & cook @U allowDepCycles+--     & \case Left (DependencyCycleError _) -> "oops"; _ -> "cycles are ok" -- :} -- "cycles are ok" allowDepCycles :: (MonadFix m) => Fire m@@ -596,7 +685,7 @@     }  fixyFollowPlanCauldron :: (MonadFix m) => Cauldron m -> BeanGetter -> Beans -> [BeanConstructionStep] -> m Beans-fixyFollowPlanCauldron = \cauldron previous initial plan -> do+fixyFollowPlanCauldron cauldron previous initial plan = do   mfix do     \final -> do       -- We prefer the final beans.@@ -640,8 +729,8 @@     AggregateBean TypeRep   deriving stock (Show, Eq, Ord) --- | Build the requested @bean@ using the 'Recipe's stored in the 'Cauldron'.--- The 'Cauldron' must contain a 'Recipe' for the requested bean, as well as+-- | Build the requested @bean@ using the 'Recipe's stored in 'Cauldron'.+-- The 'Cauldron's must contain a 'Recipe' for the requested bean, as well as -- 'Recipe's for producing all of its transitive dependencies. -- -- >>> :{@@ -649,7 +738,8 @@ -- :} -- -- >>> :{--- cook @A forbidDepCycles (mempty :: Cauldron IO)+-- (mempty :: Cauldron IO)+--  & cook @A forbidDepCycles --  & \case Left (MissingResultBeanError _) -> "no recipe for requested bean"; _ -> "oops" -- :} -- "no recipe for requested bean"@@ -660,7 +750,8 @@ -- :} -- -- >>> :{--- cook @B forbidDepCycles ([recipe $ val $ wire B] :: Cauldron IO)+-- (singleton $ val $ wire B :: Cauldron IO)+--  & cook @B forbidDepCycles --  & \case Left (MissingDependenciesError _) -> "no recipe for A"; _ -> "oops" -- :} -- "no recipe for A"@@ -669,7 +760,7 @@   (Monad m, Typeable bean) =>   -- | The types of dependency cycles that are allowed between beans.   Fire m ->-  -- | A 'Cauldron' containing the necessary 'Recipe's.+  -- | 'Cauldron's containing the necessary 'Recipe's.   Cauldron m ->   Either CookingError (m bean) cook fire cauldron = do@@ -682,10 +773,9 @@     pure bean  -- |------ Takes a 'Cauldron' converts it into a 'Constructor' where any unfilled--- dependencies are taken as the arguments of the 'Constructor'.  The--- 'Constructor' can later be included in a bigger 'Cauldron', which will+-- Takes a 'Cauldron' and converts it into a 'Constructor' where+-- any unfilled dependencies are taken as the arguments of the 'Constructor'.+-- The 'Constructor' can later be included in a bigger 'Cauldron', which will -- provide the missing dependencies. -- -- This function never fails with 'MissingDependenciesError'.@@ -714,15 +804,15 @@ -- -- >>> :{ -- do---   nested :: Constructor IO C <- nest @C forbidDepCycles [+--   nested :: Constructor IO C <- nest @C forbidDepCycles (mconcat [ --       recipe @A $ val $ wire makeA2, -- this will be used by makeC --       recipe @C $ val $ wire makeC -- takes B from outside---       ] & either throwIO pure---   action <- cook @C forbidDepCycles [+--       ]) & either throwIO pure+--   action <- cook @C forbidDepCycles (mconcat [ --       recipe @A $ val $ wire makeA, --       recipe @B $ val $ wire makeB, --       recipe @C $ nested---       ] & either throwIO pure+--       ]) & either throwIO pure --   C c <- action --   c -- :}@@ -735,11 +825,11 @@ -- -- >>> :{ -- do---   action <- cook @C forbidDepCycles [+--   action <- cook @C forbidDepCycles (mconcat [ --       recipe @A $ val $ wire makeA, --       recipe @B $ val $ wire makeB, --       recipe @C $ val $ wire makeC---       ] & either throwIO pure+--       ]) & either throwIO pure --   C c <- action --   c -- :}@@ -752,7 +842,7 @@   (Monad m, Typeable bean, HasCallStack) =>   -- | The types of dependency cycles that are allowed between beans.   Fire m ->-  -- | A 'Cauldron', possibly with unfilled dependencies.+  -- | 'Cauldron's, possibly with unfilled dependencies.   Cauldron m ->   Either CookingError (Constructor m bean) nest fire cauldron = withFrozenCallStack do@@ -770,7 +860,7 @@   () <- first MissingResultBeanError do checkEntryPointPresent (typeRep (Proxy @bean)) (Map.keysSet accumMap) cauldron   plan <- first DependencyCycleError do buildPlan shouldEnforceDependency cauldron   let missingDeps = collectMissingDeps (Map.keysSet accumMap) (Cauldron.keysSet cauldron) cauldron-  Right $+  Right     ( missingDeps,       Constructor         { _constructorCallStack = callStack,@@ -795,7 +885,7 @@   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.+-- be obtained even if the 'mconcat'ted 'Cauldron's can't be 'cook'ed successfully. getDependencyGraph :: Cauldron m -> DependencyGraph getDependencyGraph cauldron =   let (_, deps) = buildDepsCauldron cauldron@@ -807,7 +897,7 @@ checkNoDoubleDutyBeans cauldron = do   let (accumMap, beanSet) = cauldronRegs cauldron   let common = do-        (tr, (cs1,cs2)) <- Map.toList $ Map.intersectionWith (,) (fst <$> accumMap) beanSet+        (tr, (cs1, cs2)) <- Map.toList $ Map.intersectionWith (,) (fst <$> accumMap) beanSet         [DoubleDutyBean tr cs1 cs2]   case common of     ddb : ddbs -> Left $ ddb Data.List.NonEmpty.:| ddbs@@ -816,21 +906,22 @@ 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))+    do \rep aRecipe -> (recipeRegs aRecipe, Map.singleton rep (Data.List.NonEmpty.head $ getRecipeCallStack aRecipe))     recipeMap  -- | Returns the accumulators, not the main bean recipeRegs :: SomeRecipe m -> Map TypeRep (CallStack, Dynamic)-recipeRegs (SomeRecipe _ (Recipe {bean, decos})) = do+recipeRegs (SomeRecipe _ (Recipe {bare, decos})) = do   let extractRegReps c = (getConstructorCallStack c,) <$> (\ConstructorReps {regReps} -> regReps) (constructorReps c)-  extractRegReps bean+  extractRegReps bare     <> foldMap extractRegReps decos  -- | Missing depencencies for a 'Constructor'. data MissingDependencies = MissingDependencies CallStack TypeRep (Set TypeRep)   deriving stock (Show) -missingDepsToArgReps :: (Functor f, Foldable f) => +missingDepsToArgReps ::+  (Functor f, Foldable f) =>   f MissingDependencies ->   Set TypeRep missingDepsToArgReps = Set.unions . fmap (\(MissingDependencies _ _ missing) -> missing)@@ -843,17 +934,16 @@   Cauldron m ->   [MissingDependencies] collectMissingDeps accums available cauldron =-  demandsByConstructorsInCauldron cauldron & Data.Foldable.foldMap \(stack, tr, demanded) ->+  demandsByConstructorsInCauldron cauldron & Data.Foldable.foldMap \(stack, tr, demanded) -> do     let missing = Set.filter (`Set.notMember` (available `Set.union` accums)) demanded-     in if Set.null missing-          then []-          else [MissingDependencies stack tr missing]+    guard $ not (Set.null missing)+    [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)]+  (tr, SomeRecipe _ (Recipe {bare, decos})) <- Map.toList recipeMap+  ( let ConstructorReps {argReps = beanArgReps} = constructorReps bare+     in [(getConstructorCallStack bare, tr, beanArgReps)]     )     ++ do       decoCon <- Data.Foldable.toList decos@@ -874,16 +964,16 @@     Right plan -> do       Right plan -buildDepsCauldron :: Cauldron m -> (Map BeanConstructionStep CallStack, [(BeanConstructionStep, BeanConstructionStep)])+buildDepsCauldron :: forall m. Cauldron m -> (Map BeanConstructionStep CallStack, [(BeanConstructionStep, BeanConstructionStep)]) buildDepsCauldron Cauldron {recipeMap} = do   recipeMap     & Map.foldMapWithKey       \beanRep        SomeRecipe-         { _recipeCallStack,+         { _recipeCallStacks,            _recipe =              Recipe-               { bean = bean :: Constructor m bean,+               { bare,                  decos                }          } ->@@ -894,7 +984,7 @@                   (decoIndex, decoCon) <- zip [0 :: Int ..] (Data.Foldable.toList decos)                   [(PrimaryBeanDeco beanRep decoIndex, decoCon)]                 beanDeps = do-                  constructorEdges bareBean (constructorReps bean)+                  constructorEdges bareBean (constructorReps bare)                 decoDeps = do                   (decoStep, decoCon) <- decoSteps                   -- We remove the bean because from the args becase, in the@@ -912,8 +1002,8 @@                     -- 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)+                [ (bareBean, getConstructorCallStack bare),+                  (boiledBean, Data.List.NonEmpty.head _recipeCallStacks)                 ]                   ++ do                     (decoStep, decoCon) <- decoSteps@@ -942,7 +1032,7 @@              ]        ) -data BeanGetter = BeanGetter {_run :: forall t. (Typeable t) => Maybe t}+newtype BeanGetter = BeanGetter {_run :: forall t. (Typeable t) => Maybe t}  instance Semigroup BeanGetter where   BeanGetter {_run = run1} <> BeanGetter {_run = run2} =@@ -980,9 +1070,9 @@ followPlanStep makeBareView makeDecoView Cauldron {recipeMap} 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-        inserter <- followConstructor bean (makeBareView beanRep super)+      SomeRecipe {_recipe = Recipe {bare}} -> do+        let ConstructorReps {beanRep} = constructorReps bare+        inserter <- followConstructor bare (makeBareView beanRep super)         pure do inserter super     PrimaryBeanDeco rep index -> case fromJust do Map.lookup rep recipeMap of       SomeRecipe {_recipe = Recipe {decos}} -> do@@ -1001,6 +1091,7 @@ -- This can only work without blowing up if there aren't dependecy cycles -- and the order of construction respects the depedencies! followConstructor ::+  forall m bean.   (Monad m, Typeable bean) =>   Constructor m bean ->   BeanGetter ->@@ -1093,7 +1184,7 @@ removeDecos DependencyGraph {graph} =   DependencyGraph {graph = Graph.induce (\case PrimaryBeanDeco {} -> False; _ -> True) graph} --- | Unifies 'PrimaryBean's with their respective 'BarePrimaryBean's and 'PrimaryBeanDeco's.+-- | Unifies 'FinishedBean's with their respective 'BarePrimaryBean's, 'PrimaryBeanDeco's and 'AggregateBean's. -- -- Also removes any self-loops. collapseBeans :: DependencyGraph -> DependencyGraph@@ -1140,31 +1231,29 @@           []         Just (MissingDependenciesError missingDeps) ->           let missing = missingDepsToArgReps missingDeps-           in-          case step of-            FinishedBean rep-              | Set.member rep missing ->-                  [ fromString "style" Dot.:= fromString "dashed",-                    fromString "color" Dot.:= fromString "red"-                  ]-            _ -> []+           in case step of+                FinishedBean rep+                  | Set.member rep missing ->+                      [ fromString "style" Dot.:= fromString "dashed",+                        fromString "color" Dot.:= fromString "red"+                      ]+                _ -> []         Just (DoubleDutyBeansError doubleDutyBeans) ->-          let bs =  Set.fromList $ do-                      DoubleDutyBean ddb _ _ <- Data.List.NonEmpty.toList doubleDutyBeans-                      [ddb]-          in-          case step of-            FinishedBean rep-              | Set.member rep bs ->-                  [ fromString "style" Dot.:= fromString "bold",-                    fromString "color" Dot.:= fromString "green"-                  ]-            AggregateBean rep-              | Set.member rep bs ->-                  [ fromString "style" Dot.:= fromString "bold",-                    fromString "color" Dot.:= fromString "green"-                  ]-            _ -> []+          let bs = Set.fromList $ do+                DoubleDutyBean ddb _ _ <- Data.List.NonEmpty.toList doubleDutyBeans+                [ddb]+           in case step of+                FinishedBean rep+                  | Set.member rep bs ->+                      [ fromString "style" Dot.:= fromString "bold",+                        fromString "color" Dot.:= fromString "green"+                      ]+                AggregateBean rep+                  | Set.member rep bs ->+                      [ fromString "style" Dot.:= fromString "bold",+                        fromString "color" Dot.:= fromString "green"+                      ]+                _ -> []         Just (DependencyCycleError (DependencyCycle (Set.fromList . Data.Foldable.toList . fmap fst -> cycleStepSet))) ->           if Set.member step cycleStepSet             then@@ -1202,7 +1291,7 @@ -- -- 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+-- constructor will acquire 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,@@ -1307,6 +1396,7 @@ eff' = Constructor callStack  runConstructor ::+  forall m bean.   (Monad m) =>   BeanGetter ->   Constructor m bean ->@@ -1317,27 +1407,27 @@   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 :: forall m n bean. (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' :: forall m n bean. (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 :: forall m bean. 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 :: forall m bean. 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+getRecipeCallStack :: SomeRecipe m -> NonEmpty CallStack+getRecipeCallStack (SomeRecipe {_recipeCallStacks}) = _recipeCallStacks  -- | The set of all 'TypeRep' keys of the map. keysSet :: Cauldron m -> Set TypeRep@@ -1411,12 +1501,12 @@ -- >>> :{ -- do --   let cauldron :: Cauldron Identity---       cauldron = [+--       cauldron = mconcat [ --           recipe @U $ val $ wire makeU, --           recipe @V $ val $ wire makeV, --           recipe @W $ val $ wire W --         ]---   Identity w <- cook @W forbidDepCycles cauldron & either throwIO pure+--   Identity w <- cauldron & cook @W forbidDepCycles & either throwIO pure --   pure w -- :} -- W (Sum {getSum = 8})@@ -1432,20 +1522,26 @@ -- :} -- -- >>> :{---   cook @X forbidDepCycles ([+--   (mconcat [ --       recipe @X $ val $ wire makeX,---       recipe @(Sum Int) $ val $ wire makeAgg---       ] :: Cauldron IO)---       & \case Left (DoubleDutyBeansError _) -> "Sum Int is aggregate and primary"; _ -> "oops"+--       recipe @(Sum Int) $ val $ wire makeAgg :: Cauldron IO+--   ])+--     & cook @X forbidDepCycles+--     & \case Left (DoubleDutyBeansError _) -> "Sum Int is aggregate and primary"; _ -> "oops" -- :} -- "Sum Int is aggregate and primary"  -- $setup -- >>> :set -XBlockArguments+-- >>> :set -XApplicativeDo -- >>> :set -XOverloadedLists+-- >>> :set -XRequiredTypeArguments+-- >>> :set -XExplicitNamespaces -- >>> :set -Wno-incomplete-uni-patterns -- >>> import Data.Functor.Identity -- >>> import Data.Function ((&)) -- >>> import Data.Monoid -- >>> import Data.Either (either, isLeft) -- >>> import Control.Exception (throwIO)+-- >>> import System.IO+-- >>> import Cauldron.Managed
lib/Cauldron/Args/Internal.hs view
@@ -212,9 +212,9 @@ -- \"from the future\". -- -- >>> :{--- runArgs (taste Cauldron.Beans.empty) (arg @Int)+-- try @LazilyReadBeanMissing $ evaluate $ runArgs (taste Cauldron.Beans.empty) (arg @Int) -- :}--- *** Exception: LazilyReadBeanMissing Int+-- Left (LazilyReadBeanMissing Int) -- -- If more safety is needed, one can perform additional preliminary checks with -- the help of 'getArgsReps'.@@ -407,3 +407,5 @@ -- >>> import Data.Function ((&)) -- >>> import Data.Monoid -- >>> import Cauldron.Beans (taste)+-- >>> import System.IO+-- >>> import Control.Exception
lib/Cauldron/Beans.hs view
@@ -2,7 +2,6 @@ {-# LANGUAGE DataKinds #-} {-# LANGUAGE DerivingStrategies #-} {-# LANGUAGE GADTs #-}-{-# LANGUAGE LambdaCase #-} {-# LANGUAGE TypeFamilies #-} {-# LANGUAGE NoFieldSelectors #-} @@ -39,9 +38,10 @@ import Data.Semigroup qualified import Data.Set (Set) import Data.Set qualified as Set+import Data.Type.Equality (TestEquality (testEquality)) import Data.Typeable import GHC.IsList-import Type.Reflection (SomeTypeRep (..), eqTypeRep)+import Type.Reflection (SomeTypeRep (..)) import Type.Reflection qualified  empty :: Beans@@ -67,7 +67,7 @@ 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+        Just (Dynamic tr' v) | Just Refl <- tr `testEquality` tr' -> Just v         _ -> Nothing  -- | A map of 'Dynamic' values, indexed by the 'TypeRep' of each 'Dynamic'.@@ -116,11 +116,11 @@  instance Eq SomeMonoidTypeRep where   (SomeMonoidTypeRep tr1) == (SomeMonoidTypeRep tr2) =-    (SomeTypeRep tr1) == (SomeTypeRep tr2)+    SomeTypeRep tr1 == SomeTypeRep tr2  instance Ord SomeMonoidTypeRep where   (SomeMonoidTypeRep tr1) `compare` (SomeMonoidTypeRep tr2) =-    (SomeTypeRep tr1) `compare` (SomeTypeRep tr2)+    SomeTypeRep tr1 `compare` SomeTypeRep tr2  -- | The 'mempty' value corresponding to the inner 'Type.Reflection.TypeRep'. someMonoidTypeRepMempty :: SomeMonoidTypeRep -> Dynamic@@ -141,8 +141,8 @@       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 ->+            | Just Refl <- tr' `testEquality` tr1,+              Just Refl <- tr' `testEquality` tr2 ->                 Type.Reflection.withTypeable tr' (toDyn (v1 <> v2))           _ -> d2    in Beans $ Map.unionWithKey combine beans1 beans2
lib/Cauldron/Builder.hs view
@@ -1,6 +1,5 @@ {-# LANGUAGE BlockArguments #-} {-# LANGUAGE DerivingStrategies #-}-{-# LANGUAGE FunctionalDependencies #-} {-# LANGUAGE LambdaCase #-} {-# LANGUAGE TypeFamilies #-} {-# LANGUAGE UndecidableInstances #-}@@ -76,7 +75,6 @@ where  import Cauldron-import Cauldron.Args import Cauldron.Managed import Control.Exception (Exception (..)) import Control.Monad.Fix
lib/Cauldron/Managed.hs view
@@ -1,10 +1,15 @@ {-# LANGUAGE BlockArguments #-} +-- | A datatype that encapsulates @with...@-style callback-taking functions that+-- acquire and release resources.+--+-- Here's [a video about how it works](https://youtu.be/2v7BAQh_dRs). module Cauldron.Managed   ( -- * The Managed monad for handling resources     Managed,     managed,     with,+    runManaged,   ) where @@ -50,10 +55,6 @@     where       unManage (Managed a) = a --- | Make use of the managed resource by supplying a callback.-with :: Managed a -> (a -> IO b) -> IO b-with (Managed r) = r- instance Functor Managed where   fmap f (Managed m) = Managed (\k -> m (\x -> k (f x)))   {-# INLINE fmap #-}@@ -77,6 +78,23 @@   {-# INLINE liftIO #-}  instance MonadFail Managed where-    fail s = Managed (\return_ -> do-        a <- fail @IO s-        return_ a )+  fail s =+    Managed+      ( \return_ -> do+          a <- fail @IO s+          return_ a+      )++instance (Semigroup a) => Semigroup (Managed a) where+  (<>) = liftA2 (<>)++instance (Monoid a) => Monoid (Managed a) where+  mempty = pure mempty++-- | Make use of the managed resource by supplying a callback.+with :: Managed a -> (a -> IO b) -> IO b+with (Managed r) = r++-- | Run a `Managed` computation, enforcing that no acquired resources leak+runManaged :: Managed () -> IO ()+runManaged (Managed r) = r pure
test/appTests.hs view
@@ -112,17 +112,17 @@  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,+  mconcat+    [ singleton $ val $ pure makeA,+      singleton $ val $ pure makeB,+      singleton $ val $ wire makeC,+      singleton $ val $ wire makeD,+      singleton $ 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,+          { bare = val $ wire makeG,             decos =               fromDecoList                 [ val $ wire makeGDeco1@@ -131,7 +131,7 @@       recipe @H $ val $ wire makeH,       recipe @Z         Recipe-          { bean = val $ wire makeZ,+          { bare = val $ wire makeZ,             decos =               fromDecoList                 [ val $ wire makeZDeco1,
test/codecTests.hs view
@@ -65,7 +65,7 @@  cauldron :: Cauldron Identity cauldron =-  fromRecipeList+  mconcat     [ recipe @(Serializer Foo) $ val $ wire makeFooSerializer,       recipe @(Serializer Bar) $ val $ wire makeBarSerializer,       recipe @(Serializer Baz) $ val $ wire makeBazSerializer@@ -113,7 +113,7 @@  cauldronAccums1 :: Cauldron Identity cauldronAccums1 =-  fromRecipeList+  mconcat     [ 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)@@ -121,7 +121,7 @@  cauldronAccums2 :: Cauldron Identity cauldronAccums2 =-  fromRecipeList+  mconcat     [ 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)@@ -129,7 +129,7 @@  cauldronAccumsOops1 :: Cauldron Identity cauldronAccumsOops1 =-  fromRecipeList+  mconcat     [ 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)@@ -137,7 +137,7 @@  cauldronAccumsOops2 :: Cauldron Identity cauldronAccumsOops2 =-  fromRecipeList+  mconcat     [ 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)
test/managedTests.hs view
@@ -72,11 +72,11 @@  managedCauldron :: IORef [Text] -> Cauldron Managed managedCauldron ref =-  fromRecipeList+  mconcat     [ recipe @(Logger IO) $ eff $ wire $ managed (makeLogger ref),       recipe @(Weird IO)         Recipe-          { bean = eff do+          { bare = eff do               wire \logger self -> managed (makeSelfInvokingWeird ref logger self),             decos =               fromDecoList
test/tests.hs view
@@ -1,6 +1,5 @@ {-# LANGUAGE ApplicativeDo #-} {-# LANGUAGE BlockArguments #-}-{-# LANGUAGE DerivingStrategies #-} {-# LANGUAGE DerivingVia #-} {-# LANGUAGE OverloadedLists #-} {-# LANGUAGE OverloadedStrings #-}@@ -14,6 +13,7 @@ import Data.Foldable qualified import Data.Function ((&)) import Data.IORef+import Data.List (sort) import Data.Map (Map) import Data.Map qualified as Map import Data.Monoid@@ -22,7 +22,6 @@ import Data.Typeable (typeRep) import Test.Tasty import Test.Tasty.HUnit-import Data.List (sort)  type Text = String @@ -111,7 +110,6 @@  cauldron :: Cauldron M cauldron =-  fromRecipeList     [ recipe @(Logger M) $ eff $ pure makeLogger,       recipe @(Repository M) $ eff $ wire makeRepository,       recipe @(Initializer, Repository M) $ val_ $ wire (,)@@ -135,14 +133,12 @@  cauldronX1 :: Cauldron M cauldronX1 =-  fromRecipeList     [ recipe @(Logger M) $ eff $ pure makeLogger,       recipe @(Weird M) $ eff $ wire makeWeird -- overwritten     ]  cauldronX2 :: Cauldron M cauldronX2 =-  fromRecipeList     [ recipe @(Repository M) $ eff $ do         action <- wire makeRepository         pure do@@ -150,7 +146,7 @@           pure (initializer, repo),       recipe @(Weird M)         Recipe-          { bean = eff $ wire makeSelfInvokingWeird,+          { bare = eff $ wire makeSelfInvokingWeird,             decos =               fromDecoList                 [ val $ wire (weirdDeco "inner"),@@ -162,12 +158,8 @@  data Result = Result Initializer (Repository M) (Weird M) -cauldronX :: Cauldron M-cauldronX = cauldronX1 <> cauldronX2- cauldronLonely :: Cauldron M cauldronLonely =-  fromRecipeList     [ recipe @(Lonely M) $ val $ pure makeLonely     ] @@ -175,8 +167,12 @@ tests =   testGroup     "All"-    [ testCase "value" do-        (_, traces) <- case cook' cauldron of+    [ testCase "lookup" do+        case cauldron & Cauldron.lookup @(Logger M) (\_ _ -> ()) of+          Just () -> pure ()+          Nothing -> assertFailure "bean not found",+      testCase "value" do+        (_, traces) <- case cook' [cauldron] of           Left _ -> assertFailure "could not wire"           Right beansAction -> runWriterT do             boiledBeans <- beansAction@@ -195,7 +191,7 @@           ]           traces,       testCase "value sequential" do-        ((), traces) <- case cook @Result allowSelfDeps cauldronX of+        ((), traces) <- case cook @Result allowSelfDeps (mconcat [cauldronX1, cauldronX2]) of           Left _ -> assertFailure "could not wire"           Right beansAction -> do             runWriterT do@@ -208,23 +204,25 @@               pure ()         assertEqual           "traces"-          (sort [ -- "weird constructor", -- not happens, because overwritten-            -- the order of the traces here is a bit too overspecified. several orders could be valid.-            "logger constructor",-            "self-invoking weird constructor",-            "logger init",-            "repo init invoking logger",-            "store",-            "findById",-            -- the deco is applied! The outer the deco, the earliest is invoked.-            "deco for anotherWeirdOp outer",-            "deco for anotherWeirdOp inner",-            "another weirdOp 2",-            "deco for weirdOp outer",-            "deco for weirdOp inner",-            -- note that the self-invocation used the method from 'makeSelfInvokingWeird'-            "weirdOp 2"-          ])+          ( sort+              [ -- "weird constructor", -- not happens, because overwritten+                -- the order of the traces here is a bit too overspecified. several orders could be valid.+                "logger constructor",+                "self-invoking weird constructor",+                "logger init",+                "repo init invoking logger",+                "store",+                "findById",+                -- the deco is applied! The outer the deco, the earliest is invoked.+                "deco for anotherWeirdOp outer",+                "deco for anotherWeirdOp inner",+                "another weirdOp 2",+                "deco for weirdOp outer",+                "deco for weirdOp inner",+                -- note that the self-invocation used the method from 'makeSelfInvokingWeird'+                "weirdOp 2"+              ]+          )           (sort traces),       -- case getDependencyGraph cauldronNonEmpty of       --  dg2  -> do@@ -237,8 +235,12 @@        testCase "value nested" do         ((), traces) <- case ( do-                                 constructorX2 <- nest allowSelfDeps cauldronX2-                                 cook @Result allowSelfDeps (cauldronX1 & Cauldron.insert @Result constructorX2)+                                 constructorX2 <- nest allowSelfDeps [cauldronX2]+                                 cook @Result+                                   allowSelfDeps+                                   (mconcat [ cauldronX1,+                                     Cauldron.recipe @Result constructorX2+                                   ])                              ) of           Left _ -> assertFailure "could not wire"           Right beansAction -> do@@ -252,24 +254,26 @@               pure ()         assertEqual           "traces"-          (sort [ -- the order of the traces here is a bit too overspecified. several orders could be valid.-            "logger constructor",-            "self-invoking weird constructor",-            "weird constructor", -- note that this is present. Overwritten by nested, but still built-            -- The absence of the logger init is because we are only getting the aggregate beans from the nested.-            -- "logger init",-            "repo init invoking logger",-            "store",-            "findById",-            -- the deco is applied! The outer the deco, the earliest is invoked.-            "deco for anotherWeirdOp outer",-            "deco for anotherWeirdOp inner",-            "another weirdOp 2",-            "deco for weirdOp outer",-            "deco for weirdOp inner",-            -- note that the self-invocation used the method from 'makeSelfInvokingWeird'-            "weirdOp 2"-          ])+          ( sort+              [ -- the order of the traces here is a bit too overspecified. several orders could be valid.+                "logger constructor",+                "self-invoking weird constructor",+                "weird constructor", -- note that this is present. Overwritten by nested, but still built+                -- The absence of the logger init is because we are only getting the aggregate beans from the nested.+                -- "logger init",+                "repo init invoking logger",+                "store",+                "findById",+                -- the deco is applied! The outer the deco, the earliest is invoked.+                "deco for anotherWeirdOp outer",+                "deco for anotherWeirdOp inner",+                "another weirdOp 2",+                "deco for weirdOp outer",+                "deco for weirdOp inner",+                -- note that the self-invocation used the method from 'makeSelfInvokingWeird'+                "weirdOp 2"+              ]+          )           (sort traces),       -- case getDependencyGraph cauldronNonEmpty of       --  dg2  -> do@@ -290,19 +294,20 @@             pure ()         pure (),       testCase "cauldron missing dep" do-        case cook' cauldronMissingDep of-          Left (MissingDependenciesError missingDeps )-            | [MissingDependencies _ tr missingSet]  <- Data.Foldable.toList missingDeps,-              tr == typeRep (Proxy @(Repository M)) && missingSet == Data.Set.singleton (typeRep (Proxy @(Logger M))) -> pure ()+        case cook' [cauldronMissingDep] of+          Left (MissingDependenciesError missingDeps)+            | [MissingDependencies _ tr missingSet] <- Data.Foldable.toList missingDeps,+              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+        case cook' [cauldronDoubleDutyBean] of           Left (DoubleDutyBeansError _) -> pure ()           _ -> assertFailure "double duty beans not detected"         pure (),       testCase "cauldron with cycle" do-        case cook' cauldronWithCycle of+        case cook' [cauldronWithCycle] of           Left (DependencyCycleError (DependencyCycle vs)) ->             -- Why not a cycle of length 3? Because there also are bare versions for each bean.             assertEqual "cycle of the expected length" 4 (Data.Foldable.length vs)