Von ADTs zu GADTs


 ... [ GADTs in Haskell ] ... [ << Einleitung ] ... [ Beispiele für GADTs >> ] ...  




Übersicht: Von ADTs zu GADTs

  ADTs
  Phantomtypen
  Einschränkung von Typparametern
  Erweiterung der Rückgabetypen



ADTs

Algebraische Datentypen (kurz ADT, [aːdeːteː], auch Summen-Datentypen) sind Datentypen, die eine Verzweigung abbilden.
	data X = X1 A | X2 B | ...
      
Hier ist X der algebraische Datentyp, X1, X2, ... sind Konstruktorfunktionen. X1 konstruiert aus einem Wert vom Typ A einen Wert vom Typ X, welcher den Wert vom Typ A speichert.



Die Menge der Konstruktorfunktionen ist stets endlich. Der Wertebereich von X kann abhängig von den Wertebereichen A, B, ... endlich oder unendlich sein:
	|X| = |A| + |B| + ...
      
X enthält die Werte {a1, a2, a3, ..., b1, b2, b3}, wobei diese jeweils in ein Konstruktor X1, X2, ... eingepackt werden.



Dies entspricht einer einstufigen Vererbung aus der Objektorientierung, wobei X die abstrakte Oberklasse ist, und X1, X2, ... die konkreten Unterklassen von X.



Für ADTs gibt es viele Anwendungsfälle, z.B. Abstrakte-Syntax-Bäume oder allgemeiner: rekursive Datenstrukturen.

Phantomtypen

In ADTs sind beliebig viele Typparameter möglich. Im vordefinierten Listendatentyp werden sie verwendet, um den Datentyp Liste vom Elementtyp unabhängig zu machen:
	data List a = Nil
                    | Cons a (List a)
      
Listenfunktionen können nun auch unabhängig vom Elementtyp implementiert werden.



Phantomtypen sind solche Typprarameter, die auf der rechten Seite der Typgleichung nicht verwendet werden:
	data PList a b = Nil
                       | Cons a (List a b)
      
Natürlich wird b hier auf der rechten Seite verwendet; allerdings nur bei der Rekursion. Es gibt kein Attribut, welches tatsächlich den Typ b hat. Daher nennt man b Phantomtyp.



Mit Phantomtypen ist es möglich, verschiedene Listen mit dem gleichen Elementtyp voneinander zu unterscheiden. Solche Listen sind nicht mehr typgleich:
	data Euro = ...
	data USD  = ...
	euroToUsd :: PList Int Euro -> PList Int USD
      
Das Beispiel verdeutlicht, dass nun Int-Listen verschiedene Semantik haben können: Euro-Listen können nicht mehr mit USD-Listen vertauscht werden, obwohl beide den Elementtyp Int haben.

Dieses Ziel kann man auch auf andere Weise erreichen; es geht hier jedoch zunächst nur um die Verwendung von Phantomtypen, da diese oft mit GADTs kombiniert werden, um das gewünschte Ergebnis zu erzielen.

Einschränkung von Typparametern

Eine kleine Erweiterung durch GADTs ist die Möglichkeit der Einschränkungen von Typprarametern.



Eine Einschränkung wird hier durch eine Typklassen-Zugehörigkeit angegeben:
	data NumList a = NNil
	               | Num a => NCons a (NumList a)
      
NCons hat, wie die allgemeine Liste, ein Attribut von Typ a für das zu speichernde Element und ein Attribut vom Typ NumList a für die Restliste. Der Konstruktor NCons akzeptiert nun jedoch nur noch Elementtypen a, welche die Num Typklasse instanzieren.



Diese Einschränkung gilt jedoch nur für den Konstruktor NCons und nicht für den gesamten Typkonstruktor NumList. Der Typ NumList String ist daher zulässig. Es gibt jedoch in diesem Fall nur einen Wert von diesem Typ: NNil.

Erweiterung der Rückgabetypen

Die wichtigste Neuerung durch GADTs ist jedoch die Erweiterung der Rückgabetypen. In dem Typ List haben alle Typkonstruktoren (Nil und Cons) den gleichen Rückgabetyp List a.

Diese Einschränkung wird bei GADTs aufgehoben.



Um verschiedene Rückgabetypen zu ermöglichen, wurde die Syntax erweitert. Hier ist eine zum bekannten Listen-Typ äquivalente Definition mit der neuen Syntax:
	data List a where
	  Nil  :: List a
	  Cons :: a -> List a -> List a
      
Bei dieser Syntax ist die funktionale Sichtweise direkt erkennbar: Cons ist eine Funktion mit zwei Parametern a und List a sowie dem Rückgabetyp List a.



Hier ein Beispiel, welches verdeutlich, welche Möglichkeiten die neue Syntax bietet:
	Cons1 :: a -> List a -> List a
	Cons2 :: a -> List a -> List (a,a)
	Cons3 :: a -> List a -> List (List a)
	Cons4 :: a -> List a -> a              -- Fehler
      
Der Rückgabetyp ist beliebig; solange als Typkonstruktor der GADT selbst verwendet wird.



Im nächsten Abschnitt wird anhand von Beispielen gezeigt, wie man diese Erweiterung sinnvoll einsetzen kann.



 ... [ GADTs in Haskell ] ... [ << Einleitung ] ... [ Beispiele für GADTs >> ] ... [ nach oben ] ...