diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,5 @@
+# Revision history for moo-nad
+
+## 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/lib-example-impl/Moo.hs b/lib-example-impl/Moo.hs
new file mode 100644
--- /dev/null
+++ b/lib-example-impl/Moo.hs
@@ -0,0 +1,47 @@
+{-# LANGUAGE KindSignatures #-}
+{-# LANGUAGE MultiParamTypeClasses #-}
+{-# LANGUAGE FunctionalDependencies #-}
+{-# LANGUAGE NamedFieldPuns #-}
+{-# LANGUAGE TypeSynonymInstances #-}
+-- | This is the implementation module for the signature of the same name.
+module Moo (module Moo, runReaderT) where
+
+import Data.Kind
+import Control.Monad.Trans.Class (lift)
+import Control.Monad.Trans.Reader
+import Control.Monad.Dep.Has
+
+type M = ReaderT EnvIO IO
+type E = EnvIO
+type D = IO
+liftD :: D x -> M x
+liftD = lift
+
+-- | Same definition as the one in the module signature.
+class HasLogger d e | e -> d where
+    logger :: e -> Int -> String -> d ()
+
+-- | Same definition as the one in the module signature.
+data Counter d = Counter { 
+        askCounter :: d Int,
+        incCounter :: Int -> d ()
+    }
+
+-- | This is the point at which we define a concrete type for the environment.
+--
+-- The 'HasLogger' instance is backed by a bare function defined at the top
+-- level, while the 'Counter' component is a field by itself.
+data EnvIO = EnvIO {
+        _logger :: Int -> String -> IO (),
+        _counter :: Counter IO
+    }
+
+instance HasLogger D E where
+    logger (EnvIO {_logger}) = _logger
+
+-- | 'Control.Monad.Dep.Has' establishes a relationship between the environment 'E',
+-- the nominal record defining a component, and the effect monad the component
+-- uses in the environment..
+instance Has Counter D E where
+    dep (EnvIO {_counter}) = _counter
+
diff --git a/lib-example-logic-that-logs/LogicThatLogs.hs b/lib-example-logic-that-logs/LogicThatLogs.hs
new file mode 100644
--- /dev/null
+++ b/lib-example-logic-that-logs/LogicThatLogs.hs
@@ -0,0 +1,21 @@
+-- | Example of program logic in the ReaderT-record-of-functions style that
+-- abstracts away the concrete reader-like monad by depending on a module
+-- signature.
+module LogicThatLogs where
+
+-- The main Moo signature has been expanded through signature merging with a signature
+-- local to this library.
+import Moo 
+import Moo.Prelude
+
+-- | 'M' is a 'Monad'. Which one? We don't know yet, because this program logic
+-- lives in an indefinite package.
+logic :: M ()
+logic = do
+    -- Use 'self' for special-purpose HasX capabilities, like HasLogger.
+    self logger 7 "this is a message"
+    -- Use 'call' for record components located through "Control.Monad.Dep.Has.Has". 
+    c <- call askCounter
+    if c == 0 
+        then call incCounter 1
+        else pure ()
diff --git a/lib-example-logic-that-logs/Moo.hsig b/lib-example-logic-that-logs/Moo.hsig
new file mode 100644
--- /dev/null
+++ b/lib-example-logic-that-logs/Moo.hsig
@@ -0,0 +1,50 @@
+{-# LANGUAGE KindSignatures #-}
+{-# LANGUAGE MultiParamTypeClasses #-}
+{-# LANGUAGE FunctionalDependencies #-}
+-- | The logic in this sub-library needs to know how to perform logging,
+-- and use a counter
+-- 
+-- To allow for that, we expand the signature through signature merging, 
+-- defining a 'HasLogger' typeclass and demanding that the environment 'E'
+-- has an instance.
+--
+-- We also define a 'Counter' component (basically a record-of-functions polymorphic on 
+-- the base monad) and tie it to the environment 'E' using the generic Has class from 
+-- Control.Monad.Dep.Has.
+signature Moo where
+
+import Data.Kind
+import Control.Monad.Dep.Has
+
+data D :: Type -> Type  
+data E :: Type  
+instance HasLogger D E -- this wasn't in the base Moo signature
+instance Has Counter D E -- this wasn't, either
+
+-- A typeclass for extracting some capability from an environment.
+--
+-- Program logic invokes should invoke its method using "Moo.Prelude.self".
+--
+-- Typeclass definitions are allowed in signatures,
+-- methods and all. The corresponding definitions in the implementation
+-- modules must match exactly.
+--
+-- The typeclass could also have been imported from another package.
+class HasLogger d e | e -> d where
+    logger :: e -> Int -> String -> d ()
+
+-- A component is a record-of-functions parameterized by the effect monad 
+-- used by the functions.
+--
+-- Components are tied to the environment using the "Control.Monad.Dep.Has.Has" typeclass,
+-- and program logic should invoke their functions using "Moo.Prelude.call".
+--
+-- Datatype definitions are allowed in signatures. The corresponding definitions in
+-- the implementation modules must match exactly.
+--
+-- The datatype could also have been imported from another package.
+data Counter d = Counter { 
+        askCounter :: d Int,
+        incCounter :: Int -> d ()
+    }
+
diff --git a/lib/Moo.hsig b/lib/Moo.hsig
new file mode 100644
--- /dev/null
+++ b/lib/Moo.hsig
@@ -0,0 +1,34 @@
+{-# LANGUAGE KindSignatures, MultiParamTypeClasses #-}
+-- | A module signature for reader-like monads carrying a record-of-functions as
+-- environment.
+--
+-- To be useful for writing indefinite code, it should be expanded through "signature merging"
+-- to require extra instances for the main monad 'M' (like MonadIO or MonadUnliftIO) and/or
+-- the environment type 'E' (like some HasX typeclass).
+--
+-- https://github.com/danidiaz/really-small-backpack-example/tree/master/lesson3-signature-merging
+signature Moo where
+
+import Control.Monad
+import Control.Monad.Reader
+import Data.Kind
+
+-- | A reader-like monad.
+data M :: Type -> Type
+instance Functor M
+instance Applicative M
+instance Monad M
+instance MonadReader E M
+
+-- | The monad environment.
+data E :: Type
+
+-- | The monad in which the functions in the environment have their effects.
+--
+-- We don't require Functor, Applicative or Monad from 'D' because the 
+-- plan is to always lift 'D' values to 'M' as soon as they're obtained.
+data D :: Type -> Type
+
+-- | Lifts an effects from the secondary monad 'D' into 'M'.
+liftD :: D x -> M x
+
diff --git a/lib/Moo/Prelude.hs b/lib/Moo/Prelude.hs
new file mode 100644
--- /dev/null
+++ b/lib/Moo/Prelude.hs
@@ -0,0 +1,63 @@
+{-# LANGUAGE FlexibleInstances #-}
+{-# LANGUAGE MultiParamTypeClasses #-}
+{-# LANGUAGE TypeApplications #-}
+{-# LANGUAGE ScopedTypeVariables #-}
+{-# LANGUAGE FunctionalDependencies #-}
+{-# LANGUAGE TypeFamilies #-}
+{-# LANGUAGE StandaloneKindSignatures #-}
+{-# LANGUAGE DataKinds #-}
+{-# LANGUAGE TypeOperators #-}
+{-# LANGUAGE UndecidableInstances #-}
+{-# LANGUAGE FlexibleContexts #-}
+{-# LANGUAGE TypeApplications #-}
+{-# LANGUAGE ScopedTypeVariables #-}
+-- | Invocation helpers for functions with effects in a monad 'D' and which are
+-- stored in the environment 'E' of a reader-like monad 'M'.
+module Moo.Prelude (
+    self,
+    call,
+    -- * Re-exports from Moo
+    M,
+    E,
+    D 
+) where
+
+import Moo
+import Data.Kind
+import GHC.TypeLits
+import Control.Monad.Reader
+import Control.Monad.Dep.Has
+
+type Call :: Type -> Constraint
+class Call curried where
+    type LiftedD curried :: Type
+    -- | Given a way of extracting from the environment 'E' a @curried@
+    -- function that ends in a 'D' action, lift the @curried@ 'D'-function into the main
+    -- monad 'M'.
+    self :: (E -> curried) -> LiftedD curried
+
+instance Call (D r) where
+    type LiftedD (D r) = M r
+    self extractor = do
+        e <- ask
+        liftD $ extractor e
+
+instance Call curried' => Call (a -> curried') where
+    type LiftedD (a -> curried') = a -> LiftedD curried'
+    self extractor a = 
+        let extractor' = \e -> extractor e a
+        in self @curried' extractor'
+
+-- | Provided that the environment 'E' 'Control.Monad.Dep.Has' a @component@, and
+-- given a way of extracting from the @component@ a @curried@ function that
+-- ends in a 'D' action, lift the @curried@ 'D'-function into the main monad
+-- 'M'.
+--
+-- The extractor must be monomorphic on the @component@, so that the intended
+-- instance of 'Control.Monad.Dep.Has' is picked. 
+--
+-- The typical case is for the @component@ to be a parameterized record and for
+-- the extractor to be a field accessor.
+call :: forall component curried . (Has component D E, Call curried) => (component D -> curried) -> LiftedD curried
+call extractor = self (extractor . dep @component)
+
diff --git a/moo-nad.cabal b/moo-nad.cabal
new file mode 100644
--- /dev/null
+++ b/moo-nad.cabal
@@ -0,0 +1,82 @@
+cabal-version:      3.0
+name:               moo-nad
+version:            0.1.0.1
+
+synopsis:           Invocation helpers for the ReaderT-record-of-functions style.
+description:
+  Using a record-of-functions as the environment of some 
+  reader-like monad is a common way of structuring Haskell 
+  applications, somewhat resembling dependency injection in OOP.
+   
+  We often want our program logic to be polymorphic over both the
+  concrete monad and the environment. One common solution is to
+  abstract the monad using @MonadReader@, and abstract the environment
+  using @HasX@-style typeclasses.
+   
+  One minor annoyance though is that invoking the function in the 
+  environment is often a bit cumbersome: you have to ask
+  the environment for the function, and then lift the result of
+  the function back into the reader-like monad.
+    
+  This library supports a special twist on @ReaderT@-record-of-functions                     
+  style: instead of depending only on typeclasses for abstraction, 
+  we also use a module signature. This comes with different tradeoffs.
+   
+  One benefit is that we support a simpler way of invoking functions from the
+  environment, using a helper that takes care of both asking the environment
+  and lifting function results, and which works uniformly for functions of any
+  arity.
+
+license:            BSD-3-Clause
+license-file:       LICENSE
+author:             Daniel Diaz Carrete
+maintainer:         diaz_carrete@yahoo.com
+
+extra-source-files: CHANGELOG.md
+
+common common
+    build-depends:    base ^>= 4.15.0.0,
+                      mtl  ^>= 2.2,
+                      dep-t ^>= 0.4.4
+    default-language: Haskell2010
+
+-- Indefinite library which provides an invocation helper over some abstract
+-- readerlike monad which carries a record-of-functions.
+library
+    import:           common
+    signatures:       Moo
+    exposed-modules:  Moo.Prelude
+    build-depends:    
+    hs-source-dirs:   lib
+
+-- Example indefinite program logic.
+library example-logic-that-logs
+    import:           common
+    -- The Moo signature is enriched locally through signature merging.
+    signatures:       Moo
+    exposed-modules:  LogicThatLogs
+    build-depends:   
+                      moo-nad
+    hs-source-dirs:   lib-example-logic-that-logs
+
+-- Example library that implements the Moo signature.
+library example-impl
+    import:           common
+    exposed-modules:  Moo
+    -- Notice that we don't depend explicitly on the Moo signature.
+    build-depends:    
+                      transformers ^>= 0.5
+    hs-source-dirs:   lib-example-impl
+
+-- The tests put together the moo-nad library, the example indefinite package
+-- with the program logic, and the sublibrary carrying the implementation.
+test-suite tests
+    import:           common
+    type:             exitcode-stdio-1.0
+    hs-source-dirs:   test
+    main-is:          tests.hs
+    build-depends:    
+      tasty           ^>= 1.3.1,
+      tasty-hunit     ^>= 0.10.0.2,
+      example-logic-that-logs,
+      example-impl
diff --git a/test/tests.hs b/test/tests.hs
new file mode 100644
--- /dev/null
+++ b/test/tests.hs
@@ -0,0 +1,34 @@
+module Main where
+
+import Test.Tasty
+import Test.Tasty.HUnit
+import Data.IORef
+
+import LogicThatLogs
+import Moo
+
+tests :: TestTree
+tests =
+  testGroup
+    "All"
+    [ 
+        let test = do
+                loggerRef <- newIORef "" 
+                counterRef <- newIORef 0
+                let env = EnvIO {
+                              _logger = \_ -> writeIORef loggerRef
+                            , _counter = Counter {
+                                   askCounter = readIORef counterRef
+                                 , incCounter = \v -> modifyIORef counterRef (+v)
+                                 }
+                        }
+                runReaderT logic env
+                msg <- readIORef loggerRef
+                assertEqual "log output" "this is a message" msg
+                counterVal <- readIORef counterRef
+                assertEqual "counter output" 1 counterVal
+         in testCase "run logic" $ test
+    ]
+
+main :: IO ()
+main = defaultMain tests
