packages feed

dep-t-advice-0.2.0.0: README.md

# dep-t-advice

This package is a companion to
[dep-t](http://hackage.haskell.org/package/dep-t). It provides a mechanism for
handling cross-cutting concerns in your application by adding "advices" to the
functions in your record-of-functions, in a way that is composable and
independent of each function's particular number of arguments.

## Rationale

So, you have decided to structure your program in a record-of-functions style,
using [dep-t](http://hackage.haskell.org/package/dep-t). Good choice!

You have already selected your functions, decided which base monad use for
`DepT`, and now you are ready to construct the environment record, which serves
as your [composition
root](https://stackoverflow.com/questions/6277771/what-is-a-composition-root-in-the-context-of-dependency-injection).

Now seems like a good moment to handle some of those pesky ["croscutting
concerns"](https://en.wikipedia.org/wiki/Cross-cutting_concern), don't you
think?

Stuff like:

- Logging
- Caching
- Monitoring
- Validation
- Setting up transaction boundaries.
- Setting up exception handlers for uncaught exceptions.

But how will you go about it?

### A perfectly simple and reasonable solution

Imagine that you want to make this function print its argument to stdout:

    foo :: Int -> DepT e IO () 

Easy enough:

    foo' :: Int -> DepT e IO ()
    foo' arg1 = do
        liftIO $ putStrLn (show arg1)
        foo arg1

You can even write your own general "printArgs" combinator:

    printArgs :: Show a => (a -> DepT e IO ()) -> (a -> DepT e IO ())
    printArgs f arg1 = do
        liftIO $ putStrLn (show arg1)
        f arg1

You could wrap `foo` in `printArgs` when constructing the record-of-functions,
or perhaps you could modify the corresponding field after the record had been
constructed.

This solution works, and is easy to understand. There's an annoyance though:
you need a different version of `printArgs` for each number of arguments a
function might have.

And if you want to compose different combinators (say, `printArgs` and
`printResult`) before applying them to functions, you need a composition
combinator specific for each number of arguments.

### The solution using "advices"

The `Advice` datatype provided by this package encapsulates a transformation on
`DepT`-effectful functions, *in a way that is polymorphic over the number of
arguments*. The same advice will work for functions with `0`, `1` or `N`
arguments.

Advices are parameterized by the constraints they require of the function:

- The function arguments. "All the arguments must be showable".
- The `DepT` environment and the base monad. "The environment must have a
  logger, and the base monad must have a `MonadIO` instance."
- The function return type. "The function must return a type that is a
  `Monoid`."

Here's how a `printArgs` advice might be defined:

    printArgs :: forall e m r. MonadIO m => Handle -> String -> Advice Show e m r
    printArgs h prefix =
      makeArgsAdvice
        ( \args -> do
            liftIO $ hPutStr h $ prefix ++ ":"
            hctraverse_ (Proxy @Show) (\(I a) -> liftIO (hPutStr h (" " ++ show a))) args
            liftIO $ hPutStrLn h "\n"
            liftIO $ hFlush h
            pure args
        )

The advice receives the arguments of the function in the form of an [n-ary
product](http://hackage.haskell.org/package/sop-core-0.5.0.1/docs/Data-SOP-NP.html#t:NP)
from [sop-core](http://hackage.haskell.org/package/sop-core-0.5.0.1). But it
must be polymorphic on the shape of the type-level list which indexes the
product. This makes the advice work for any number of parameters.

The advice would be applied like this:

    advise (printArgs stdout "foo args: ") foo

## Advices should be applied at the composition root

It's worth emphasizing that advices should be applied at the ["composition
root"](https://stackoverflow.com/questions/6277771/what-is-a-composition-root-in-the-context-of-dependency-injection),
the place in our application in which all the disparate functions are assembled
and we commit to a concrete monad, namely `DepT`.

Before being brought into the composition root, the functions need not be aware
that `DepT` exists. They might be working in some generic `MonadReader`
environment, plus some constraints on that environment.

Once we decide to use `DepT`, we can apply the advice, because advice only
works on functions that end on a `DepT` action. Also, advice might depend on
the full gamut of functionality stored in the environment.

## Links

- [Aspect Oriented Programming with
  Spring](https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#aop)
  and [Spring AOP
  APIs](https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#aop-api).

- [Using the “constraints” package to make a wrapped function less
  polymorphic](https://stackoverflow.com/questions/65800809/using-the-constraints-package-to-make-a-wrapped-function-less-polymorphic)

- [Dependency Injection Principles, Practices, and
  Patterns](https://www.goodreads.com/book/show/44416307-dependency-injection-principles-practices-and-patterns)
  This is a good book on the general princples of DI.