I\'ve been reading up on type classes in Scala and thought I had a good grasp on it, until I remembered Java\'s java.util.Comparator
.
If I understand proper
The term type class comes from Haskell were they are part of the language. In scala, it is not, it is more of a pattern, which happens to have a lot of language support in scala (implicits, mostly). The pattern makes sense even without this syntactic support, for instance in java, and I would say that Comparator
is a typical example of that pattern there (although the term type class is not used in java).
From an object oriented perspective, the pattern consist in having Comparator
rather than Comparable
. The most basic object thinking would have the comparison service in the object, say class String implements Comparable<String>
. However, extracting it has numerous advantages:
These two reasons are enough to have Comparable
in java, and to use them in in sorted collections (e.g TreeSet
) Comparable
is kept, as it gives a convenient default (no need to pass a Comparator when you want the "default" comparison, and it is easier to call (x.compareTo(y) rather than comparator.compare(x,y)). In scala, with implicits, none of this reason is compelling (interoperability with java would still be a reason to implement Ordered/Comparable in scala).
There are other, less obvious advantages to type classes. Among them :
sum(list)
. It requires that there is some sort of addition available on the elements of the list. This might be available in the element themselves. Say they could be some Addable[T]
with def add(other: T): T
. But if you pass the empty list to sum, it should return the "zero" of the type of the type of the list (0 for ints, the empty string for strings...). Having a def zero: T
in Addable[T]
would be useless, as at that moment, you have no Addable
around. But this works fine with a type class such as Numeric
or Monoid
. Int
and String
to have an ordering defined on the pair (Int, String)
, or given an Ordering
on T
, build an ordering on List[T]
. Scala does that with implicits, but it still makes sense in java, explicitly. A more sophisticated example:
// Comparison by the first comparator which finds the two elements different.
public static Comparator<T> lexicographic<T>(final Comparator<T>... comparators) {
return new Comparator<T>() {
public int compare(T t1, T t2) {
for(comparator : comparators) {
int result = comparator.compare(t1, t2);
if (result != 0) return result;
}
return 0;
}
}
}
(might be simpler in scala, but again, this is of interest in java)
There are some small disadvantages too (much more so in java than in scala, but still)
if(x is Comparable<?>) {do some sorting}
,
this would not be possible with a Comparator.No java.util.Comparator is an interface
public interface Comparator<T>
A comparison function, which imposes a total ordering on some collection of objects. Comparators can be passed to a sort method (such as Collections.sort or Arrays.sort) to allow precise control over the sort order. Comparators can also be used to control the order of certain data structures (such as sorted sets or sorted maps), or to provide an ordering for collections of objects that don't have a natural ordering.
I prefer not to talk specifically about type classes but about the type class pattern in Scala; the reason is that when you start asking "what is the type class", you end up concluding that it is just an interface used in a particular way.
(In Haskell it makes more sense to call a specific construct a type class.)
The type class pattern consists of three essential parts (but there are usually a couple more for convenience). The first is an interface parameterized by a single type that abstracts some sort of capability on the parameterized type. java.util.Comparator
is a perfect example: it provides an interface for comparison. Let's just use that.
The second thing you need is a method that makes use of that parameterization, which you can specify with short-hand notation in Scala:
// Short signature
// v------------------- "We must be able to find a Comparator for A"
def ordered[A: java.util.Comparator](a0: A, a1: A, a2: A) = {
val cmp = implicitly[java.util.Comparator[A]] // This is the Comparator
cmp.compare(a0, a1) <= 0 && cmp.compare(a1, a2) <= 0
}
// Long signature version
def ordered[A](a0: A, a1: A, a2: A)(implicit cmp: java.util.Comparator[A]) = {
cmp.compare(a0, a1) <= 0 && cmp.compare(a1, a2) <= 0
}
Okay, but where do you get that comparator from? That's the third necessary piece. By default, Scala doesn't give you Comparator
s for the classes you might like, but you can define your own:
implicit object IntComp extends java.util.Comparator[Int] {
def compare(a: Int, b: Int) = a.compareTo(b)
}
scala> ordered(1,2,3)
res5: Boolean = true
scala> ordered(1,3,2)
res6: Boolean = false
Now that you've provided the functionality for Int
(implicitly), the compiler will fill in the implicit parameter to ordered
to make it work. If you haven't yet provided the functionality, it gives an error:
scala> ordered("fish","wish","dish")
<console>:12: error: could not find implicit value
for parameter cmp: java.util.Comparator[String]
ordered("fish","wish","dish")
until you supply that functionality:
implicit object StringComp extends java.util.Comparator[String] {
def compare(a: String, b: String) = a.compareTo(b)
}
scala> ordered("fish","wish","dish")
res11: Boolean = false
So, do we call java.util.Comparator
a type class? It certainly functions just as well as a Scala trait that handles the equivalent part of the type class pattern. So even though the type class pattern doesn't work as well in Java (since you have to explicitly specify the instance to use instead of having it implicitly looked up), from a Scala perspective java.util.Comparator
is as much a type class as anything.