Tagless-final benefits

Borche Jadroski
3 min readNov 27, 2020

This blog post got inspired from a post I encountered at r/scala. Basically, the post author questions the benefits of writing Scala programs using what’s called tagless-final style. For those of you who haven’t encountered this term before, tagless-final is a programing technique that enables embedding domain specific languages (DSL’s) into a host language such as Scala for example.

Of course, the statement above is an over simplification of an entire concept, so if you’re interested to know more, there are countless blogs posts that deal with this subject, including the original papers published by Oleg Kiselyov.

To get back to the topic I wanted to discuss today, a typical method written in tagless-final style often looks like the following:

def myLogic[F[_] : Monad : MyStore : MyService](input: Input) : F[Output]

The post author finds the F[_] : Monad : MyStore : MyService portion of the method signature bearing too much technical information and obscuring the original business problem being solved in that method from whoever is reading the method signature.

So finally, the author asks, what are the benefits, if any?

The top rated answer (at the moment when I read this post on Reddit) took me by surprise, hence why I wanted to discuss its consequences in this blog post.

Basically the author of that answer argues that using tagless-final techniques enabled him to conclude that a given method (written in tagless-final style) cannot “do anything error-related”, based solely on reading the method signature and its constraints.

Further more, he/she states that this enabled him/her to write unit tests that dealt with business logic only, without having to deal with writing error handling tests.

For me this was a phenomenal example, but at first glance I couldn’t follow how can the method signature alone guarantee that nothing error related can be implemented programmatically?

So lets take a step back and take a look at the F[_] : Monad : MyStore : MyService construct, known in Scala as context bound . What it really states is that for a given higher kinded type F[_] (type that serves a a container for other types, for example List[Int], IO[Unit] etc), there have to be implicit instances of Monad[F], MyStore[F] and MyService[F] available in scope when the myLogic method runs.

Further more, this means that given that F[_] is an abstract type, all logic described inside myLogic has to be expressed in terms of Monad[F], MyStore[F] and MyService[F], and if those 3 instances don’t offer error throwing methods in their API, no error can be thrown as a consequence.

Neat, right?

Lets take a look at the simple program below:

As you can see from the program above, the myConstraintedCompare method introduces a A : Comparison context bound, meaning that all logic inside myConstraintedCompare will be described in terms of the Comparison trait, and in this very simple example, the only method you can programmatically call in Comparison is lesser, so your maneuvering space is limited in terms of what API’s are available to you, meaning the method definition follows the principle of least power to reach a given objective.

This in turn means that just by looking at the method signature and knowing the methods Comparison offers, the only thing that can be implemented in the myConstraintedCompare body is returning the lesser of two values of type A using the lesser method .

Hope you found this blog post informative, feel free to share your opinion on the matter.

Until next time.

--

--