diff --git a/CHANGELOG.md b/CHANGELOG.md
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,9 +1,13 @@
-# Revision history for dep-t
-
-## 0.1.0.1 
-
-* Minor documentation changes.
-
-## 0.1.0.0 -- YYYY-mm-dd
-
-* First version. Released on an unsuspecting world.
+# Revision history for dep-t
+
+## 0.1.0.2 
+
+* Minor documentation changes.
+
+## 0.1.0.1 
+
+* Minor documentation changes.
+
+## 0.1.0.0 -- YYYY-mm-dd
+
+* First version. Released on an unsuspecting world.
diff --git a/LICENSE b/LICENSE
--- a/LICENSE
+++ b/LICENSE
@@ -1,30 +1,30 @@
-Copyright (c) 2021, Daniel Diaz
-
-All rights reserved.
-
-Redistribution and use in source and binary forms, with or without
-modification, are permitted provided that the following conditions are met:
-
-    * Redistributions of source code must retain the above copyright
-      notice, this list of conditions and the following disclaimer.
-
-    * Redistributions in binary form must reproduce the above
-      copyright notice, this list of conditions and the following
-      disclaimer in the documentation and/or other materials provided
-      with the distribution.
-
-    * Neither the name of Daniel Diaz nor the names of other
-      contributors may be used to endorse or promote products derived
-      from this software without specific prior written permission.
-
-THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
-A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
-OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
-SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
-LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
-DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
-THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
-(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
-OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+Copyright (c) 2021, Daniel Diaz
+
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+    * Redistributions of source code must retain the above copyright
+      notice, this list of conditions and the following disclaimer.
+
+    * Redistributions in binary form must reproduce the above
+      copyright notice, this list of conditions and the following
+      disclaimer in the documentation and/or other materials provided
+      with the distribution.
+
+    * Neither the name of Daniel Diaz nor the names of other
+      contributors may be used to endorse or promote products derived
+      from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/README.md b/README.md
--- a/README.md
+++ b/README.md
@@ -46,13 +46,12 @@
 
 Here's a function which obtains its dependencies from the environment record:
 
-    mkControllerIO :: (HasLogger e IO, HasRepository e IO) => Int -> ReaderT e IO Int
+    mkControllerIO :: (HasLogger e IO, HasRepository e IO) => Int -> ReaderT e IO String
     mkControllerIO x = do
-      doLog <- asks logger
-      liftIO $ doLog "I'm going to insert in the db!"
-      insert <- asks repository
-      liftIO $ insert x
-      return $ x * x
+      e <- ask
+      liftIO $ logger e "I'm going to insert in the db!"
+      liftIO $ repository e x
+      return "view"
 
 That's all and well, but there are two issues that bug me:
 
@@ -78,7 +77,7 @@
     data Env m = Env
       { _logger :: String -> m (),
         _repository :: Int -> m (),
-        _controller :: Int -> m Int
+        _controller :: Int -> m String
       }
     -- helper from the "rank2classes" package
     $(Rank2.TH.deriveFunctor ''Env)
@@ -103,19 +102,18 @@
 
     mkStdoutRepository :: (MonadReader e m, HasLogger e m, MonadIO m) => Int -> m ()
     mkStdoutRepository entity = do
-      doLog <- asks logger
-      doLog "I'm going to write the entity!"
+      e <- ask
+      logger e "I'm going to write the entity!"
       liftIO $ print entity
 
 And here's the controller:
 
-    mkController :: (MonadReader e m, HasLogger e m, HasRepository e m) => Int -> m Int
+    mkController :: (MonadReader e m, HasLogger e m, HasRepository e m) => Int -> m String
     mkController x = do
-      doLog <- asks logger
-      doLog "I'm going to insert in the db!"
-      insert <- asks repository
-      insert x
-      return $ x * x
+      e <- ask
+      logger e "I'm going to insert in the db!"
+      repository e x
+      return "view"
 
 Now, lets choose `IO` as the base monad and assemble an environment record:
 
@@ -169,15 +167,10 @@
 collecting the outputs of functions working as ["test
 doubles"](https://martinfowler.com/bliki/TestDouble.html).
 
-## Invoking the functions in the environment is cumbersome
-
-Yeah, it's annoying to perform the "ask for function, invoke function" dance each time:
-
-    mkController x = do
-      doLog <- asks logger
-      doLog "I'm going to insert in the db!"
+## How to avoid using "ask" or "asks" before invoking a dependency?
 
-One workaround (at the cost of more boilerplate) is to define helper functions like:  
+One possible workaround (at the cost of more boilerplate) is to define helper
+functions like:  
 
     logger' :: (MonadReader e m, HasLogger e m) => String -> m ()
     logger' msg = asks logger >>= \f -> f msg
@@ -186,6 +179,59 @@
 
     mkController x = do
       logger' "I'm going to insert in the db!"
+
+I'm not sure it's worth the hassle.
+
+## How to instrument functions in the environment?
+
+Once we have commited to a concrete monad and constructed our
+record-of-functions, we might indulge in a bit of low-calorie
+aspect-oriented-programming.
+
+For example, imagine we want a generic way of adding logging of function
+parameters to any function in the environment, provided the environment already
+contains a logging function.
+
+We can write the following typeclass:
+
+    class Instrumentable e m r | r -> e m where
+      instrument ::
+        ( forall x.
+          HasLogger (e (DepT e m)) (DepT e m) =>
+          [String] ->
+          DepT e m x ->
+          DepT e m x
+        ) ->
+        r ->
+        r
+
+Which means "if you tell me how to transform a terminal `DepT` action, using
+the list of preceding arguments, in an environment that has as logger, then
+I'll be able to transform any function which ends in `DepT`".
+
+The terminal case is a `DepT` without preceding parameters:
+
+    instance HasLogger (e (DepT e m)) (DepT e m) => Instrumentable e m (DepT e m x) where
+      instrument f d = f [] d
+
+The recursive case handles functions argument by argument:
+
+    instance (Instrumentable e m r, Show a) => Instrumentable e m (a -> r) where
+      instrument f ar =
+        let instrument' = instrument @e @m @r
+         in \a -> instrument' (\names d -> f (show a : names) d) (ar a)
+
+Here's how to add logging advice to the controller function:
+
+    instrumentedEnv :: Env (DepT Env (Writer TestTrace))
+    instrumentedEnv =
+       let loggingAdvice args action = do
+                e <- ask
+                logger e $ "advice before " ++ intercalate "," args
+                r <- action
+                logger e $ "advice after"
+                pure r
+        in env { _controller = instrument loggingAdvice (_controller env) }
 
 ## Caveats
 
diff --git a/Setup.hs b/Setup.hs
--- a/Setup.hs
+++ b/Setup.hs
@@ -1,2 +1,2 @@
-import Distribution.Simple
-main = defaultMain
+import Distribution.Simple
+main = defaultMain
diff --git a/dep-t.cabal b/dep-t.cabal
--- a/dep-t.cabal
+++ b/dep-t.cabal
@@ -1,42 +1,42 @@
-cabal-version:       3.0
-
-name:                dep-t
-version:             0.1.0.1
-synopsis:            Reader-like monad transformer for dependency injection.
-description:         Put all your functions in the environment record! Let all
-                     your functions read from the environment record! No favorites!
--- bug-reports:
-license:             BSD-3-Clause
-license-file:        LICENSE
-author:              Daniel Diaz
-maintainer:          diaz_carrete@yahoo.com
-category:            Control
-extra-source-files:  CHANGELOG.md, README.md
-
-source-repository    head
-  type:     git
-  location: https://github.com/danidiaz/dep-t.git
-
-common common
-  build-depends:       base >=4.10.0.0 && < 5,
-                       transformers ^>= 0.5.0.0,
-                       mtl ^>= 2.2,
-                       unliftio-core ^>= 0.2.0.0,
-  default-language:    Haskell2010
-
-library
-  import: common
-  exposed-modules:     Control.Monad.Dep
-  hs-source-dirs:      lib 
-
-test-suite dep-t-test
-  import: common
-  type:                exitcode-stdio-1.0
-  hs-source-dirs:      test
-  main-is:             tests.hs
-  build-depends:       
-    dep-t, 
-    rank2classes       ^>= 1.4.1,
-    template-haskell,
-    tasty              >= 1.3.1,
-    tasty-hunit        >= 0.10.0.2,
+cabal-version:       3.0
+
+name:                dep-t
+version:             0.1.0.2
+synopsis:            Reader-like monad transformer for dependency injection.
+description:         Put all your functions in the environment record! Let all
+                     your functions read from the environment record! No favorites!
+-- bug-reports:
+license:             BSD-3-Clause
+license-file:        LICENSE
+author:              Daniel Diaz
+maintainer:          diaz_carrete@yahoo.com
+category:            Control
+extra-source-files:  CHANGELOG.md, README.md
+
+source-repository    head
+  type:     git
+  location: https://github.com/danidiaz/dep-t.git
+
+common common
+  build-depends:       base >=4.10.0.0 && < 5,
+                       transformers ^>= 0.5.0.0,
+                       mtl ^>= 2.2,
+                       unliftio-core ^>= 0.2.0.0,
+  default-language:    Haskell2010
+
+library
+  import: common
+  exposed-modules:     Control.Monad.Dep
+  hs-source-dirs:      lib 
+
+test-suite dep-t-test
+  import: common
+  type:                exitcode-stdio-1.0
+  hs-source-dirs:      test
+  main-is:             tests.hs
+  build-depends:       
+    dep-t, 
+    rank2classes       ^>= 1.4.1,
+    template-haskell,
+    tasty              >= 1.3.1,
+    tasty-hunit        >= 0.10.0.2,
diff --git a/lib/Control/Monad/Dep.hs b/lib/Control/Monad/Dep.hs
--- a/lib/Control/Monad/Dep.hs
+++ b/lib/Control/Monad/Dep.hs
@@ -1,146 +1,146 @@
-{-# LANGUAGE DeriveFunctor #-}
-{-# LANGUAGE FlexibleInstances #-}
-{-# LANGUAGE GeneralizedNewtypeDeriving #-}
-{-# LANGUAGE ImportQualifiedPost #-}
-{-# LANGUAGE MultiParamTypeClasses #-}
-{-# LANGUAGE RankNTypes #-}
-{-# LANGUAGE ScopedTypeVariables #-}
-{-# LANGUAGE StandaloneDeriving #-}
-{-# LANGUAGE StandaloneKindSignatures #-}
-{-# LANGUAGE UndecidableInstances #-}
-
--- |
---    This package provides 'DepT', a monad transformer similar to 'ReaderT'.
---
---    The difference is that the environment of 'DepT' must be parameterized by
---    @DepT@'s own monad stack.
---
---    There's a function 'withDepT' which is analogous to 'withReaderT'.
---
---    There's no analogue of 'mapReaderT' however. This means you can't tweak
---    the monad below the 'DepT' with a natural transformation.
-module Control.Monad.Dep
-  ( DepT (DepT),
-    runDepT,
-    toReaderT,
-    withDepT,
-    zoomEnv,
-  )
-where
-
-import Control.Applicative
-import Control.Monad.Cont.Class
-import Control.Monad.Error.Class
-import Control.Monad.IO.Unlift
-import Control.Monad.Reader
-import Control.Monad.State.Class
-import Control.Monad.Trans.Class
-import Control.Monad.Trans.Identity
-import Control.Monad.Writer.Class
-import Control.Monad.Zip
-import Data.Kind (Type)
-
--- |
---    A monad transformer which adds a read-only environment to the given monad.
---    The environment type must be parameterized with the transformer's stack.
---
---    The 'return' function ignores the environment, while @>>=@ passes the
---    inherited environment to both subcomputations.
-type DepT ::
-  ((Type -> Type) -> Type) ->
-  (Type -> Type) ->
-  Type ->
-  Type
-newtype DepT env m r = DepT {toReaderT :: ReaderT (env (DepT env m)) m r}
-  deriving
-    ( Functor,
-      Applicative,
-      Alternative,
-      Monad,
-      MonadFix,
-      MonadFail,
-      MonadZip,
-      MonadPlus,
-      MonadCont,
-      MonadIO,
-      MonadUnliftIO,
-      MonadReader (env (DepT env m))
-    )
-
-instance MonadTrans (DepT env) where
-  lift = DepT . lift
-
-deriving instance MonadState s m => MonadState s (DepT env m)
-
-deriving instance MonadWriter w m => MonadWriter w (DepT env m)
-
-deriving instance MonadError e m => MonadError e (DepT env m)
-
--- |
---    Runs a 'DepT' action in an environment.
-runDepT :: DepT env m r -> env (DepT env m) -> m r
-runDepT = runReaderT . toReaderT
-
--- |
---    Changes the environment of a 'DepT', for example making the 'DepT' work in
---    a "bigger" environment than the one in which was defined initially.
---
---    The scary first parameter is a function that, given a natural
---    transformation of monads, changes the monad parameter of the environment
---    record. This function can be defined manually for each environment record,
---    or it can be generated using TH from the "rank2classes" package.
-withDepT ::
-  forall small big m a.
-  Monad m =>
-  -- | rank-2 map function
-  ( forall p q.
-    (forall x. p x -> q x) ->
-    small p ->
-    small q
-  ) ->
-  -- | get a small environment from a big one
-  (forall t. big t -> small t) ->
-  DepT small m a ->
-  DepT big m a
-withDepT mapEnv inner (DepT (ReaderT f)) =
-  DepT
-    ( ReaderT
-        ( \big ->
-            let small :: small (DepT small m)
-                -- we have a big environment at hand, so let's extract the
-                -- small environment, transform every function in the small
-                -- environment by supplying the big environment and, as a
-                -- finishing touch, lift from the base monad m so that it
-                -- matches the monad expected by f.
-                small = mapEnv (lift . flip runDepT big) (inner big)
-             in f small
-        )
-    )
-
--- |
---    Makes the functions inside a small environment require a bigger environment.
---
---    This can be useful if we are encasing the small environment as a field of
---    the big environment, in order to make the types match.
---
---    The scary first parameter is a function that, given a natural
---    transformation of monads, changes the monad parameter of the environment
---    record. This function can be defined manually for each environment record,
---    or it can be generated using TH from the "rank2classes" package.
-
--- For the reason for not inlining, see https://twitter.com/DiazCarrete/status/1350116413445439493
-{-# NOINLINE zoomEnv #-}
-zoomEnv ::
-  forall small big m a.
-  Monad m =>
-  -- | rank-2 map function
-  ( forall p q.
-    (forall x. p x -> q x) ->
-    small p ->
-    small q
-  ) ->
-  -- | get a small environment from a big one
-  (forall t. big t -> small t) ->
-  small (DepT small m) ->
-  small (DepT big m)
-zoomEnv mapEnv inner = mapEnv (withDepT mapEnv inner)
+{-# LANGUAGE DeriveFunctor #-}
+{-# LANGUAGE FlexibleInstances #-}
+{-# LANGUAGE GeneralizedNewtypeDeriving #-}
+{-# LANGUAGE ImportQualifiedPost #-}
+{-# LANGUAGE MultiParamTypeClasses #-}
+{-# LANGUAGE RankNTypes #-}
+{-# LANGUAGE ScopedTypeVariables #-}
+{-# LANGUAGE StandaloneDeriving #-}
+{-# LANGUAGE StandaloneKindSignatures #-}
+{-# LANGUAGE UndecidableInstances #-}
+
+-- |
+--    This package provides 'DepT', a monad transformer similar to 'ReaderT'.
+--
+--    The difference is that the environment of 'DepT' must be parameterized by
+--    @DepT@'s own monad stack.
+--
+--    There's a function 'withDepT' which is analogous to 'withReaderT'.
+--
+--    There's no analogue of 'mapReaderT' however. This means you can't tweak
+--    the monad below the 'DepT' with a natural transformation.
+module Control.Monad.Dep
+  ( DepT (DepT),
+    runDepT,
+    toReaderT,
+    withDepT,
+    zoomEnv,
+  )
+where
+
+import Control.Applicative
+import Control.Monad.Cont.Class
+import Control.Monad.Error.Class
+import Control.Monad.IO.Unlift
+import Control.Monad.Reader
+import Control.Monad.State.Class
+import Control.Monad.Trans.Class
+import Control.Monad.Trans.Identity
+import Control.Monad.Writer.Class
+import Control.Monad.Zip
+import Data.Kind (Type)
+
+-- |
+--    A monad transformer which adds a read-only environment to the given monad.
+--    The environment type must be parameterized with the transformer's stack.
+--
+--    The 'return' function ignores the environment, while @>>=@ passes the
+--    inherited environment to both subcomputations.
+type DepT ::
+  ((Type -> Type) -> Type) ->
+  (Type -> Type) ->
+  Type ->
+  Type
+newtype DepT env m r = DepT {toReaderT :: ReaderT (env (DepT env m)) m r}
+  deriving
+    ( Functor,
+      Applicative,
+      Alternative,
+      Monad,
+      MonadFix,
+      MonadFail,
+      MonadZip,
+      MonadPlus,
+      MonadCont,
+      MonadIO,
+      MonadUnliftIO,
+      MonadReader (env (DepT env m))
+    )
+
+instance MonadTrans (DepT env) where
+  lift = DepT . lift
+
+deriving instance MonadState s m => MonadState s (DepT env m)
+
+deriving instance MonadWriter w m => MonadWriter w (DepT env m)
+
+deriving instance MonadError e m => MonadError e (DepT env m)
+
+-- |
+--    Runs a 'DepT' action in an environment.
+runDepT :: DepT env m r -> env (DepT env m) -> m r
+runDepT = runReaderT . toReaderT
+
+-- |
+--    Changes the environment of a 'DepT', for example making the 'DepT' work in
+--    a "bigger" environment than the one in which was defined initially.
+--
+--    The scary first parameter is a function that, given a natural
+--    transformation of monads, changes the monad parameter of the environment
+--    record. This function can be defined manually for each environment record,
+--    or it can be generated using TH from the "rank2classes" package.
+withDepT ::
+  forall small big m a.
+  Monad m =>
+  -- | rank-2 map function
+  ( forall p q.
+    (forall x. p x -> q x) ->
+    small p ->
+    small q
+  ) ->
+  -- | get a small environment from a big one
+  (forall t. big t -> small t) ->
+  DepT small m a ->
+  DepT big m a
+withDepT mapEnv inner (DepT (ReaderT f)) =
+  DepT
+    ( ReaderT
+        ( \big ->
+            let small :: small (DepT small m)
+                -- we have a big environment at hand, so let's extract the
+                -- small environment, transform every function in the small
+                -- environment by supplying the big environment and, as a
+                -- finishing touch, lift from the base monad m so that it
+                -- matches the monad expected by f.
+                small = mapEnv (lift . flip runDepT big) (inner big)
+             in f small
+        )
+    )
+
+-- |
+--    Makes the functions inside a small environment require a bigger environment.
+--
+--    This can be useful if we are encasing the small environment as a field of
+--    the big environment, in order to make the types match.
+--
+--    The scary first parameter is a function that, given a natural
+--    transformation of monads, changes the monad parameter of the environment
+--    record. This function can be defined manually for each environment record,
+--    or it can be generated using TH from the "rank2classes" package.
+
+-- For the reason for not inlining, see https://twitter.com/DiazCarrete/status/1350116413445439493
+{-# NOINLINE zoomEnv #-}
+zoomEnv ::
+  forall small big m a.
+  Monad m =>
+  -- | rank-2 map function
+  ( forall p q.
+    (forall x. p x -> q x) ->
+    small p ->
+    small q
+  ) ->
+  -- | get a small environment from a big one
+  (forall t. big t -> small t) ->
+  small (DepT small m) ->
+  small (DepT big m)
+zoomEnv mapEnv inner = mapEnv (withDepT mapEnv inner)
diff --git a/test/tests.hs b/test/tests.hs
--- a/test/tests.hs
+++ b/test/tests.hs
@@ -1,192 +1,240 @@
-{-# LANGUAGE FlexibleContexts #-}
-{-# LANGUAGE FlexibleInstances #-}
-{-# LANGUAGE FunctionalDependencies #-}
-{-# LANGUAGE ImportQualifiedPost #-}
-{-# LANGUAGE MultiParamTypeClasses #-}
-{-# LANGUAGE NamedFieldPuns #-}
-{-# LANGUAGE RankNTypes #-}
-{-# LANGUAGE StandaloneKindSignatures #-}
-{-# LANGUAGE TemplateHaskell #-}
-
-module Main (main) where
-
-import Control.Monad.Dep
-import Control.Monad.Reader
-import Control.Monad.Writer
-import Data.Kind
-import Rank2 qualified
-import Rank2.TH qualified
-import Test.Tasty
-import Test.Tasty.HUnit
-import Prelude hiding (log)
-
--- Some helper typeclasses.
---
--- Has-style typeclasses can be provided to avoid depending on concrete
--- environments.
--- Note that the environment determines the monad.
-type HasLogger :: Type -> (Type -> Type) -> Constraint
-class HasLogger r m | r -> m where
-  logger :: r -> String -> m ()
-
-logger' :: (MonadReader e m, HasLogger e m) => String -> m ()
-logger' msg = asks logger >>= \f -> f msg
-
-type HasRepository :: Type -> (Type -> Type) -> Constraint
-class HasRepository r m | r -> m where
-  repository :: r -> Int -> m ()
-
--- Some possible implementations.
---
--- An implementation of the controller, done programming against interfaces
--- (well, against typeclasses).
--- Polymorphic on the monad.
-mkController :: (MonadReader e m, HasLogger e m, HasRepository e m) => Int -> m Int
-mkController x = do
-  doLog <- asks logger
-  doLog "I'm going to insert in the db!"
-  insert <- asks repository
-  insert x
-  return $ x * x
-
--- A "real" logger implementation that interacts with the external world.
-mkStdoutLogger :: MonadIO m => String -> m ()
-mkStdoutLogger msg = liftIO (putStrLn msg)
-
--- A "real" repository implementation 
-mkStdoutRepository :: (MonadReader e m, HasLogger e m, MonadIO m) => Int -> m ()
-mkStdoutRepository entity = do
-  doLog <- asks logger
-  doLog "I'm going to write the entity!"
-  liftIO $ print entity
-
--- The traces we accumulate from the fakes during tests
-type TestTrace = ([String], [Int])
-
--- A "fake". A pure implementation for tests.
-mkFakeLogger :: MonadWriter TestTrace m => String -> m ()
-mkFakeLogger msg = tell ([msg], [])
-
--- Ditto.
-mkFakeRepository :: (MonadReader e m, HasLogger e m, MonadWriter TestTrace m) => Int -> m ()
-mkFakeRepository entity = do
-  doLog <- asks logger
-  doLog "I'm going to write the entity!"
-  tell ([], [entity])
-
-
---
---
--- Here we define a monomorphic environment working on IO
-type EnvIO :: Type
-data EnvIO = EnvIO
-  { _loggerIO :: String -> IO (),
-    _repositoryIO :: Int -> IO ()
-  }
-
-instance HasLogger EnvIO IO where
-  logger = _loggerIO
-
-instance HasRepository EnvIO IO where
-  repository = _repositoryIO
-
--- In the monomorphic environment, the controller function lives "separate",
--- having access to the logger and the repository through the ReaderT
--- environment.
---
--- The question is: the repository function *also* needs to know about the
--- logger!  Shouldn't it be aware of the ReaderT environment as well? Why
--- privilege the controller function in such a manner?
---
--- In a sufficiently complex app, the diverse functions will form a DAG of
--- dependencies between each other. So it would be nice if the functions were
--- treated uniformly, all having access to (views of) the environment record.
-mkControllerIO :: (HasLogger e IO, HasRepository e IO) => Int -> ReaderT e IO Int
-mkControllerIO x = do
-  doLog <- asks logger
-  liftIO $ doLog "I'm going to insert in the db!"
-  insert <- asks repository
-  liftIO $ insert x
-  return $ x * x
-
---
---
--- Here we define some polymorphic environments, which are basically
--- records-of-functions parameterized by an effect monad.
-type Env :: (Type -> Type) -> Type
-data Env m = Env
-  { _logger :: String -> m (),
-    _repository :: Int -> m (),
-    _controller :: Int -> m Int
-  }
-$(Rank2.TH.deriveFunctor ''Env)
-
--- If our environment is parmeterized by the monad m, then logging is done in
--- m.
-instance HasLogger (Env m) m where
-  logger = _logger
-
-instance HasRepository (Env m) m where
-  repository = _repository
-
--- This bigger environment is for demonstrating how to "nest" environments.
-type BiggerEnv :: (Type -> Type) -> Type
-data BiggerEnv m = BiggerEnv
-  { _inner :: Env m,
-    _extra :: Int -> m Int
-  }
-$(Rank2.TH.deriveFunctor ''BiggerEnv)
-
---
---
--- Creating environment values and commiting to a concrete monad.
---
--- This is the first time DepT is used in this module.
--- Note that it is only here where we settle for a concrete monad for the
--- polymorphic environments.
-env :: Env (DepT Env (Writer TestTrace))
-env =
-  let _logger = mkFakeLogger
-      _repository = mkFakeRepository
-      _controller = mkController
-   in Env {_logger,  _repository, _controller}
-
--- An IO variant
-envIO :: Env (DepT Env IO)
-envIO =
-  let _logger = mkStdoutLogger
-      _repository = mkStdoutRepository
-      _controller = mkController
-   in Env {_logger,  _repository, _controller}
-
-biggerEnv :: BiggerEnv (DepT BiggerEnv (Writer TestTrace))
-biggerEnv =
-  let -- We embed the small environment into the bigger one using "zoomEnv"
-      -- and the rank-2 fmap that allows us to change the monad which
-      -- parameterized the environment.
-      --
-      -- _inner' = (Rank2.<$>) (withDepT (Rank2.<$>) inner) env,
-      _inner' = zoomEnv (Rank2.<$>) _inner env
-      _extra = pure
-   in BiggerEnv {_inner = _inner', _extra}
-
-biggerEnvIO :: BiggerEnv (DepT BiggerEnv IO)
-biggerEnvIO =
-  let _inner' = zoomEnv (Rank2.<$>) _inner envIO
-      _extra = pure
-   in BiggerEnv {_inner = _inner', _extra}
-
-expected :: TestTrace
-expected = (["I'm going to insert in the db!", "I'm going to write the entity!"], [7])
-
-tests :: TestTree
-tests =
-  testGroup
-    "All"
-    [ testCase "hopeThisWorks" $
-        assertEqual "" expected $
-          execWriter $ runDepT ((_controller . _inner $ biggerEnv) 7) biggerEnv
-    ]
-
-main :: IO ()
-main = defaultMain tests
+{-# LANGUAGE FlexibleContexts #-}
+{-# LANGUAGE FlexibleInstances #-}
+{-# LANGUAGE FunctionalDependencies #-}
+{-# LANGUAGE ImportQualifiedPost #-}
+{-# LANGUAGE MultiParamTypeClasses #-}
+{-# LANGUAGE NamedFieldPuns #-}
+{-# LANGUAGE RankNTypes #-}
+{-# LANGUAGE ScopedTypeVariables #-}
+{-# LANGUAGE StandaloneKindSignatures #-}
+{-# LANGUAGE TemplateHaskell #-}
+{-# LANGUAGE TypeApplications #-}
+{-# LANGUAGE UndecidableInstances #-}
+
+module Main (main) where
+
+import Control.Monad.Dep
+import Control.Monad.Reader
+import Control.Monad.Writer
+import Data.Kind
+import Rank2 qualified
+import Rank2.TH qualified
+import Test.Tasty
+import Test.Tasty.HUnit
+import Prelude hiding (log)
+import Data.List (intercalate)
+
+-- Some helper typeclasses.
+--
+-- Has-style typeclasses can be provided to avoid depending on concrete
+-- environments.
+-- Note that the environment determines the monad.
+type HasLogger :: Type -> (Type -> Type) -> Constraint
+class HasLogger r m | r -> m where
+  logger :: r -> String -> m ()
+
+-- Possible convenience function to avoid having to use ask before logging
+-- Worth the extra boilerplate, or not?
+logger' :: (MonadReader e m, HasLogger e m) => String -> m ()
+logger' msg = asks logger >>= \f -> f msg
+
+type HasRepository :: Type -> (Type -> Type) -> Constraint
+class HasRepository r m | r -> m where
+  repository :: r -> Int -> m ()
+
+-- Some possible implementations.
+--
+-- An implementation of the controller, done programming against interfaces
+-- (well, against typeclasses).
+-- Polymorphic on the monad.
+mkController :: (MonadReader e m, HasLogger e m, HasRepository e m) => Int -> m String
+mkController x = do
+  e <- ask
+  logger e "I'm going to insert in the db!"
+  repository e x
+  return "view"
+
+-- A "real" logger implementation that interacts with the external world.
+mkStdoutLogger :: MonadIO m => String -> m ()
+mkStdoutLogger msg = liftIO (putStrLn msg)
+
+-- A "real" repository implementation
+mkStdoutRepository :: (MonadReader e m, HasLogger e m, MonadIO m) => Int -> m ()
+mkStdoutRepository entity = do
+  e <- ask
+  logger e "I'm going to write the entity!"
+  liftIO $ print entity
+
+-- The traces we accumulate from the fakes during tests
+type TestTrace = ([String], [Int])
+
+-- A "fake". A pure implementation for tests.
+mkFakeLogger :: MonadWriter TestTrace m => String -> m ()
+mkFakeLogger msg = tell ([msg], [])
+
+-- Ditto.
+mkFakeRepository :: (MonadReader e m, HasLogger e m, MonadWriter TestTrace m) => Int -> m ()
+mkFakeRepository entity = do
+  e <- ask
+  logger e "I'm going to write the entity!"
+  tell ([], [entity])
+
+--
+--
+-- Here we define a monomorphic environment working on IO
+type EnvIO :: Type
+data EnvIO = EnvIO
+  { _loggerIO :: String -> IO (),
+    _repositoryIO :: Int -> IO ()
+  }
+
+instance HasLogger EnvIO IO where
+  logger = _loggerIO
+
+instance HasRepository EnvIO IO where
+  repository = _repositoryIO
+
+-- In the monomorphic environment, the controller function lives "separate",
+-- having access to the logger and the repository through the ReaderT
+-- environment.
+--
+-- The question is: the repository function *also* needs to know about the
+-- logger!  Shouldn't it be aware of the ReaderT environment as well? Why
+-- privilege the controller function in such a manner?
+--
+-- In a sufficiently complex app, the diverse functions will form a DAG of
+-- dependencies between each other. So it would be nice if the functions were
+-- treated uniformly, all having access to (views of) the environment record.
+mkControllerIO :: (HasLogger e IO, HasRepository e IO) => Int -> ReaderT e IO String
+mkControllerIO x = do
+  e <- ask
+  liftIO $ logger e "I'm going to insert in the db!"
+  liftIO $ repository e x
+  return "view"
+
+--
+--
+-- Here we define some polymorphic environments, which are basically
+-- records-of-functions parameterized by an effect monad.
+type Env :: (Type -> Type) -> Type
+data Env m = Env
+  { _logger :: String -> m (),
+    _repository :: Int -> m (),
+    _controller :: Int -> m String
+  }
+
+$(Rank2.TH.deriveFunctor ''Env)
+
+-- If our environment is parmeterized by the monad m, then logging is done in
+-- m.
+instance HasLogger (Env m) m where
+  logger = _logger
+
+instance HasRepository (Env m) m where
+  repository = _repository
+
+-- This bigger environment is for demonstrating how to "nest" environments.
+type BiggerEnv :: (Type -> Type) -> Type
+data BiggerEnv m = BiggerEnv
+  { _inner :: Env m,
+    _extra :: Int -> m Int
+  }
+
+$(Rank2.TH.deriveFunctor ''BiggerEnv)
+
+--
+--
+-- Creating environment values and commiting to a concrete monad.
+--
+-- This is the first time DepT is used in this module.
+-- Note that it is only here where we settle for a concrete monad for the
+-- polymorphic environments.
+env :: Env (DepT Env (Writer TestTrace))
+env =
+  let _logger = mkFakeLogger
+      _repository = mkFakeRepository
+      _controller = mkController
+   in Env {_logger, _repository, _controller}
+
+-- An IO variant
+envIO :: Env (DepT Env IO)
+envIO =
+  let _logger = mkStdoutLogger
+      _repository = mkStdoutRepository
+      _controller = mkController
+   in Env {_logger, _repository, _controller}
+
+biggerEnv :: BiggerEnv (DepT BiggerEnv (Writer TestTrace))
+biggerEnv =
+  let -- We embed the small environment into the bigger one using "zoomEnv"
+      -- and the rank-2 fmap that allows us to change the monad which
+      -- parameterized the environment.
+      --
+      -- _inner' = (Rank2.<$>) (withDepT (Rank2.<$>) inner) env,
+      _inner' = zoomEnv (Rank2.<$>) _inner env
+      _extra = pure
+   in BiggerEnv {_inner = _inner', _extra}
+
+biggerEnvIO :: BiggerEnv (DepT BiggerEnv IO)
+biggerEnvIO =
+  let _inner' = zoomEnv (Rank2.<$>) _inner envIO
+      _extra = pure
+   in BiggerEnv {_inner = _inner', _extra}
+
+expected :: TestTrace
+expected = (["I'm going to insert in the db!", "I'm going to write the entity!"], [7])
+
+--
+--
+-- Experiment about adding instrumetation
+class Instrumentable e m r | r -> e m where
+  instrument ::
+    ( forall x.
+      HasLogger (e (DepT e m)) (DepT e m) =>
+      [String] ->
+      DepT e m x ->
+      DepT e m x
+    ) ->
+    r ->
+    r
+
+instance HasLogger (e (DepT e m)) (DepT e m) => Instrumentable e m (DepT e m x) where
+  instrument f d = f [] d
+
+instance (Instrumentable e m r, Show a) => Instrumentable e m (a -> r) where
+  instrument f ar =
+    let instrument' = instrument @e @m @r
+     in \a -> instrument' (\names d -> f (show a : names) d) (ar a)
+
+instrumentedEnv :: Env (DepT Env (Writer TestTrace))
+instrumentedEnv =
+   let loggingAdvice args action = do
+            e <- ask
+            logger e $ "advice before: " ++ intercalate "," args
+            r <- action
+            logger e $ "advice after"
+            pure r
+    in env { _controller = instrument loggingAdvice (_controller env) }
+
+expectedInstrumented :: TestTrace
+expectedInstrumented = (["advice before: 7","I'm going to insert in the db!","I'm going to write the entity!","advice after"], [7])
+--
+--
+--
+
+tests :: TestTree
+tests =
+  testGroup
+    "All"
+    [ 
+      testCase "hopeThisWorks" $
+        assertEqual "" expected $
+          execWriter $ runDepT ((_controller . _inner $ biggerEnv) 7) biggerEnv,
+      testCase "hopeAOPWorks" $
+        assertEqual "" expectedInstrumented $
+          execWriter $ runDepT ((_controller $ instrumentedEnv) 7) instrumentedEnv
+    ]
+
+
+main :: IO ()
+main = defaultMain tests
