Retroactive Polymorphism with Scala Typeclasses :: Jan 31, 2017
As an object-functional language, Scala supports many ways of writing generic, polymorphic code. This article will introduce retroactive polymorphism, and examine the advanced language features enabling it in Scala.
Suppose we are writing a function
sum that combines the elements of a sequence. The definition is simple with a numeric type like
sum is generic, however, the elements’ capability to combine must be explicitly stated. One approach is declaring an interface
Or in Java:
Semigroup can now be passed to
But it’s impossible to retroactivly declare types we don’t control (Like
Semigroups. Scala offers two workarounds: the Adapter Pattern and Type Classes.
Lets first consider Adapters. Popularized by the Gang of Four and used in Java, we define generic wrappers for every type needed:
But this requires users to laboriously create wrapper objects, which is both wordy and unperformant:
Typeclasses circumvent the Adapter Pattern’s issues, at the potential cost of greater complexity. Instead of extending type
T with an interface to declare some behavior
B, a typeclass implementation is an external object able to perform
T. Beginning with the simplest of typeclasses, this post and its follow-ups should progress to the advanced definitions seen in the wild.
As implementations of the typeclass
Semigroup[A] will be themselves combining elements of type
A, a second parameter
b is added to
sum can now be redefined as:
And used after implementing
There are still many issues with our typeclass, namely:
Semigroupinstances must be manually passed around
- It would be preferable in many cases to use infix notation
Implicit parameters solve the former. If a second
implicit parameter list is added to
sum, the compiler will attempt to find an implicitly declared instance, first using static scoping rules, and finally by checking the typeclass’s static fields. Redeclaring
Or alternatively using Context Bound syntax,
sum can now be called without passing
Implicits also permit a form of ad-hoc polymorphism when used with typeclasses; should a library designer declare common instances in the typeclass’s companion object, users are free to pass or implicitly declare alternate definitions.
To scrap some boilerplate, we define static
Semigroup.apply[A] to return the implicitly required
Although I find the context-bound
Semigroup.apply more appealing, the first version should be used because it can be inlined if desired.
Next up is infix syntax. Scala has implicit classes which allow just that:
Regular implicit classes create a lot of wrapper objects, so
SemigroupOps should be restructured as a Value Class, which doesn’t allocate any runtime objects when used correctly.
Typeclasses are a powerful means of achieving retroactive polymorphism. In future posts, I hope to cover the different styles used by open source libraries like spire and cats, with usability and performance considerations.
The final code can be found here
It should also be mentioned that while Structural Types can be used in similar ways, the feature relies on runtime reflection and is discouraged from use.