1.7.1 Algebraische Spezifikation
Während bei der axiomatischen Beschreibung die Operationen nur implizit
angegeben werden (s.u.), werden sie bei einer algebraischen Beschreibung
explizit als Funktionen dargestellt. Diese Funktionen brauchen allerdings
nicht unbedingt besonders rechner- oder implementationsnah zu sein.
Manchmal wird die Beschreibung auf diese Weise auch eher umständlich.
Beispiel: SET[X]
Wegen der algebraischen Darstellungsweise ist einer der am einfachsten
beschreibbaren Datentypen der einer Menge von Elementen von einem Typ
X.
Da die Menge Elemente beliebigen Datentyps X speichern können soll,
haben wir es hier mit einem (durch einen anderen Typ) parametrisierten
Datentyp zu tun (wir schreiben SET[X]). Dieses Konzept wird von den
meisten objektorientierten Sprachen unterstützt und manchmal sogar als
Voraussetzung dafür gesehen, daß sich eine Sprache objektorientiert
nennen darf.
Unsere Notation einer algebraischen Beschreibung wird aus der folgenden
Beispiel-Spezifikation deutlich. Typnamen wollen wir mit großen Buchstaben
schreiben, Funktionsnamen mit kleinen.
| algebra
| SET[X]
| // Name der Algebra
|
| sorts
| SET, X, BOOLEAN
| // vorkommende Typnamen
|
| operations
| | | // Signaturen der Operationen
|
|
| new:
| -> SET[X]
| // Anlegen einer neuen Menge (leer)
|
|
| isempty:
| SET[X] -> BOOLEAN
| // Test, ob die Menge leer ist
|
|
| insert:
| SET[X] × X -> SET[A]
| // Hinzufügen eines Elements
|
|
| delete:
| SET[X] × X -> SET[A]
| // Herausnehmen eines Elements
|
|
| contains:
| SET[X] × X -> BOOLEAN
| // Enthaltenseins-Test
|
| sets
| SET[X] = P(X) (Potenzmenge)
| // Menge der Werte für SET[X]
|
functions
|
| new
| :={ }
|
| isempty(S)
| :=(S={ })
|
| insert(S,x)
| :=S vereinigt {x}
|
| delete(S,x)
| :=S \ {x}
|
| contains(S,x)
| :=(x in S)
| end
| SET[X]
| | | | | | | |
Den Typ BOOLEAN mit der Operation not: BOOLEAN ->BOOLEAN
(für Wahrheitswerte true und false) wollen wir als eine Art
"Metatyp" ansehen, da er für die Notation der Bedingungen notwendig
ist (siehe Schreibweise bei isempty und contains).
Im Abschnitt operations werden lediglich die Signaturen der
Operationen festgelegt. Um den Operationen eine Bedeutung zu geben, muß
zusätzlich eine Semantik angegeben werden.
Dazu zählt zunächst (im sets-Teil) die Definition des
sogenannten Trägers des Datentyps, d.h. der reinen Menge der Werte,
die Elemente des Datentyps annehmen können (sozusagen der Datentyp nach
Vergessen der algebraischen Struktur).
Bei unserer algebraischen Spezifikation folgt dann im functions-Teil
die direkte Definition der Operationen mit Hilfe mathematischer Notationen.
Bemerkungen:
-
Bei dieser Definition braucht nicht auf irgendwelche algorithmentheoretische
Bedingungen geachtet zu werden: Es dürfen beispielsweise
Unendlichkeiten wie unendliche Mengen oder Summen mit nicht endlichen
Grenzen verwendet werden.
Wenn die Operationen algorithmisch beschrieben werden (insbesondere
ohne Unendlichkeiten), spricht man nicht mehr von einer Algebra, sondern von
einer Datenstruktur. Die Implementierung einer Datenstruktur in einer
Programmiersprache schließlich ist dann ein Modul, eine Klasse
o.ä.
-
Die Notation insbesondere der Funktionen insert und delete ist
vielleicht etwas gewöhnungsbedürftig. In realen Implementationen
wird es natürlich so sein, daß durch sie unser Objekt (unsere
Menge) verändert wird. In der Funktionsschreibweise
(SET[X]× X -> SET[X]) sieht es so aus, als müßten wir
ein vollständig neues Objekt erzeugen.
Unsere Schreibweise ist dennoch die mathematisch einzig sinnvolle, und wenn
wir mathematische Hilfsmittel zur Verfügung haben wollen, müssen wir
sie verwenden.
-
Man beachte, daß wir hier alle Operationen explizit angegeben
haben. Da der Begriff "Menge" aber relativ abstrakt und rechnerfern ist,
haben wir dennoch keine Implementation "versehentlich" mitspezifiziert.
Durch diese Art der Definition ist man aber manchmal doch gezwungen, den
halben Weg in Richtung einer Implementation zu gehen. Die abstrakte
Spezifikation (s.u.) vermeidet dies meist.
-
Noch eine Bemerkung zur Parametrisierung des Typs: Mengen bestimmter
Datentypen (z.B. zusammenhängende Bereiche aus den ganzen Zahlen)
lassen sich auf dem Rechner gut durch Bitfelder darstellen -- jedes
mögliche Element bekommt ein Bit zugeordnet, das genau dann
gesetzt ist, wenn es tatsächlich in der Menge enthalten ist. So
sind Mengen in PASCAL implementiert.
Bei einer Definition wie oben, für beliebigen Datentyp, ohne
Ordnungsrelation auf X, etc., würde es dagegen dem
Implementierer vermutlich nicht erspart bleiben, die Elemente alle
(z.B. in einer Liste) zu speichern, bei contains alle Elemente
durchzugehen, usw.
In C++ ist eine solche parametrisierte Definition von Datentypen
tatsächlich erlaubt. Bei der Deklaration eines Objekts dieses Typs
muß dann der Typ X mit angegeben werden (in der Art "sei
s ein Stack für int-Objekte"). Werden irgendwelche
Operationen mit dem Typ X in der
Definition benötigt, z.B. bei einem sortierten Binärbaum die
Ordnungsrelation <=, müssen diese natürlich für
X definiert sein.