Type classes and instances in Haskell

The best way to explain type classes and instances in Haskell for Java programmers is by way of interfaces and classes in Java. (Note the possibility for confusion here: both languages have something called classes, but the classes in Haskell are like the interfaces in Java.) In Java, a class is a specific set of definitions which in some sense implement a data type (for example, trees, hash tables, etc., or even something more application-specific such as pizzas or Go strategies). On the other hand, in Java an interface is a sort of abstract template, a set of bodyless method definitions (i.e., just names and types) which some specific class might implement. And in fact, when we want to say that a specific class in Java meets up to the abstract specification of an interface, we specify that the class implements the interface overtly in the class header (and then of course the method definitions specified by the interface must be declared, with the appropriate types in the class body).

In Haskell, when we want to define a data type, we usually define it using an algebraic type definition, along with a few function definitions. (In Haskell, unlike Java, the data type and functions don't get packaged up together: instead, the functions are defined "externally" to the type definition. If we want to package them up as in Java, we put them in a separate file and call it a module. But that's a story for another day ... .) On the other hand, when we want to specify and abstract template in Haskell, we define a type class, similar to an interface in Java: basically a name (such as Eq or Ord) for a set of "abstract functions", i.e., just names and types without bodies. Then, when we want to say that the data type "implements" the specification defined by the type class, we make an instance declaration (see the example below for syntax on all these things).

For example, here are a definition for a color type, a comparison function on colors, a class defining an abstract comparison function and an instance declaration connecting them:

data Color = Red | Green | Blue

warmer Red   Green = True
warmer Red   Blue  = True
warmer Green Blue  = True
warmer c1    c2    = False		-- this isn't the most exciting function ...

class Testable a where
  test :: a -> a -> Bool

instance Testable Color where
  test = warmer
In this example, the Color data type has three values and the warmer function takes any two colors and returns a boolean result (note by the way that it uses two variables in the last clause to return False in any case which isn't covered by the first three clauses). The Testable type class says basically that any type can be considered Testable if it has an appropriate boolean function called test. Finally, the instance declaration says that the justification for Color being a Testable type (the reason it is a member of the Testable class) is that it has a function called warmer defined on it (and which has the type requested for test).

Now, this is a good time to point out another aspect of the analogy between Java and Haskell approaches. In Java, when we declare that a class implements some interface, that specific implementation becomes inextricably tied to the class: we can't have two implementations of an interface for the same class, even if there might be multiple ways to justify it (i.e., multiple sets of method definitions that could fit the bill). Likewise in Haskell. Once we declare that Color is a Testable type by virtue of the warmer function, that's it: there can only be one way of making Color Testable at any given time.

There is, however, a way around this in Haskell just as there is in Java: in Java we use anonymous inner classes (see the Java documentation at Sun for details; look up Comparator versus Comparable). Basically, the Comparable interface is implemented by the (Java) class itself, giving the class a "native" sorting method, one which is built in. The Comparator interface is instead implemented anywhere you would like to define a new comparison method (the actual name is compare) which can then be used to sort things.

In Haskell, when we declare that a data type is an instance of some class, we are in some sense building in the associated functions as "native". In Haskell, however, we don't build them into the type so much as we build them into the connection between the type and the class, by putting them in as the "witnesses" in the class instance declaration. For example, the function warmer from above witnesses the ability to test colors, thus making the Color type and instance of the type class Testable.

You can see how these features get used by looking at the types of the sort and sortBy functions in the List module:

Main> :l List
Reading file "/usr/local/lib/hugs/lib/List.hs":
Reading file "/usr/local/lib/hugs/lib/Maybe.hs":
Reading file "/usr/local/lib/hugs/lib/List.hs":
List> :t sort      
sort :: Ord a => [a] -> [a]
List> :t sortBy
sortBy :: (a -> a -> Ordering) -> [a] -> [a]
The basic sort function assumes that the items in the list to be sorted are already an instance of Ord, so that the appropriate comparison functions are already available. So sort uses the "native" ordering defined via the Ord instance of the type a (and enforces that it have such an instance by way of the qualification "Ord a => ..."). On the other hand, the sortBy function takes any comparison function you give it as its first argument and uses it to sort the list given as its second argument. The ...

(oops, haven't finished this yet, nbut I'll work on it ...)

Two extra "phenomena" that we see in Hasekell: first, a class declaration can require that the type it concerns also meet some other criteria. For example, we might want to define some class (say, Eligible) that presumes the existence of a test function, but also declares some further functions to be available. So we need to require that any type which is an instance of the Eligible class is also an instance of the Testable class: we do this by adding a qualification or condition on the class declaration:

class Testable a => Eligible a where
  foo :: a -> Int
  ...
Now a type cannot be declared to be Eligible unless it is already demonstrably Testable. Furthermore, in Haskell some classes can ...

(oops: see "oops" above)