Understanding Scala's Type Classes
by Brendan McAdams
Over the last year or so, I have found myself making more and more use of Scala’s Type Class system to add flexibility to my code. This is especially evident in the MongoDB Scala Driver, http://github.com/mongodb/casbah, where the most recent work has been to simplify many features by migrating them to type classes.
During this work however, I’ve found during that many otherwise adroit Scala engineers seem befuddled or daunted by the Type Class. It does me no good to take advantage of clever features that my users don’t understand, and many will benefit from introducing these concepts to their own code. So let’s take a look at what type classes are, as well as how & why we can utilize them.
Wikipedia defines a Type Class as “… a type system construct that supports ad-hoc polymorphism. This is achieved by adding constraints to type variables in parametrically polymorphic types”. Admittedly, a bit of a mouthful – and not very helpful to those of us who are self taught and lack the benefit of a comprehensive academic Computer Science education (myself included). Surely, there must be a way to simplify this concept?
In evaluating these ideas, I’ve found it easiest to think of a Type Class (in Scala, at least) as a special kind of adapter, which can impart additional capabilities upon a given type or set of types. In Scala the Type Class is communicated through implicits, and imparts one, or both, of two behaviors. First, a Type Class can be to utilized to filter what types are valid for a given method call (which I detailed in this earlier post). Second, a Type Class can impart additional features and behaviors upon a type at method invocation time. This latter is much along the lines of an enhanced kind of composition, rather than the weaker inheritance which often plagues similar behaviors in a language like Java.
To better understand what I am describing, let’s compare a few concepts around the creation and interaction of custom domain objects. I have several sets of tasks I have had to accomplish in Scala in the past – and Scala solutions show some elegant Type Class oriented approaches which are rooted in the Standard Library. While this may seem a bit contrived, it is exactly the kind of problem through which I initially came to understand Type Classes –– and is thus an ideal lesson.
First, let’s take a look at sorting and custom objects to best understand how one accomplishes this. It is not an uncommon task in development for us to create our own objects and need to integrate them into Standard Library behaviors, such as sorting. Let’s work with a few sample objects in the form of “Bank Accounts” to look at how this all work (and I’m aware of the poor concurrency control, etc. around balance – this is a contrived example). Here’s our Bank Account object:
We can easily populate collections with instances of these accounts as well.
Given collections of instances of these bank accounts in each language, we’d like to easily sort them –– given an arbitrary set of requirements. Now, neither Scala or Java can “automatically” figure out how to sort these, instead requiring assistance from us (the developer).
It is unfortunately not quite that easy, as the above code will fail to compile asking for a missing argument:
Like in Java, we need to provide Scala information about how to sort a class of type
BankAccount. In Java however, we would need to use inheritance and actually change the structure of
BankAccount by implementing the
Comparable interface. Personally, I’ve never been a fan of that approach – changing a class directly can lead to behavioral oddities. It also has two major limitations that I’ve run into in the past.
First we get locked into only one way to sort a
BankAccount. If initially we want to sort by
accountNumber, and code that in we are restricted should another part of our application need to sort by
balance. We either work around the builtin sort methods or subclass, introducing more complications.
Second, we are severely restricted in our ability to handle this with a third party class. What if
BankAccount is a vendor supplied class and is
final so we cannot even create an extended version which implements comparable? Suddenly we are restricted from taking advantage of the sort routines built into the standard library and have to reinvent our own. Not ideal.
Instead, with Scala, the implementation of our
Comparable equivalent is done externally in a Type Class of type
scala.math.Ordering. When implemented, our instance of
Ordering will both control what classes can be sorted as well as providing information about how to sort. But because it is implemented externally and provided as an implicit we can provide multiple versions should we need different sorting behaviors down the line*.
It is important to note that a Type Class in Scala is typically stateless. It is provided to callers as a single static instance based on Type, and only infers necessary state information from instances of the referenced type passed to its methods. The Type Class is controlling how instances of a given type should behave generically and should be side effect free.
The normal way of providing a typeclass is to create a static implicit object of a trait implementation of
TypeClass[A] in scope. I prefer to declare the base trait separately from the implicit object to encourage easier reusage.
For providing Ordering[BankAccount], we need to implement an abstract method
def compare(x: T, y: T): Int which compares two instances of
T (Where, in this case,
BankAccount) and returns an
Int signifying their order against one another. Negative represents that
x < y, positive that
x > y and zero if
x == y.
Let’s take a look at how our
Ordering instance for sorting a
accountNumber might look.
Now with an implicit instance of
Ordering[BankAccount] in scope, our sort can succeed. Running our code should produce expected results:
The big benefit here (in my eyes) is that we didn’t need to modify our
BankAccount class at all to provide this behavior. Even if
BankAccount was a sealed third party class we can provide sorting information for it. This is far superior to an inheritance based solution such as Java’s. And if we wanted later to sort by
balance instead of
accountNumber we can explicitly pass a different instance to sort:
Complete control is passed to us from an externally controlled system. I’ll save the details for a future post, but we can even use a type class to define what it means if I say
brendansAccount - johnsAccount using an instance of
Now go forth and Type with Class.