The tagless-final pattern lets us write pure functional programs which are explicit about the effects they require.
However, scaling this pattern might become challenging. I'll try to demonstrate this with an example. Imagine a simple program that reads records from the database and prints them to the console. We will require some custom typeclasses Database
and Console
, in addition to Monad
from cats/scalaz in order to compose them:
def main[F[_]: Monad: Console: Database]: F[Unit] =
read[F].flatMap(Console[F].print)
def read[F[_]: Functor: Database]: F[List[String]] =
Database[F].read.map(_.map(recordToString))
The problem starts when I want to add a new a effect to a function in the inner layers. For example, I want my read
function to log a message if no records were found
def read[F[_]: Monad: Database: Logger]: F[List[String]] =
Database[F].read.flatMap {
case Nil => Logger[F].log("no records found") *> Nil.pure
case records => records.map(recordToString).pure
}
But now, I have to add the Logger
constraint to all the callers of read
up the chain. In this contrived example it's just main
, but imagine this is several layers down a complicated real-world application.
We can look at this issue in two ways:
- We can say it's a good thing that were explicit about our effects, and we know exactly which effects are needed by each layer
- We can also say that this leaks implementation details -
main
doesn't care about logging, it's just needs the result ofread
. Also, in real applications you see really long chains of effects in the top layers. It feels like a code-smell, but I can't put my finger on what other approach I can take.
Would love to get your insights on this.
Thanks.
read
up the chain": what you're calling layers here form a tree, and while it's true that near the base of the tree (yourmain
), you're stuck enumerating all the effects needed by all dependents, these trees a) don't tend to be terribly deep in practice and b) the implementations near the base should be fairly minimal, just composing pieces with fewer requirements and more logic. – Travis Brown