packages feed

dep-t-advice 0.5.1.0 → 0.6.0.0

raw patch · 10 files changed

+360/−104 lines, 10 filesdep ~dep-tPVP ok

version bump matches the API change (PVP)

Dependency ranges changed: dep-t

API changes (from Hackage documentation)

- Dep.Advice: instance (GHC.Base.Functor m, Dep.Advice.Multicurryable as e_ m r advised) => Dep.Advice.DistributiveProduct e_ m (GHC.Generics.S1 ('GHC.Generics.MetaSel msymbol su ss ds) (GHC.Generics.Rec0 advised))
- Dep.SimpleAdvice.Basic: SyntheticCallStackException :: SomeException -> SyntheticCallStack -> SyntheticCallStackException
- Dep.SimpleAdvice.Basic: data SyntheticCallStackException
- Dep.SimpleAdvice.Basic: instance GHC.Exception.Type.Exception Dep.SimpleAdvice.Basic.SyntheticCallStackException
- Dep.SimpleAdvice.Basic: instance GHC.Show.Show Dep.SimpleAdvice.Basic.SyntheticCallStackException
+ Dep.Advice: instance (GHC.Base.Functor m, Dep.Advice.DistributiveSubcomponent (Dep.Advice.DiscriminateDistributiveSubcomponent advised) e_ m advised) => Dep.Advice.DistributiveProduct e_ m (GHC.Generics.S1 ('GHC.Generics.MetaSel msymbol su ss ds) (GHC.Generics.Rec0 advised))
+ Dep.Advice: instance (GHC.Base.Functor m, Dep.Advice.DistributiveSubcomponent (Dep.Advice.DiscriminateDistributiveSubcomponent advised) e_ m advised) => Dep.Advice.DistributiveSubcomponent 'Dep.Advice.IWrapped e_ m (Data.Functor.Identity.Identity advised)
+ Dep.Advice: instance (GHC.Base.Functor m, Dep.Advice.DistributiveSubcomponent (Dep.Advice.DiscriminateDistributiveSubcomponent advised) e_ m advised) => Dep.Advice.DistributiveSubcomponent 'Dep.Advice.IWrapped e_ m (Data.SOP.BasicFunctors.I advised)
+ Dep.Advice: instance (GHC.Base.Functor m, Dep.Advice.Multicurryable as e_ m r advised) => Dep.Advice.DistributiveSubcomponent 'Dep.Advice.Terminal e_ m advised
+ Dep.Advice: instance Dep.Advice.DistributiveRecord e_ m subrecord => Dep.Advice.DistributiveSubcomponent 'Dep.Advice.Recurse e_ m (subrecord (Control.Monad.Dep.DepT e_ m))
+ Dep.SimpleAdvice.Basic: SyntheticStackTraceException :: SomeException -> SyntheticStackTrace -> SyntheticStackTraceException
+ Dep.SimpleAdvice.Basic: data SyntheticStackTraceException
+ Dep.SimpleAdvice.Basic: instance (GHC.Base.Monad m, Dep.SimpleAdvice.Basic.HasSyntheticCallStack (e_ (Control.Monad.Dep.DepT e_ m))) => Dep.SimpleAdvice.Basic.MonadCallStack (Control.Monad.Dep.DepT e_ m)
+ Dep.SimpleAdvice.Basic: instance (GHC.Base.Monad m, Dep.SimpleAdvice.Basic.HasSyntheticCallStack runenv) => Dep.SimpleAdvice.Basic.MonadCallStack (Control.Monad.Trans.Reader.ReaderT runenv m)
+ Dep.SimpleAdvice.Basic: instance Dep.SimpleAdvice.Basic.MonadCallStack m => Dep.SimpleAdvice.Basic.MonadCallStack (Dep.SimpleAdvice.Internal.AspectT m)
+ Dep.SimpleAdvice.Basic: instance GHC.Exception.Type.Exception Dep.SimpleAdvice.Basic.SyntheticStackTraceException
+ Dep.SimpleAdvice.Basic: instance GHC.Show.Show Dep.SimpleAdvice.Basic.SyntheticStackTraceException
+ Dep.SimpleAdvice.Basic: instance forall k s (x :: k). Dep.SimpleAdvice.Basic.HasSyntheticCallStack s => Dep.SimpleAdvice.Basic.HasSyntheticCallStack (Data.Functor.Const.Const s x)
+ Dep.SimpleAdvice.Basic: type SyntheticStackTrace = NonEmpty StackFrame
- Dep.SimpleAdvice.Basic: keepCallStack :: (MonadUnliftIO m, MonadReader runenv m, HasSyntheticCallStack runenv, Exception e) => (SomeException -> Maybe e) -> NonEmpty (TypeRep, MethodName) -> Advice ca m r
+ Dep.SimpleAdvice.Basic: keepCallStack :: (MonadUnliftIO m, MonadCallStack m, Exception e) => (SomeException -> Maybe e) -> NonEmpty (TypeRep, MethodName) -> Advice ca m r
- Dep.SimpleAdvice.Basic: type StackFrame = (TypeRep, MethodName)
+ Dep.SimpleAdvice.Basic: type StackFrame = NonEmpty (TypeRep, MethodName)

Files

CHANGELOG.md view
@@ -1,5 +1,20 @@ # Revision history for dep-t-advice
 
+
+## 0.6.0.0 
+
+* SyntheticCallStackException renamed to SyntheticStackTraceException.
+  SyntheticStackTraceException now contains NonEmpty stack traces. 
+
+  Also, for each frame, the full path in the environment to the component is
+  provided. This is helpful for Dep.Tagged components.
+  
+  [Issue #18](https://github.com/danidiaz/dep-t-advice/issues/18).
+
+* `Dep.Advice.component` can now treat components with nested records.
+
+* Removed deprecated modules.
+
 ## 0.5.1.0 
 
 * `Control.Monad.whatever` renamed to `whatever`. 
dep-t-advice.cabal view
@@ -1,7 +1,7 @@ cabal-version:       3.0
 
 name:                dep-t-advice
-version:             0.5.1.0
+version:             0.6.0.0
 synopsis:            Giving good advice to functions in records-of-functions.
 description:         
                      Companion to the dep-t package. Easily add behaviour to
@@ -30,7 +30,7 @@   build-depends:       base >=4.10.0.0 && < 5,
                        sop-core ^>= 0.5.0.0,
                        transformers ^>= 0.5.0.0,
-                       dep-t >= 0.5.1.0 && < 0.6,
+                       dep-t ^>= 0.6,
                        mtl ^>= 2.2,
                        unliftio-core ^>= 0.2.0.0,
   default-language:    Haskell2010
@@ -58,11 +58,6 @@                        Dep.Advice.Basic
                        Dep.SimpleAdvice
                        Dep.SimpleAdvice.Basic
-                       -- deprecated
-                       Control.Monad.Dep.Advice
-                       Control.Monad.Dep.Advice.Basic
-                       Control.Monad.Dep.SimpleAdvice
-                       Control.Monad.Dep.SimpleAdvice.Basic
   other-modules:       Dep.SimpleAdvice.Internal    
                         
   hs-source-dirs:      lib 
− lib/Control/Monad/Dep/Advice.hs
@@ -1,7 +0,0 @@-module Control.Monad.Dep.Advice {-# DEPRECATED "Renamed to Dep.Advice" #-}
-  (
-    module Dep.Advice
-  )
-where
-
-import Dep.Advice
− lib/Control/Monad/Dep/Advice/Basic.hs
@@ -1,7 +0,0 @@-module Control.Monad.Dep.Advice.Basic {-# DEPRECATED "Renamed to Dep.Advice.Basic" #-}
-  ( 
-    module Dep.Advice.Basic
-  )
-where
-
-import Dep.Advice.Basic
− lib/Control/Monad/Dep/SimpleAdvice.hs
@@ -1,8 +0,0 @@-module Control.Monad.Dep.SimpleAdvice {-# DEPRECATED "Renamed to Dep.SimpleAdvice" #-}
-  (
-    module Dep.SimpleAdvice
-  )
-where
-
-import Dep.SimpleAdvice
-
− lib/Control/Monad/Dep/SimpleAdvice/Basic.hs
@@ -1,7 +0,0 @@-module Control.Monad.Dep.SimpleAdvice.Basic {-# DEPRECATED "Renamed to Dep.SimpleAdvice.Basic" #-}
-  (
-    module Dep.SimpleAdvice.Basic 
-  )
-where
-
-import Dep.SimpleAdvice.Basic
lib/Dep/Advice.hs view
@@ -766,13 +766,59 @@ 
 instance
   ( 
+    Functor m,
+    DistributiveSubcomponent (DiscriminateDistributiveSubcomponent advised) e_ m advised
+  ) =>
+  DistributiveProduct e_ m (G.S1 ( 'G.MetaSel msymbol su ss ds) (G.Rec0 advised))
+  where
+  _distributeProduct f = G.M1 . G.K1 $ _distributeSubcomponent @(DiscriminateDistributiveSubcomponent advised) @e_ @m @advised (fmap (fmap (G.unK1 . G.unM1))  f)
+
+-- Here we have dropped the polymorphic parameter in the last type argument.
+type DistributiveSubcomponent :: RecordComponent -> ((Type -> Type) -> Type) -> (Type -> Type) -> Type -> Constraint
+class DistributiveSubcomponent component_type e_ m sub where
+  _distributeSubcomponent ::  (e_ (DepT e_ m) -> m sub) -> sub
+
+instance
+  ( 
     Functor m, 
     Multicurryable as e_ m r advised
   ) =>
-  DistributiveProduct e_ m (G.S1 ( 'G.MetaSel msymbol su ss ds) (G.Rec0 advised))
+  DistributiveSubcomponent Terminal e_ m advised
   where
-  _distributeProduct f = G.M1 . G.K1 $ askFinalDepT @as @e_ @m @r (fmap (fmap (G.unK1 . G.unM1))  f)
+  _distributeSubcomponent f = askFinalDepT @as @e_ @m @r f
 
+instance
+  (
+  Functor m,
+  DistributiveSubcomponent (DiscriminateDistributiveSubcomponent advised) e_ m advised 
+  ) =>
+  DistributiveSubcomponent IWrapped e_ m (Identity advised)
+  where
+  _distributeSubcomponent f = Identity (_distributeSubcomponent @(DiscriminateDistributiveSubcomponent advised) @e_ @m (fmap (fmap runIdentity) f))
+
+instance
+  (
+  Functor m,
+  DistributiveSubcomponent (DiscriminateDistributiveSubcomponent advised) e_ m advised 
+  ) =>
+  DistributiveSubcomponent IWrapped e_ m (I advised)
+  where
+  _distributeSubcomponent f = I (_distributeSubcomponent @(DiscriminateDistributiveSubcomponent advised) @e_ @m (fmap (fmap unI) f))
+
+instance
+    (DistributiveRecord e_ m subrecord)
+    =>
+    DistributiveSubcomponent Recurse e_ m (subrecord (DepT e_ m)) where
+  _distributeSubcomponent f = _distribute @e_ @m f
+
+type DiscriminateDistributiveSubcomponent :: Type -> RecordComponent
+type family DiscriminateDistributiveSubcomponent c where
+  DiscriminateDistributiveSubcomponent (a -> b) = Terminal
+  DiscriminateDistributiveSubcomponent (DepT e_ m x) = Terminal
+  DiscriminateDistributiveSubcomponent (Identity _) = IWrapped
+  DiscriminateDistributiveSubcomponent (I _) = IWrapped
+  DiscriminateDistributiveSubcomponent _ = Recurse
+
 -- advising *all* fields of a record
 --
 --
@@ -829,12 +875,6 @@      in G.M1 (G.K1 (_adviseComponent @(DiscriminateAdvisedComponent advised) @ca @e_ @m @cr acc' f advised))
 
 instance
-  AdvisedRecord ca e_ m cr advisable =>
-  AdvisedComponent Recurse ca e_ m cr (advisable (DepT e_ m))
-  where
-  _adviseComponent acc f advised = _adviseRecord @ca @e_ @m @cr acc f advised
-
-instance
   (Multicurryable as e_ m r advised, All ca as, cr r, Monad m) =>
   AdvisedComponent Terminal ca e_ m cr advised
   where
@@ -851,6 +891,13 @@   AdvisedComponent IWrapped ca e_ m cr (I advised)
   where
   _adviseComponent acc f (I advised) = I (_adviseComponent @(DiscriminateAdvisedComponent advised) @ca @e_ @m @cr acc f advised)
+
+instance
+  AdvisedRecord ca e_ m cr advisable =>
+  AdvisedComponent Recurse ca e_ m cr (advisable (DepT e_ m))
+  where
+  _adviseComponent acc f advised = _adviseRecord @ca @e_ @m @cr acc f advised
+
 
 -- | Gives 'Advice' to all the functions in a record-of-functions.
 --
lib/Dep/SimpleAdvice/Basic.hs view
@@ -3,6 +3,7 @@ {-# LANGUAGE GADTs #-}
 {-# LANGUAGE ImportQualifiedPost #-}
 {-# LANGUAGE RankNTypes #-}
+{-# LANGUAGE PolyKinds #-}
 {-# LANGUAGE ScopedTypeVariables #-}
 {-# LANGUAGE StandaloneKindSignatures #-}
 {-# LANGUAGE TypeApplications #-}
@@ -12,6 +13,9 @@ {-# LANGUAGE ViewPatterns #-}
 {-# LANGUAGE TypeSynonymInstances #-}
 {-# LANGUAGE FlexibleInstances #-}
+{-# LANGUAGE UndecidableInstances #-}
+{-# LANGUAGE StandaloneDeriving #-}
+{-# LANGUAGE GeneralisedNewtypeDeriving #-}
 
 -- |
 -- This module contains basic examples advices.
@@ -32,8 +36,9 @@     MethodName,
     StackFrame,
     SyntheticCallStack,
-    SyntheticCallStackException (..),
     HasSyntheticCallStack (..),
+    SyntheticStackTrace,
+    SyntheticStackTraceException (..),
     keepCallStack
   )
 where
@@ -48,6 +53,7 @@ import Data.SOP (hctraverse_)
 import Data.SOP.NP
 import Data.Type.Equality
+import Data.Coerce
 import System.IO
 import Control.Concurrent
 import Control.Monad.IO.Unlift
@@ -57,6 +63,8 @@ import qualified Data.Typeable as T
 import Data.List.NonEmpty (NonEmpty (..))
 import Data.List.NonEmpty qualified as NonEmpty
+import Control.Monad.Dep (DepT)
+import Data.Functor.Const
 
 -- $setup
 --
@@ -182,17 +190,34 @@ 
 -- | The typeable representation of the record which contains the invoked
 -- function, along with the field name of the invoked function.
-type StackFrame = (T.TypeRep, MethodName)
+type StackFrame = NonEmpty (T.TypeRep, MethodName)
 
 type SyntheticCallStack = [StackFrame]
 
+type SyntheticStackTrace = NonEmpty StackFrame
+
 -- | Wraps an exception along with a 'SyntheticCallStack'.
-data SyntheticCallStackException
-  = SyntheticCallStackException SomeException SyntheticCallStack
+data SyntheticStackTraceException
+  = SyntheticStackTraceException SomeException SyntheticStackTrace
   deriving stock Show
 
-instance Exception SyntheticCallStackException
+instance Exception SyntheticStackTraceException
 
+-- | Monads that carry a SyntheticCallStack.
+class MonadCallStack m where
+  askCallStack :: m SyntheticCallStack
+  addStackFrame :: StackFrame -> m r -> m r
+
+instance (Monad m, HasSyntheticCallStack runenv) => MonadCallStack (ReaderT runenv m) where
+  askCallStack = asks (view callStack)
+  addStackFrame frame action = local (over callStack (frame :)) action
+
+instance (Monad m, HasSyntheticCallStack (e_ (DepT e_ m))) => MonadCallStack (DepT e_ m) where
+  askCallStack = asks (view callStack)
+  addStackFrame frame action = local (over callStack (frame :)) action
+
+deriving newtype instance MonadCallStack m => MonadCallStack (AspectT m)
+
 -- | Class of environments that carry a 'SyntheticCallStack' value that can be
 -- modified.
 class HasSyntheticCallStack e where
@@ -204,6 +229,10 @@ instance HasSyntheticCallStack SyntheticCallStack where
     callStack = id
 
+instance HasSyntheticCallStack s => HasSyntheticCallStack (Const s x) where
+    callStack f = fmap Const . callStack f . getConst
+
+
 -- | If the environment carries a 'SyntheticCallStack', make advised functions add
 -- themselves to the 'SyntheticCallStack' before they start executing.
 --
@@ -211,28 +240,30 @@ -- doesn't need to be 'Control.Monad.Dep.DepT', it can be regular a
 -- 'Control.Monad.Reader.ReaderT'.
 --
--- Caught exceptions are rethrown wrapped in 'SyntheticCallStackException's,
+-- Caught exceptions are rethrown wrapped in 'SyntheticStackTraceException's,
 -- with the current 'SyntheticCallStack' added.
 keepCallStack ::
-  (MonadUnliftIO m, MonadReader runenv m, HasSyntheticCallStack runenv, Exception e) =>
+  (MonadUnliftIO m, MonadCallStack m, Exception e) =>
   -- | A selector for the kinds of exceptions we want to catch.
   -- For example @fromException \@IOError@.
   (SomeException -> Maybe e) ->
-  -- | The path to the current component/method in the environment. Only the
-  -- head is used. It will be usually obtained through
+  -- | The path to the current component/method in the environment.
+  -- It will be usually obtained through
   -- 'Dep.SimpleAdvice.adviseRecord'.
   NonEmpty (T.TypeRep, MethodName) ->
   Advice ca m r
-keepCallStack selector (NonEmpty.head -> method) = makeExecutionAdvice \action -> do
-  currentStack <- asks (view callStack)
+keepCallStack selector method = makeExecutionAdvice \action -> do
+  currentStack <- askCallStack
   withRunInIO \unlift -> do
-    er <- tryJust selector (unlift (local (over callStack (method :)) action))
+    er <- tryJust selector (unlift (addStackFrame method action))
     case er of
-      Left e -> throwIO (SyntheticCallStackException (toException e) (method : currentStack))
+      Left e -> throwIO (SyntheticStackTraceException (toException e) (method :| currentStack))
       Right r -> pure r
-  where
-  view l = getConstant . l Constant
-  over l f = runIdentity . l (Identity . f)
 
 
+view :: ((a1 -> Constant a1 b1) -> a2 -> Constant c b2) -> a2 -> c
+view l = getConstant . l Constant
+
+over :: ((a1 -> Identity a2) -> a3 -> Identity c) -> (a1 -> a2) -> a3 -> c
+over l f = runIdentity . l (Identity . f)
 
test/doctests.hs view
@@ -5,8 +5,8 @@ main =
   doctest
     [ "-ilib",
-      "lib/Control/Monad/Dep/Advice.hs"
-    , "lib/Control/Monad/Dep/Advice/Basic.hs"
-    , "lib/Control/Monad/Dep/SimpleAdvice.hs"
-    , "lib/Control/Monad/Dep/SimpleAdvice/Basic.hs"
+      "lib/Dep/Advice.hs"
+    , "lib/Dep/Advice/Basic.hs"
+    , "lib/Dep/SimpleAdvice.hs"
+    , "lib/Dep/SimpleAdvice/Basic.hs"
     ]
test/synthetic-callstack-tests.hs view
@@ -35,6 +35,7 @@ import Control.Monad.Trans.Cont
 import Data.Function ((&))
 import Data.Functor ((<&>))
+import Control.Arrow ((>>>))
 import Data.Functor.Compose
 import Data.Functor.Constant
 import Data.Functor.Identity
@@ -62,6 +63,7 @@   ( Has (dep),
     asCall,
   )
+import Dep.Tagged (Tagged (..), tagged, untag)
 import Dep.SimpleAdvice
   ( Advice,
     AspectT (..),
@@ -70,13 +72,23 @@     advising,
     makeExecutionAdvice,
   )
-import Dep.SimpleAdvice.Basic (HasSyntheticCallStack (callStack), MethodName, StackFrame, SyntheticCallStack, SyntheticCallStackException (SyntheticCallStackException), injectFailures, keepCallStack)
+import Dep.SimpleAdvice.Basic
+  ( HasSyntheticCallStack (callStack),
+    MethodName,
+    StackFrame,
+    SyntheticCallStack,
+    SyntheticStackTrace,
+    SyntheticStackTraceException (SyntheticStackTraceException),
+    injectFailures,
+    keepCallStack,
+  )
 import GHC.Generics (Generic)
 import GHC.TypeLits
 import Lens.Micro (Lens', lens)
 import System.IO
 import Test.Tasty
 import Test.Tasty.HUnit
+import Data.Functor.Const
 import Prelude hiding (insert, lookup)
 
 -- THE BUSINESS LOGIC
@@ -143,9 +155,25 @@     { route = \toInsert toLookup -> do
         call emitMsg "serving..."
         call insert toInsert
+        call emitMsg "before lookup..."
         call lookup toLookup
     }
 
+makeController2Loggers :: 
+  (Has Logger m env, 
+  Has (Tagged "secondary" Logger) m env, Has Repository m env, Monad m) =>
+  env ->
+  Controller m
+makeController2Loggers (asCall -> call) =
+  Controller
+    { route = \toInsert toLookup -> do
+        call (untag @"secondary" >>> emitMsg) "serving..."
+        call insert toInsert
+        call emitMsg "before lookup..."
+        call lookup toLookup
+    }
+
+
 -- THE COMPOSITION ROOT
 --
 -- Here we define our dependency injection environment.
@@ -159,6 +187,7 @@ -- "Higer-Kinded Data" pattern.)
 data Env h m = Env
   { logger :: h (Logger m),
+    logger2 :: h (Tagged "secondary" Logger m),
     repository :: h (Repository m),
     controller :: h (Controller m)
   }
@@ -185,7 +214,7 @@ --
 -- There could be more phases, like for example an initial "read configuration"
 -- phase.
-type Phases env_ m = Allocator `Compose` Constructor env_ m
+type Phases env = Allocator `Compose` Constructor env
 
 -- Environment value
 --
@@ -194,30 +223,41 @@ --
 -- Notice that neither the interfaces nor the implementations which we defined
 -- earlier knew anything about the ReaderT.
-env :: Env (Phases Env (ReaderT SyntheticCallStack IO)) (ReaderT SyntheticCallStack IO)
+env :: Env (Phases (Env Identity (ReaderT SyntheticCallStack IO))) (ReaderT SyntheticCallStack IO)
 env =
   Env
     { logger =
         allocateBombs 1 `bindPhase` \bombs ->
-          constructor (\_ -> makeStdoutLogger)
-            <&> advising
-              ( adviseRecord @Top @Top \method ->
-                  keepCallStack ioEx method <> injectFailures bombs
-              ),
+          constructor \_ ->
+            makeStdoutLogger
+              & advising
+                ( adviseRecord @Top @Top \method ->
+                    keepCallStack ioEx method <> injectFailures bombs
+                ),
+      logger2 =
+        allocateBombs 0 `bindPhase` \bombs ->
+          constructor \_ ->
+            tagged @"secondary" makeStdoutLogger
+              & advising
+                ( adviseRecord @Top @Top \method ->
+                    keepCallStack ioEx method <> injectFailures bombs
+                ),
       repository =
         allocateSet `bindPhase` \ref ->
-          constructor (makeInMemoryRepository ref)
-            <&> advising
-              ( adviseRecord @Top @Top \method ->
-                  keepCallStack ioEx method
-              ),
+          constructor \env ->
+            makeInMemoryRepository ref env
+              & advising
+                ( adviseRecord @Top @Top \method ->
+                    keepCallStack ioEx method
+                ),
       controller =
         skipPhase @Allocator $
-          constructor makeController
-            <&> advising
-              ( adviseRecord @Top @Top \method ->
-                  keepCallStack ioEx method
-              )
+          constructor \env ->
+            makeController env
+              & advising
+                ( adviseRecord @Top @Top \method ->
+                    keepCallStack ioEx method
+                )
     }
 
 -- Catch only IOExceptions for this example.
@@ -274,43 +314,105 @@   Env
     { logger =
         allocateBombs 1 `bindPhase` \bombs ->
-          Identity (A.component \_ -> makeStdoutLogger)
-            <&> ( A.adviseRecord @Top @Top \method ->
-                    A.fromSimple_ (keepCallStack ioEx method <> injectFailures bombs)
-                ),
+          Identity $ A.component \_ ->
+            makeStdoutLogger
+              & A.adviseRecord @Top @Top \method ->
+                A.fromSimple_ (keepCallStack ioEx method <> injectFailures bombs),
+      logger2 =
+        allocateBombs 0 `bindPhase` \bombs ->
+          Identity $ A.component \_ ->
+            tagged @"secondary" makeStdoutLogger
+              & A.adviseRecord @Top @Top \method ->
+                A.fromSimple_ (keepCallStack ioEx method <> injectFailures bombs),
       repository =
         allocateSet `bindPhase` \ref ->
-          Identity (A.component (makeInMemoryRepository ref))
-            <&> ( A.adviseRecord @Top @Top \method ->
-                    A.fromSimple_ (keepCallStack ioEx method)
-                ),
+          Identity $ A.component \env ->
+            makeInMemoryRepository ref env
+              & A.adviseRecord @Top @Top \method ->
+                A.fromSimple_ (keepCallStack ioEx method),
       controller =
         skipPhase @Allocator $
-          Identity (A.component makeController)
-            <&> ( A.adviseRecord @Top @Top \method ->
-                    A.fromSimple_ (keepCallStack ioEx method)
-                )
+          Identity $ A.component \env ->
+            makeController env
+              & A.adviseRecord @Top @Top \method ->
+                A.fromSimple_ (keepCallStack ioEx method)
     }
 
+
+-- THE COMPOSITION ROOT - YET ANOTER APPROACH
+--
+-- This approach also uses DepT, but not to carry the dependencies, only to carry 
+-- the call stack (like ReaderT in the first approach). 
+--
+-- This is done by parameterizing DepT with Const, which makes DepT
+-- behave almost as a regular ReaderT.
+-- 
+-- What are the benefits of unsing DepT instead of ReaderT here? Basically
+-- being able to use runFinalDepT in the test, which feels somewhat cleaner than
+-- runReaderT.
+type RT e m = DepT (Const e) m
+
+env'' :: Env (Phases (Env Identity (RT SyntheticCallStack IO))) (RT SyntheticCallStack IO)
+env'' = 
+  Env
+    { logger =
+        allocateBombs 1 `bindPhase` \bombs ->
+          constructor \_ ->
+            makeStdoutLogger
+              & A.adviseRecord @Top @Top \method ->
+                A.fromSimple_ (keepCallStack ioEx method <> injectFailures bombs),
+      logger2 =
+        allocateBombs 0 `bindPhase` \bombs ->
+           constructor \_ ->
+            tagged @"secondary" makeStdoutLogger
+              & A.adviseRecord @Top @Top \method ->
+                A.fromSimple_ (keepCallStack ioEx method <> injectFailures bombs),
+      repository =
+        allocateSet `bindPhase` \ref ->
+          constructor \env ->
+            makeInMemoryRepository ref env
+              & A.adviseRecord @Top @Top \method ->
+                A.fromSimple_ (keepCallStack ioEx method),
+      controller =
+        skipPhase @Allocator $
+          constructor \env ->
+            makeController env
+              & A.adviseRecord @Top @Top \method ->
+                A.fromSimple_ (keepCallStack ioEx method)
+    }
+
 -- TESTS
 --
 --
-expectedException :: (IOError, SyntheticCallStack)
+expectedException :: (IOError, SyntheticStackTrace)
 expectedException =
   ( userError "oops",
-    [ (typeRep (Proxy @Logger), "emitMsg"),
-      (typeRep (Proxy @Repository), "insert"),
-      (typeRep (Proxy @Controller), "route")
+    NonEmpty.fromList
+    [ NonEmpty.fromList [(typeRep (Proxy @Logger), "emitMsg")],
+      NonEmpty.fromList [(typeRep (Proxy @Repository), "insert")],
+      NonEmpty.fromList [(typeRep (Proxy @Controller), "route")]
     ]
   )
 
+expectedExceptionTagged :: (IOError, SyntheticStackTrace)
+expectedExceptionTagged =
+  ( userError "oops",
+    NonEmpty.fromList
+    [ NonEmpty.fromList [
+                (typeRep (Proxy @Logger), "emitMsg")
+        ,       (typeRep (Proxy @(Tagged "secondary" Logger)), "unTagged")
+        ],
+      NonEmpty.fromList [(typeRep (Proxy @Controller), "route")]
+    ]
+  )
+
 -- Test the "Constructor"-based version of the environment.
 testSyntheticCallStack :: Assertion
 testSyntheticCallStack = do
   let action =
         runContT (pullPhase @Allocator env) \constructors -> do
           -- here we complete the construction of the environment
-          let (asCall -> call) = fixEnv constructors 
+          let (asCall -> call) = fixEnv constructors
           flip
             runReaderT
             ([] :: SyntheticCallStack) -- the initial stack trace for the call
@@ -318,12 +420,43 @@                 _ <- call route 1 2
                 pure ()
             )
-  me <- try @SyntheticCallStackException action
+  me <- try @SyntheticStackTraceException action
   case me of
-    Left (SyntheticCallStackException (fromException @IOError -> Just ex) trace) ->
+    Left (SyntheticStackTraceException (fromException @IOError -> Just ex) trace) ->
       assertEqual "exception with callstack" expectedException (ex, trace)
     Right _ -> assertFailure "expected exception did not appear"
 
+-- Test the "Constructor"-based version of the environment.
+testSyntheticCallStackTagged :: Assertion
+testSyntheticCallStackTagged = do
+  let envz = env {
+          controller =
+            skipPhase @Allocator $
+              constructor \env ->
+                makeController2Loggers env
+                  & advising
+                    ( adviseRecord @Top @Top \method ->
+                        keepCallStack ioEx method
+                    )
+        }
+      action =
+        runContT (pullPhase @Allocator envz) \constructors -> do
+          -- here we complete the construction of the environment
+          let (asCall -> call) = fixEnv constructors
+          flip
+            runReaderT
+            ([] :: SyntheticCallStack) -- the initial stack trace for the call
+            ( do
+                _ <- call route 1 2
+                pure ()
+            )
+  me <- try @SyntheticStackTraceException action
+  case me of
+    Left (SyntheticStackTraceException (fromException @IOError -> Just ex) trace) ->
+      assertEqual "exception with callstack" expectedExceptionTagged (ex, trace)
+    Right _ -> assertFailure "expected exception did not appear"
+
+
 -- Test the "DepT"-based version of the environment.
 testSyntheticCallStack' :: Assertion
 testSyntheticCallStack' = do
@@ -331,18 +464,82 @@         runContT (pullPhase @Allocator env') \runnable -> do
           _ <- A.runFromDep (pure (CallEnv [] runnable)) route 1 2
           pure ()
-  me <- try @SyntheticCallStackException action
+  me <- try @SyntheticStackTraceException action
   case me of
-    Left (SyntheticCallStackException (fromException @IOError -> Just ex) trace) ->
+    Left (SyntheticStackTraceException (fromException @IOError -> Just ex) trace) ->
       assertEqual "exception with callstack" expectedException (ex, trace)
     Right _ -> assertFailure "expected exception did not appear"
 
+testSyntheticCallStackTagged' :: Assertion
+testSyntheticCallStackTagged' = do
+  let envz = env' {
+          controller =
+            skipPhase @Allocator $
+              Identity $ A.component \env ->
+                makeController2Loggers env
+                  & A.adviseRecord @Top @Top \method ->
+                    A.fromSimple_ (keepCallStack ioEx method)
+        }
+      action =
+        runContT (pullPhase @Allocator envz) \runnable -> do
+          _ <- A.runFromDep (pure (CallEnv [] runnable)) route 1 2
+          pure ()
+  me <- try @SyntheticStackTraceException action
+  case me of
+    Left (SyntheticStackTraceException (fromException @IOError -> Just ex) trace) ->
+      assertEqual "exception with callstack" expectedExceptionTagged (ex, trace)
+    Right _ -> assertFailure "expected exception did not appear"
+
+
+
+testSyntheticCallStack'' :: Assertion
+testSyntheticCallStack'' = do
+  let action =
+        runContT (pullPhase @Allocator env'') \constructors -> do
+          -- here we complete the construction of the environment
+          let (asCall -> call) = fixEnv constructors
+          A.runFinalDepT (pure (Const [])) (call route) 1 2
+          pure ()
+  me <- try @SyntheticStackTraceException action
+  case me of
+    Left (SyntheticStackTraceException (fromException @IOError -> Just ex) trace) ->
+      assertEqual "exception with callstack" expectedException (ex, trace)
+    Right _ -> assertFailure "expected exception did not appear"
+
+
+-- Test the "Constructor"-based version of the environment.
+testSyntheticCallStackTagged'' :: Assertion
+testSyntheticCallStackTagged'' = do
+  let envz = env'' {
+          controller =
+            skipPhase @Allocator $
+              constructor \env ->
+                makeController2Loggers env
+                  & A.adviseRecord @Top @Top \method ->
+                    A.fromSimple_ (keepCallStack ioEx method)
+        }
+      action =
+        runContT (pullPhase @Allocator envz) \constructors -> do
+          -- here we complete the construction of the environment
+          let (asCall -> call) = fixEnv constructors
+          A.runFinalDepT (pure (Const [])) (call route) 1 2
+          pure ()
+  me <- try @SyntheticStackTraceException action
+  case me of
+    Left (SyntheticStackTraceException (fromException @IOError -> Just ex) trace) ->
+      assertEqual "exception with callstack" expectedExceptionTagged (ex, trace)
+    Right _ -> assertFailure "expected exception did not appear"
+
 tests :: TestTree
 tests =
   testGroup
     "All"
     [ testCase "synthetic call stack" testSyntheticCallStack,
-      testCase "synthetic call stack - DepT" testSyntheticCallStack'
+      testCase "synthetic call stack with Tagged" testSyntheticCallStackTagged,
+      testCase "synthetic call stack - DepT" testSyntheticCallStack',
+      testCase "synthetic call stack with Tagged - DepT" testSyntheticCallStackTagged',
+      testCase "synthetic call stack - constructor + DepT" testSyntheticCallStack'',
+      testCase "synthetic call stack with Tagged - constructor + DepT" testSyntheticCallStackTagged''
     ]
 
 main :: IO ()