Typeclassopedia
Funktoren, Monaden, Arrows: Typklassen für Typkonstruktoren

von Robert Steuck


Einleitung | Grundlagen | Vom Funktor zur Monade | Monoid | Arrows | Quellenverzeichnis

Grundlagen


Typklassen allgemein

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.


Typklassen für einfache Datentypen

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.


Typklassen für Typkonstruktoren

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.


Einleitung | Grundlagen | Vom Funktor zur Monade | Monoid | Arrows | Quellenverzeichnis