Typklassen bieten die Möglichkeit zur Überladung von Funktionen. Somit kann die konkrete Implementierung für unterschiedliche Parametertypen individuell gestaltet werden. Für jeden Datentyp können Instanzen unterschiedlicher Typklassen implementiert werden, jedoch nur eine Instanz für jedes Paar aus Typklasse und Datentyp.
Beispiel für einen einfache Typklasse:
class Eq a where
(==) :: a -> a -> Bool
Die Typklasse Eq
bietet eine Funktion zur Implementierung an. Die Funktion (==)
(Die runden Klammen erlauben die spätere Infixnotation.) prüft zwei Werte vom Typ a
auf Gleichheit. Eine Instanz für den Datentyp Bool
könnte wie folgt aussehen:
instance Eq Bool where
x == y = (x && y) || (not x && not y)
Für den Typen a
bzw. Bool
im Beispiel bestehen keine Vorraussetzungen. Wird jedoch zur Implementierung einer Typklasse die Implementierung einer, oder mehrerer anderer Typklassen vorrausgesetzt kann dies wie folgt eingefordert werden:
data Ordering = LT | EQ | GT
class (Eq a) => Ord a where
compare :: a -> a -> Ordering
Der Zusatz (Eq a) =>
erfordert bei der Implementierung von Ord
für einen Datentyp a
, dass dieser die Typklasse Eq
implementiert.
Bei den oben gezeigten Typklassen treten die Typvariablen als Platzhalter für einfache Datentypen in den Typsignaturen der Funktionen auf. Somit lassen sich diese Klassen nur für vollständige Typen, wie z.B. Integer
, oder Bool
implementieren. Die Implementierung für Datencontainer, wie z.B. []
ist nur mit Angaben über den Elementtyp möglich:
instance (Eq a) => Eq [a] where
[] == [] = True
(x:xs) == (y:ys) = x == y && xs == ys
_xs == _ys = False
Folgende Implementierung sieht zwar auf den ersten Blick vielversprechend aus (trotz fehlerhafter Semantik von ==
), führt jedoch beim Kompilieren zu einem Fehler:
instance Eq [] where
[] == [] = True
_ == _ = False
-- Fehler:
-- `[]' is not applied to enough type arguments
Da für den Typparameter von []
kein Wert übergeben wurde, handelt es sich nicht um einen einfachen Datentyp, sondern um einen Typkonstruktor. Folgerichtig bemängelt der Compiler das Fehlen eines Argumentes, welches aus []
einen einfachen Typen machen würde. Es existieren jedoch Typklassen für genau diesen Fall.
Es ist möglich Typklassen zu schreiben, die nicht für einfache Datentypen, sondern für Typkonstruktoren gedacht sind. Dies wird dadurch deutlich gemacht, dass die Typvariable nicht alleine an Parameterposition, sondern als Konstruktor auftritt:
class Foo a where
bar :: a b -> b
Die Funktion bar
hat einen Parameter vom Typ a b
und liefert als Resultat einen Wert vom Typ b
. Zwei mögliche Implementierungen der Typklasse Foo
könnten wie folgt aussehen:
instance Foo [] where
bar [] = undefined
bar (x:xs) = x
instance Foo Maybe where
bar Nothing = undefined
bar (Just x) = x
Für den Datentypen von b
werden durch die Typklasse keine Angaben, oder Einschränkungen gemacht. Er dient lediglich als Typ des Rückgabewertes von bar
. Das einzige was in der Implementierung von bar
also passieren kann, ist dass ein Wert aus dem Container ausgewählt und zurückgegeben wird. Die Semantik dieser Implementierungen entspricht grob der von head
, oder
fst
.