Type classes vs object interfaces
In their basic form, type classes are somewhat similar to object interfaces. However, in many respects, they are much more general.


Dispatch is on types, not values. No value is required to perform it. For example, it is possible to do dispatch on the result type of function, as with Haskell's Read class:

class Read a where
  readsPrec :: Int -&gt; String -&gt; [(a, String)]
  ...


Such dispatch is clearly impossible in conventional OO.
Type classes naturally extend to multiple dispatch, simply by providing multiple parameters:

class Mul a b c where
  (*) :: a -&gt; b -&gt; c

instance Mul Int Int Int where ...
instance Mul Int Vec Vec where ...
instance Mul Vec Vec Int where ...

Instance definitions are independent from both class and type definitions, which makes them more modular. A type T from module A can be retrofitted to a class C from module M2 without modifying the definition of either, simply by providing an instance in module M3. In OO, this requires more esoteric (and less OO-ish) language features like extension methods.
Type classes are based on parametric polymorphism, not subtyping. That enables more accurate typing. Consider e.g.

pick :: Enum a =&gt; a -&gt; a -&gt; a
pick x y = if fromEnum x == 0 then y else x


vs.

pick(x : Enum, y : Enum) : Enum = if x.fromEnum() == 0 then y else x


In the former case, applying pick '\0' 'x' has type Char, whereas in the latter case, all you'd know about the result would be that it's an Enum. (This is also the reason why most OO languages these days integrate parametric polymorphism.)
Closely related is the issue of binary methods. They are completely natural with type classes:

class Ord a where
  (&lt;) :: a -&gt; a -&gt; Bool
  ...

min :: Ord a =&gt; a -&gt; a -&gt; a
min x y = if x &lt; y then x else y


With subtyping alone, the Ord interface is impossible to express. You need a more complicated, recursive form or parametric polymorphism called "F-bounded quantification" to do it accurately. Compare Java's Comparable and its use:

interface Comparable&lt;T&gt; {
  int compareTo(T y);
};

&lt;T extends Comparable&lt;T&gt;&gt; T min(T x, T y) {
  if (x.compareTo(y) &lt; 0)
    return x;
  else
    return y;
}



On the other hand, subtyping-based interfaces naturally allow the formation of heterogeneous collections, e.g. a list of type List&lt;C&gt; can contain members that have various subtypes of C (although it is not possible to recover their exact type, except by using downcasts). To do the same based on type classes, you need existential types as an additional feature.