Monthly Archives: January 2020

Ad-hoc Polymorphism In Scala

Over the past few years, there seems to be a subtle trend of software engineers favoring typeclass patterns that implement polymorphism in an ad-hoc fashion, namely, Ad-hoc Polymorphism. To see the benefits of such kind of polymorphism, let’s first look at what F-bounded polymorphism, a subtype polymorphism, has to offer.

F-bounded polymorphism

Next, a couple of child classes are defined:

A F-bounded type has a peculiar signature of the self-recursive A[T <: A[T]] which mandates the given type T itself a sub-type of A[T], like how type Sedan is defined (Sedan <: Car[Sedan]). Note that the self-type annotation used in the trait isn’t requirement for F-bounded type. Rather, it’s a common practice for safeguarding against undesirable mix-up of sub-classes like below:

“Type argument” versus “Type member”

Rather than a type argument, a F-bounded type could also be expressed as a type member which needs to be defined in its child classes.:

It should be noted that with the type member approach, self-type would not be applicable, hence mix-up of sub-classes mentioned above is possible.

Let’s define a sedan and test out method setPrice:

Under the F-bounded type’s “contract”, a method such as the following would work as intended to return the specified sub-type:

Had the Car/Sedan hierarchy been set up as the less specific T <: Car, the corresponding method:

would fail as it couldn’t guarantee the returning type is the exact type of the input.

F-bounded type collection

Next, let’s look at a collection of cars.

The resulting type is a rather ugly sequence of gibberish. To help the compiler a little, give it some hints about T <: Car[T] as shown below:

Ad-hoc polymorphism

Contrary to subtype polymorphism which orients around a supertype with a rigid subtype structure, let’s explore a different approach using typeclasses, known as Ad-hoc polymorphism.

Next, a couple of “ad-hoc” implicit objects are created to implement the trait methods.

Note that alternatively, the implicit objects could be set up as ordinary companion objects of the case classes with implicit anonymous classes:

Unifying implemented methods

Finally, an implicit conversion for cars of type T is provided by means of an implicit class to create a “unified” method that takes the corresponding method implementations from the provided implicit Car[T] parameter.

Testing it out:

New methods, like setSalePrice, can be added as needed in the implicit objects:

Ad-hoc type collection

Next, a collection of cars:

Similar to the F-bounded collection, the inferred resulting type isn’t very helpful. Unlike in the F-bounded case, we do not have a T <: Car[T] contract. Using an approach illustrated in this blog post, we could assemble the collection as a list of (car, type) tuples:

By means of a simple example, we’ve now got a sense of how Ad-hoc polymorphism works. The F-bounded example serves as a contrasting reference of how the polymorphism bound by a more “strict” contract plays out in comparison. Given the flexibility of not having to bind the base classes into a stringent subtype relationship upfront, the rising popularity of Ad-hoc polymorphism certainly has its merits.

That said, lots of class models in real-world applications still fits perfectly well into a subtype relationship. In suitable use cases, F-bounded polymorphism generally imposes less boilerplate code. In addition, Ad-hoc polymorphism typically involves using of implicits that may impact code maintainability.