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:
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).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
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:
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 ...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]
(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:
Now a type cannot be declared to be Eligible unless it is already demonstrably Testable.
Furthermore, in Haskell some classes can ...
class Testable a => Eligible a where
foo :: a -> Int
...
(oops: see "oops" above)