packages feed

dep-t-0.4.0.2: README.md

# 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 perform dependency injection in Haskell, a common solution is to build a
record of functions and pass it to the program logic using some variant of
[`ReaderT`](http://hackage.haskell.org/package/mtl-2.2.2/docs/Control-Monad-Reader.html).

To avoid becoming tied to a concrete reader environment, let's define some
auxiliary typeclasses that extract functions from a generic environment:

    type HasLogger :: (Type -> Type) -> Type -> Constraint
    class HasLogger d e | e -> d where
      logger :: e -> String -> d ()

    type HasRepository :: (Type -> Type) -> Type -> Constraint
    class HasRepository d e | e -> d where
      repository :: e -> Int -> d ()

We see that the type `e` of the environment determines the monad `d` on which
the effects take place.

Here's a monomorphic environment record with functions that have effects in `IO`:

    type EnvIO :: Type
    data EnvIO = EnvIO
      { _loggerIO :: String -> IO (),
        _repositoryIO :: Int -> IO ()
      }

    instance HasLogger IO EnvIO where
      logger = _loggerIO

    instance HasRepository IO EnvIO 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 can get its dependencies from the monomorphic
environment:

    mkControllerIO :: (HasLogger IO e, HasRepository IO e) => 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"

That's all and well, but there are two issues that bug me:

- 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. 

- 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.

To tackle these issues, we begin by giving the controller a more general signature:

    mkControllerIO :: (HasLogger IO e, HasRepository IO e, MonadIO m, MonadReader e m) => Int -> m String

Now the function can work in other reader-like monads besides `ReaderT`.

Let's go one step further, and abstract away the `IO`, so that functions in the
record can have effects in other monads:

    mkController :: (HasLogger d e, HasRepository d e, LiftDep d m, MonadReader e m) => Int -> m String
    mkController x = do
      e <- ask
      liftD $ logger e "I'm going to insert in the db!"
      liftD $ repository e x
      return "view"

Now both the signature and the implementation have changed:

- There's a new type variable `d`, the monad in which functions taken from the
  environment `e` have their effects.

- `MonadIO` has been replaced by `LiftDep` from `Control.Monad.Dep.Class`, a
  constraint that says we can lift `d` effects into `m` (though it could still
  make sense to require `MonadIO m` for effects not originating in the
  environment).

- Uses of `liftIO` have been replaced by `liftD`.

If all those constraints prove annoying to write, there's a convenient shorthand using the `MonadDep` type family:

    mkController :: MonadDep [HasLogger, HasRepository] d e m => Int -> m String

The new, more polymorphic `mkController` function can replace the original `mkControllerIO`:

    mkControllerIO' :: (HasLogger IO e, HasRepository IO e) => Int -> ReaderT e IO String
    mkControllerIO' = mkController

Now let's focus on the environment record. We'll parameterize its type by a
monad: 

    type Env :: (Type -> Type) -> Type
    data Env m = Env
      { _logger :: String -> m (),
        _repository :: Int -> m (),
        _controller :: Int -> m String
      }

    instance HasLogger m (Env m) where
      logger = _logger

    instance HasRepository m (Env 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`, just as the controller did:

    mkStdoutRepository :: (MonadDep '[HasLogger] d e m, MonadIO m) => Int -> m ()
    mkStdoutRepository entity = do
      e <- ask
      liftD $ logger e "I'm going to write the entity!"
      liftIO $ print entity

It's about time we choose a concrete 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. For dependency injection to
work for *all* functions, `Env` needs to be parameterized with a monad that
provides that same `Env` environment. And trying to use a `ReaderT (Env
something) IO` to parameterize `Env` won't fly; you'll get weird "infinite
type" kind of errors. So I created the `DepT` newtype over `ReaderT` to mollify
the compiler.

`DepT` has `MonadReader` and `LiftDep` instances, so the effects of
`mkController` can take place on it.

## So how do we invoke the controller now?

I suggest something like

    runDepT (do e <- ask; _controller e 7) envIO 

or 

    (do e <- ask; _controller e 7) `runDepT` envIO 

The companion package
[dep-t-advice](http://hackage.haskell.org/package/dep-t-advice) has some more
functions for running `DepT` computations.

## How to avoid using "ask" and "liftD" before invoking a dependency?

One possible workaround (at the cost of more boilerplate) is to define helper
functions like:  

    loggerD :: MonadDep '[HasLogger] d e m => String -> m ()
    loggerD msg = asks logger >>= \f -> liftD $ f msg

Which you can invoke like this:

    usesLoggerD :: MonadDep [HasLogger, HasRepository] d e m => Int -> m String
    usesLoggerD i = do
      loggerD "I'm calling the logger!"
      return "foo"

Though perhaps this isn't worth the hassle.

## 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).

## How to make a function "see" a different evironment from the one seen by its dependencies?

Sometimes we want a function in the environment to see a slightly different
record from the record seen by the other functions, and in particular from the
record seen by its own dependencies. 

For example, the function might have a `HasLogger` constraint but we don't want
it to use the default `HasLogger` instance of the environment.

The companion package
[dep-t-advice](http://hackage.haskell.org/package/dep-t-advice) provides a
`deceive` function that allows for this.

## How to add AOP-ish "aspects" to functions in an environment?

The companion package
[dep-t-advice](http://hackage.haskell.org/package/dep-t-advice) provides a
general method of extending the behaviour of `DepT`-effectful functions, in a
way reminiscent of aspect-oriented programming.

## 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).

- An [SO
  answer](https://stackoverflow.com/questions/57703898/how-to-call-impure-functions-from-pure-ones/57714058#57714058)
  about records-of-functions and the "veil of polymorphism".

- The answers to [this SO
  question](https://stackoverflow.com/questions/61642492/simplifying-the-invocation-of-functions-stored-inside-an-readert-environment)
  gave me the idea for how to "instrument" monadic functions (although the
  original motive of the question was different).

- 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)
  which relies on having "open" and "closed" versions of the environment
  record. 

  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 a "closed", already assembled record.

  With `DepT` a function might use `local` if it knows enough about the
  environment. That doesn't seem very useful for program logic; if fact it
  sounds like a recipe for confusion. It could perhaps be useful for [AOP-ish
  things](http://hackage.haskell.org/package/dep-t-advice), to keep a synthetic
  "call stack", or to implement something like Logback's [Mapped Diagnostic
  Context](http://logback.qos.ch/manual/mdc.html).

- [RIO](http://hackage.haskell.org/package/rio) is a featureful ReaderT-like /
  prelude replacement library which favors monomorphic environments.

- Another exploration of dependency injection with `ReaderT`:
  [ReaderT-OpenProduct-Environment](https://github.com/keksnicoh/ReaderT-OpenProduct-Environment).

- The [van Laarhoven Free Monad](http://r6.ca/blog/20140210T181244Z.html).

> Swierstra notes that by summing together functors representing primitive I/O
> actions and taking the free monad of that sum, we can produce values use
> multiple I/O feature sets. Values defined on a subset of features can be
> lifted into the free monad generated by the sum. The equivalent process can
> be performed with the van Laarhoven free monad by taking the product of
> records of the primitive operations. Values defined on a subset of features
> can be lifted by composing the van Laarhoven free monad with suitable
> projection functions that pick out the requisite primitive operations. 

- [registry](http://hackage.haskell.org/package/registry) is a package that
  implements an alternative approach to dependency injection, one different
  from the `ReaderT`-based one.