Rekursive Typdefinition Die VDM-Basistypen Trees

Unions

 

Was ist eine Union ?

Definition Eine Union  ist eine Klasse von Objekten, deren Datentyp nicht statisch festgelegt ist, sondern aus einer Menge mehrerer möglicher Datentypen stammt und sich dynamisch ändern kann.

Sprechweisen Ein Objekt dieser Klasse wird oft selbst als Union bezeichnet; zur Vermeidung von Irritationen wird hier allerdings eine Trennung versucht. Die verschiedenen möglichen Datentypen eines Union-Objekts prägen den Begriff der ,,Union-Komponenten`` oder ,,Union-Elemente``, von denen immer eine(s) aktuell ist, die anderen sind sozusagen virtuell: es gibt sie irgendwo, und es gibt Operationen, um sie real zu machen. Der ,,Inhalt`` eines Union-Objekts heißt so, weil die Verarbeitung erst nach einer Selektion möglich ist, nicht direkt.

unions Die unions in C tragen nicht zufällig den gleichen Namen: hinter ihnen steht das gleiche Konzept, die VDM-Unions können mit Hilfe von unions implementiert werden. Einen Vorteil haben die VDM-Unions allerdings -- Sie können ihren aktuellen Typ ermitteln, weil er in Form einer Zusatzinformation mitgespeichert wird. Sie können also Fehler, die auf der falschen Interpretation des Inhalts einer union beruhen, mit VDM-Unions vermeiden, da die Selektion eines ,,falschen`` Objekts nicht möglich ist.

Operationen Auch für Unions sind nur sehr wenige Operationen erforderlich: Erzeugung eines Union-Objekts, Ermittlung des aktuellen Typs eines Union-Objekts, Selektion des Inhalts, Überschreiben des Inhalts mit einem Wert vom gleichen oder anderen Typ und Test auf Gleichheit zweier Union-Objekte.

Nomenklatur Da Sie wie bei Trees auf verschiedene Datentypen innerhalb eines Objekts zugreifen können (im Unterschied zu Trees allerdings nicht gleichzeitig), können Sie auch für die Datentypen einer Union jeweils einen speziellen Namen festlegen, der den gleichen Konventionen folgt, also mit der Sequenz ,,s_`` beginnt und noch mindestens einen weiteren Kleinbuchstaben enthält. Da im Unterschied zu Trees mehrfach auftretende Datentypen sinnlos sind (ein Union-Objekt enthält ja nur ein Objekt...), sind diese Namen nicht Pflicht! Wenn Sie Namen angegeben haben, werden die komponentenbezogenen Operationen wie bei den Trees mit dem variablen Teil des Komponenten-Namens erweitert -- im anderen Falle erfolgt die Erweiterung mit dem Namen des jeweiligen Komponententyps. Sie sollten, wenn Sie von der Möglichkeit der Namensgebung Gebrauch machen möchten, sprechende Namen für die Komponenten wählen.

DSL Um in der DSL-Spezifikation eine Union Uni mit den möglichen Datentypen tex2html_wrap_inline7837 zu definieren, wählen Sie eine der beiden folgenden Möglichkeiten aus:

tex2html_wrap_inline8015 oder
*[1ex] tex2html_wrap_inline8017
*

Anwendung Unions können Sie immer dann einsetzen, wenn Sie wissen, daß ein bestimmtes Objekt eine endliche Anzahl verschiedener Ausprägungen haben kann und Sie alle möglichen Ausprägungen in einer einheitlichen Struktur bearbeiten möchten.

Beispiele Eine Anwendung haben Sie bereits kennengelernt: das Optional ist eine Union des Elementtyps und eines Typs mit dem einzigen Wert nil.

Im Compilerbau benötigen Sie Unions, um die in der Grammatik definierten Alternativen für ein Lexem (Terminal oder Non-Terminal) zu modellieren, die ja in der BNF (Backus-Naur-Form) ebenfalls mit Hilfe der senkrechten Striche ausgedrückt werden. Ein Beispiel:

statement ::= while | if-else | do-while | for | assignment | call | goto
*[0.5ex]

Ein Objekt, das ein Statement modellieren soll, muß alle diese Alternativen aufnehmen können.

Eine weitere Anwendung im Compilerbau ist die Symboltabelle. Die Konstanten eines Programms (als Beispiel) werden mit ihrem Namen und ihrem Wert gespeichert; der Wert kann aber einer der ordinalen Zahlentypen, einer der Gleitkomma-Zahlentypen oder auch ein Pointer sein, deren Repräsentation sich jeweils unterscheidet. Eine Union dieser Typen gibt Auskunft über den Typ und den Wert der Konstanten und kann zusammen mit dem Namen der Konstanten als Tree mit fester Größe modelliert werden. Hier ein Beispiel, das allerdings nur einige der genannten Typen abdeckt:

 DOMAINS 
 *
Type=Float|Int|Char 
 *
Float		=		ELEMENTARY 
 *
Int		=		ELEMENTARY 
 [0.5ex]
IMPLEMENTATIONS 
 *
Tab		=		union + ...
 *
Float		=		unsigned(CSTRUCT="float") 
 *
Int		=		scalar(LB=-32768,UB=32767) 
 *

Union-Implementationen

Für Unions stehen zwei Implementationen zur Verfügung:

Union-Operationen

Folgende Operationen erfahren eine Erweiterung mit dem variablen Komponenten-Namen: mk, s, chg, ovwrt und subst.

Zur Veranschaulichung dienen die Variablen t1 und t2 vom oben definierten Typ Type für Konstanten-Beschreibungen in der Symboltabelle eines Compilers.

Union-Konstruktoren

mk Mitmkerzeugen Sie ein Union-Objekt aus einem Objekt eines Komponententyps. Daher wird mk mit dem variablen Komponenten-Namen erweitert.
Beispiel -- Eintrag einer ,,int``-Konstante:

t1 = mk_int(14);
*[1.5ex]

subst substkopiert das Union-Objekt und überschreibt den Inhalt des neuen Union-Objekts mit dem angegebenen Wert, allerdings nur, wenn das Union-Objekt vorher den gleichen Typ hatte.
Beispiel -- eine weitere ``int``-Konstante soll aufgenommen werden:

t2 = mk_int(9);oder
* t2 = subst_int(t1, 9);
*[1.5ex]

copy / copy1 Auch ein Union-Objekt können Sie natürlich durch Kopieren eines anderen Union-Objekts erzeugen.

Union-Kombinatoren

Es gibt zwei Klassen von Kombinatoren: die einen ändern den Inhalt des Union-Objekts, aber nicht den Typ, die anderen können auch den Typ verändern. Beiden Arten ist gemeinsam, daß das Union-Objekt überschrieben und der alte Inhalt damit zerstört wird. Sollte das Union-Objekt vor dem Aufruf einer dieser Operationen Sub-Strukturen haben, müssen Sie es eventuell vorher mit del löschen.

Nomenklatur Alle Kombinatoren werden mit dem variablen Teil des Komponenten-Namens erweitert.

chg Mitchgüberschreiben Sie den Inhalt eines Union-Objekts, ohne dabei dessen Typ ändern zu können; ein solcher Versuch wird mit einem Laufzeitfehler quittiert!
Beispiel -- Änderung des Wertes der ersten Konstanten (welch ein Widerspruch in sich...):

t1 = chg_int(t1, 14965);
*[1.5ex]

ovwrt Mitovwrtüberschreiben Sie ein Union-Objekt ohne Rücksicht auf den vorherigen Inhalt.
Beispiel -- die erste Konstante stellt nun eine ,,Char``-Konstante dar (in dem gewählten Kontext völlig sinnlos):

t1 = ovwrt_char(t1, 'T');
*[1.5ex]

Union-Prädikate

is_eq Mitis_eqtesten Sie, ob zwei Unions, deren aktueller Typ gleich ist, den gleichen Inhalt haben.

t1 = mk_int(14);t2 = subst_int(t1, 9);
* tex2html_wrap_inline7296 is_eq_type(t1, t2) == FALSE
*[1.5ex]

is_gr Mitis_grtesten Sie zwei Unions des gleichen Typs, ob ihr Inhalt der Relation ,,gößer als`` genügt. is_gr können Sie nur anfordern, wenn es für alle möglichen Datentypen der Union definiert ist.

is_ge Mitis_getesten Sie, ob für zwei Unions des gleichen Typs eines der beiden obigen Prädikate erfüllt ist. Es gilt die gleiche Restriktion wie für is_gr.

is isstellt eine ganze Klasse von Prädikaten dar: sie ermöglichen die Ermittlung des aktuellen Typs eines Union-Objekts. Zu jeder Zeit ist genau eines dieser Prädikate erfüllt. is wird mit dem variablen Teil des Komponenten-Namens erweitert.

is_int(t1) == TRUE tex2html_wrap_inline8076 (is_char(t1) == FALSE tex2html_wrap_inline6445 is_float(t1) == FALSE)
*[1.5ex]

Union-Attribute

Diskriminante Die Information, welchen Typ ein Union-Objekt gerade hat, ist im Objekt in Form eines Datums gespeichert, das einem Aufzählungstyp angehört. Dieser Aufzählungstyp wird implizit mit enum definiert, wenn Sie eine Union spezifizieren; die Elemente heißen Diskriminanten der Union, sie stehen für die möglichen Datentypen. Der Name einer Diskriminante ist bis auf den ersten Buchstaben identisch mit dem Selektornamen: das führende ,,s`` wird durch ein ,,d`` ersetzt. Sie können verwendet werden, um in einer switch-Anweisung abhängig vom aktuellen Typ bestimmte Aktionen auszuführen.

discr discrliefert die Diskriminante eines Union-Objekts.
Beispiel -- Ausgabe des Konstantenwertes mittels spezieller Druckroutinen:

switch (discr_type(t1)) {
* case d_int: print_int(t1); break;
* case d_float:print_fl(t1); break;
* case d_char:print_char(t1); break;
* }
*[2ex]

Selektion in einer Union

s Mitsgreifen Sie auf den Inhalt eines Union-Objekts zu. Sie müssen allerdings die richtige, zum aktuellen Typ passende Version auswählen. Der Name wird jeweils mit dem variablen Teil des Komponenten-Namens erweitert, wodurch sich auch hier -- wie bei den Trees -- die Identität mit dem Komponenten-Namen ergibt.
Beispiel -- die eben eingeführten Druckroutinen müssen den Inhalt des Union-Objekts selektieren:

void print_int (Type t)
* { printf("%d", s_int(t)); }
*[2ex]

Funktionale

?switch Mit den beiden Funktionalen sswitchundcswitchkönnen Sie sogenannte Switch-Funktionen  erzeugen, die abhängig vom aktuellen Typ des Union-Objekts bestimmte Funktionen ausführen, also von der Struktur her ähnlich aufgebaut sind wie das Beispiel für die Operation discr; allerdings wird diesen Verarbeitungsfunktionen bereits der korrekt selektierte Inhalt des Union-Objekts übergeben.

Parameter Sie können den Resultattyp der einzelnen Verarbeitungsfunktionen (und damit auch der Switch-Funktion) mit dem Implementationsparameter RESULT selbst bestimmen -- wenn Sie nichts angeben, werden Prozeduren vorausgesetzt. Außerdem können Sie jeder Verarbeitungsfunktion weitere Argumente übergeben, deren Datentypen Sie mit den Parametern XX, XXX, XXXX, ... festlegen; beim Aufruf der Switch-Funktion müssen Sie diese Argumente dann zusätzlich angeben.

Nomenklatur Die Namen der Verarbeitungsfunktionen entsprechen dem erweiterten Namen der Switch-Funktion. Bei sswitch werden die Namen mit den variablen Teilen der Komponenten-Namen erweitert, bei cswitch mit den Namen der möglichen Datentypen in der Union.

Ein Beispiel soll die Zusammenhänge verdeutlichen: Sie fordern in der DSL-Spezifikation für die Konstanten-Tabelle folgende Funktion an:

cswitch.sw(RESULT="Bool",XX="Nat0")
*

Daraus wird die folgende Switch-Funktion generiert:

Bool sw_type(Type t,Nat0 n)
* {
* switch (discr_type(t)) {
* case d_float:return sw_float(s_float(t), n);
* case d_int:return sw_int(s_int(t), n);
* case d_char:return sw_char(s_char(t), n);
* }
* }
*[1.5ex]

Die Wirkung von sswitch ist die gleiche, da bei der Definition von Type keine Komponenten-Namen angegeben wurden.

Die Verarbeitungsfunktionen sw_float, sw_int und sw_char mit den entsprechenden Prototypen müssen Sie natürlich definieren; die mühselige Schreibarbeit für die Switch-Funktion bleibt Ihnen jedoch erspart!

Rekursive Typdefinition Die VDM-Basistypen Trees

VDM Class Library