Consider the following functions :
sum: which takes a list of integers and returns their sum.
sumDifference: which takes two lists of integers and returns the difference of the their sums.
sumNonEmpty: which takes a list of integers and returns an
Option[Int], which is
Noneif the list is empty, and a
Somecontaining the sum of the members otherwise.
Suppose we want to generalize these functions to work on lists of some other type, such as
Doubles, pairs of integers i.e.
(Int, Int) with
(x1, x2) + (y1, y2) defined as
(x1 + y1, x2 + y2), or pairs of
Doubles. Take a moment and think about how you would do it.
All these types seem to naturally support the operations addition and subtraction, but this fact is not encoded in their inheritance hierarchy, which makes it extremely difficult to write generic functions that work for all of them.
To solve this problem, let’s define a trait called
Group is a generic trait, which takes a type parameter
T and declares the operations plus, minus and inverse for objects of type
T, as well as an element zero. Note that minus is defined using plus and inverse because
x - y = x + (-y)(how clever!).
Groups are a real thing, and the operation plus needs to be associative i.e.
(x + y) + z should be equal to
x + (y + z), otherwise we may get weird results.
Instances of the trait
Group should ideally be defined as implicit members of the object
Group, which lives is the same file as the trait. It is called the companion object of the trait. When the Scala compiler needs to supply an implicit parameter of type
Group[T], it looks inside the
Group companion object. So, we won’t need to pass around
Group instances explicitly while writing and using functions that depend on group operations.
Let’s define an instance of the trait for integers (inside the
Group object, of course):
The instance for
Double looks almost identical, except for the types :
Let’s try something a little more interesting. Here’s a method that produces a an instance of
Group for the pair
(T1, T2), if it can find instances of
This single method will automatically provide
Group instances for
(Double, Int) and
(Double, Double) whenever we need them. Isn’t that neat?
Alright, now that we’ve done all the hard work, let’s use the trait to make our
sum functions generic :
With these small modifications, you can now use these functions on lists of any type
T for which the compiler can find an implicit object of type
That’s it! That’s the type class pattern.
Group is called a type class (because it represents a class of types that support group operations), and the types
(Int, Int) etc. are called members of the type class
Group because the compiler can find instances of the type class for these types.
This post merely scratches the surface of the use cases and benefits of using type classes in Scala. Type classes are awesome and you should use them all over your code base. There are many great resources (much better than this one) for learning about type classes in Scala, like this, this and this. The only reason this post exists is so that I can point out the deficiencies in this naive implementation, and show you how to make it much better in my next post.