|
Übersicht: Von ADTs zu GADTs
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.
|