By definition type classes are a type system construct that supports ad hoc polymorphism.
Let us elaborate this definition by explaining following aspects of type classes:
- Externalized not implemented inside the class in question
- Classes which we do not control
- Possibility to have multiple implementations
- Lawfullness
An excellent example for showcasing the aforementioned aspects and the differences between the type class approach opposed to the usual inheritance approach are the java interfaces Comparable
and Comparator
.
External vs internal implementation
In order to use inheritance approach the class which instances we want compare has to extend Comparable
and add an implementation for the compareTo
method. Opposite to that if we take the type class approach we will implement a Comparator
instance.
Classes which we do not control
It's not possible to use the inheritance approach in cases in which we do not have control over the class we want to compare. E.g. the class is pulled from an external dependency.
There are way around that, e.g. defining a wrapper class which can sometimes be inconvenient. The type class approach does not have that restriction. We are free to implement Comparator
instance for every class we can our hands on.
Single vs multiple implementations
Using Comparable
restricts us to a single Comparable implementation. There are ways around that too, e.g using subclasses and define different implementation for each subclass. Depending on the language we use we can define one or more type class implementations. While scala allows multiple type class implementations, expected they are not defined at the same resolution level. You can read more about it here. Haskell usually does not allow multiple type class instances. Although there are ways to avoid this restriction.
Lawfullness
Every type class comes with a set of laws which every instance needs to abide to. In case of the Comparator
type class we have e.g.:
if x.equals(y) then x.compareTo(y) == 0
if x.compareTo(y) == 0 then y.compareTo(x) == 0
if x.compareTo(y) < 0 and y.compareTo(z) < 0
then x.comapreTo(z) < 0
In general this is all there is to type classes. While this is just an example there is a plethora of type classes most of which come from category theory. But you definitely don't need to go into that topic to make use of them. In scala type classes are implemented using traits which are quite close to java interfaces.
When it comes to scalas implementation of type classes the part that confuses people the most seems not to be the actual concept of type classes but the scala implicits magic behind the scenes. The magic is required because, different from haskell which defines type classes as first-class structure
In scala they are not part of the language core, instead they are implemented using other language features such as implicit parameters and implicit conversions. Implicits can sometimes be confusing especially for newcomers, but don't let yourself get discouraged.
As a result the notion of comparing instance of a certain class is externalized. Method definitions using the inheritance approach would look this this:
// java
public <A extends Comparable<A>> ResultType methodWhichNeedsToCompareInstances(A a)
// scala
def methodWhichNeedsToCompareInstances[A <: Comparable[A]](a: A): ResultType
While using the type class approach they would look like this:
// java
public <A> ResultType methodWhichNeedsToCompareInstances(A a)(Comparator<A> comparator)
// scala
def methodWhichNeedsToCompareInstances[A](a: A)
(implicit comparator: Comparator[A]): ResultType
If I managed to gain you interest you can take a look at one of the following libraries like cats, scalaz for scala and vavr for java which contain type class definitions and implementations for common types.
Top comments (0)