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