diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,5 @@
+# Revision history for dep-t
+
+## 0.1.0.0 -- YYYY-mm-dd
+
+* First version. Released on an unsuspecting world.
diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +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.
diff --git a/README.md b/README.md
new file mode 100644
--- /dev/null
+++ b/README.md
@@ -0,0 +1,194 @@
+# dep-t
+
+`DepT` is a
+[ReaderT](http://hackage.haskell.org/package/mtl-2.2.2/docs/Control-Monad-Reader.html)-like
+monad transformer for dependency injection.
+
+The difference with `ReaderT` is that `DepT` takes an enviroment whose type is
+parameterized by `DepT` itself.
+
+## Rationale
+
+To achieve dependency injection in Haskell, a common solution is to build a
+record of functions and pass it to the program logic some variant of
+[`ReaderT`](http://hackage.haskell.org/package/mtl-2.2.2/docs/Control-Monad-Reader.html).
+
+Let's start by defining some auxiliary typeclasses to extract functions from an
+environment record:
+
+    type HasLogger :: Type -> (Type -> Type) -> Constraint
+    class HasLogger r m | r -> m where
+      logger :: r -> String -> m ()
+
+    type HasRepository :: Type -> (Type -> Type) -> Constraint
+    class HasRepository r m | r -> m where
+      repository :: r -> Int -> m ()
+
+We see that the type of the record determines the monad in which the effects take place.
+
+Let's define a monomorphic record with effects in `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
+
+Record-of-functions-in-IO is a simple technique which works well in many
+situations. There are even [specialized
+libraries](http://hackage.haskell.org/package/rio) that support it.
+
+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 x = do
+      doLog <- asks logger
+      liftIO $ doLog "I'm going to insert in the db!"
+      insert <- asks repository
+      liftIO $ insert x
+      return $ x * x
+
+That's all and well, but there are two issues that bug me:
+
+- What if the repository function needs access to the logger, too? The
+  repository lives in the environment record, but isn't aware of it. That means
+  it can't use the `HasLogger` typeclass for easy and convenient dependency
+  injection. Why privilege the controller in such a way?
+
+  In a sufficiently complex app, the diverse functions that comprise it will be
+  organized in a big
+  [DAG](https://en.wikipedia.org/wiki/Directed_acyclic_graph) of dependencies.
+  And it would be nice if all the functions taking part in dependency injection
+  were treated uniformly; if all of them had access to (some view of) the
+  environment record.
+
+- We might want to write code that is innocent of `IO` and polymorphic over the
+  monad, to ensure that the program logic can't do some unexpected missile
+  launch, or to allow testing our app in a "pure" way. 
+
+Let's parameterize our environment by a monad: 
+
+    type Env :: (Type -> Type) -> Type
+    data Env m = Env
+      { _logger :: String -> m (),
+        _repository :: Int -> m (),
+        _controller :: Int -> m Int
+      }
+    -- helper from the "rank2classes" package
+    $(Rank2.TH.deriveFunctor ''Env)
+
+    instance HasLogger (Env m) m where
+      logger = _logger
+
+    instance HasRepository (Env m) m where
+      repository = _repository
+
+Notice that the controller function is now part of the environment. No
+favorites here!
+
+The following implementation of the logger function has no dependencies besides
+`MonadIO`:
+
+    mkStdoutLogger :: MonadIO m => String -> m ()
+    mkStdoutLogger msg = liftIO (putStrLn msg)
+
+But look at this implementation of the repository function. It gets hold of the
+logger through `HasLogger`:
+
+    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
+
+And here's the controller:
+
+    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
+
+Now, lets choose `IO` as the base monad and assemble an environment record:
+
+    envIO :: Env (DepT Env IO)
+    envIO =
+      let _logger = mkStdoutLogger
+          _repository = mkStdoutRepository
+          _controller = mkController
+       in Env {_logger,  _repository, _controller}
+
+Not very complicated, except... what is that weird `DepT Env IO` doing there in
+the signature? 
+
+Well, that's the whole reason this library exists. Trying to use a `ReaderT
+(Env something) IO` to parameterize `Env` won't fly; you'll get weird "infinite
+type" kind of errors because the `Env` needs to be parameterized with the monad
+that provides the `Env` environment. So I created the `DepT` newtype over
+`ReaderT` to mollify the compiler.
+
+## How to embed environments into other environments?
+
+Sometimes it might be convenient to nest an environment into another one,
+basically making it a field of the bigger environment:
+
+    type BiggerEnv :: (Type -> Type) -> Type
+    data BiggerEnv m = BiggerEnv
+      { _inner :: Env m,
+        _extra :: Int -> m Int
+      }
+    $(Rank2.TH.deriveFunctor ''BiggerEnv)
+
+When constructing the bigger environment, we have to tweak the monad parameter
+of the smaller one, to make the types match. This can be done with the
+`zoomEnv` function:
+
+    biggerEnvIO :: BiggerEnv (DepT BiggerEnv IO)
+    biggerEnvIO =
+      let _inner' = zoomEnv (Rank2.<$>) _inner envIO
+          _extra = pure
+       in BiggerEnv {_inner = _inner', _extra}
+
+We need to pass as the first parameter of `zoomEnv` a function that tweaks the
+monad parameter of `Env` using a natural transformation. We can write such a
+function ourselves, but here we are using the function generated for us by the
+[rank2classes
+TH](http://hackage.haskell.org/package/rank2classes-1.4.1/docs/Rank2-TH.html#v:deriveFunctor).
+
+## How to use "pure fakes" during testing?
+
+The [test suite](./test/tests.hs) has an example of using a `Writer` monad for
+collecting the outputs of functions working as ["test
+doubles"](https://martinfowler.com/bliki/TestDouble.html).
+
+## Caveats
+
+The structure of the `DepT` type might be prone to trigger a [known infelicity
+of the GHC
+simplifier](https://twitter.com/DiazCarrete/status/1350116413445439493).
+
+## Links
+
+- This library was extracted from my answer to [this Stack Overflow
+  question](https://stackoverflow.com/a/61782258/1364288).
+
+- The implementation of `mapDepT` was teased out in [this other SO question](https://stackoverflow.com/questions/65710657/writing-a-zooming-function-for-a-readert-like-monad-transformer).
+
+- I'm unsure of the relationship between `DepT` and the technique described in
+  [Adventures assembling records of
+  capabilities](https://discourse.haskell.org/t/adventures-assembling-records-of-capabilities/623). 
+
+  It seems that, with `DepT`, functions in the environment obtain their
+  dependencies anew every time they are invoked. If we change a function in the
+  environment record, all other functions which depend on it will be affected
+  in subsequent invocations. I don't think this happens with "Adventures..." at
+  least when changing an already "assembled" record.
+
diff --git a/Setup.hs b/Setup.hs
new file mode 100644
--- /dev/null
+++ b/Setup.hs
@@ -0,0 +1,2 @@
+import Distribution.Simple
+main = defaultMain
diff --git a/dep-t.cabal b/dep-t.cabal
new file mode 100644
--- /dev/null
+++ b/dep-t.cabal
@@ -0,0 +1,42 @@
+cabal-version:       3.0
+
+name:                dep-t
+version:             0.1.0.0
+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
new file mode 100644
--- /dev/null
+++ b/lib/Control/Monad/Dep.hs
@@ -0,0 +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, ir 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
new file mode 100644
--- /dev/null
+++ b/test/tests.hs
@@ -0,0 +1,189 @@
+{-# 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 ()
+
+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
