Vom Urknall zum Universum

Gedanken zu einem Konzept
für physikalische Grundtypen
in Ada

Christoph Grein

Zusammenfassung: Es wird gezeigt, daß Ada sich nur sehr bedingt dazu eignet, das physikalische Einheiten-System mit seinem Typkonzept darzustellen, besonders unter harten Echtzeitbedingungen, wo die Korrektheit der Einheiten schon zur Übersetzungszeit überprüft werden soll. Daher wird ein Satz von Ada-Paketen vorgestellt, mit deren Hilfe das Rechnen mit physikalischen Größen weitestgehend ohne Typumwandlungen ermöglicht wird, wobei jedoch die Vorteile der Typbindung in kritischen Fällen erhalten bleiben. Grundlegende mathematische Funktionen wie die Quadratwurzel und trigonometrische Funktionen werden dabei zu vordefinierten Operationen der numerischen Basistypen, wobei Formeln wie x=x0cos(phi) mit Winkeln in Grad- und Bogenmaß dimensionsrichtig ebenfalls ohne Typumwandlung darstellbar sind.
(Siehe auch SI Units - Checked and Unchecked.)

Einleitung:

Die Typen in Ada mit ihrer strengen Bindung bieten bei richtiger Anwendung große Datensicherheit, das heißt, sie verhindern, daß unabsichtlich Birnen mit Äpfeln verglichen werden. Dieses Konzept hat sich so gut bewährt, daß auch Sprachen wie C, die ursprünglich höchstens über eine sehr eingeschränkte Typbindung verfügten, ähnliche Ideen in ihren neuen Standards implementieren. Nun haben auch physikalische Dimensionen die Eigenheit, nur auf spezielle Weise miteinander verknüpfbar zu sein, und jeder Physiker ist gewohnt, Gleichungen auf dimensionsmäßige Richtigkeit zu überprüfen. Somit liegt der Versuch nahe, dem Compiler diese Arbeit aufzubürden, indem Dimensionen durch Typen dargestellt werden. Daß das allerdings nicht so ganz einfach ist, zeigt schon die Beobachtung, daß zwar Meter plus Meter wieder Meter ergibt, Meter mal Meter jedoch Meter zum Quadrat, und daß Ada-Operatoren stets typtreu arbeiten (für A, B vom Typ T ist A*B ebenfalls vom Typ T). Die folgenden Überlegungen werden zeigen, daß Ada sogar völlig ungeeignet ist, mit vernünftigem Aufwand zur Übersetzungszeit Dimensionsprüfungen bei Zuweisungen in voller Allgemeinheit durchzuführen.

Der gedankliche Fehler bei diesem Versuch liegt darin, daß Typen, die nur reine Zahldarstellungen in irgendeiner Maschine beschreiben, mit willkürlich gewählten Objekten der realen Welt in Verbindung gebracht werden sollen, die als Bezug bei der Messung physikalischer Größen dienen.

Ein Meter ist die Länge des sogenannten Urmeters, einer Stange aus Platin, die in Paris unter wohldefinierten Umweltbedingungen aufbewahrt wird. (So wenigstens war es einst definiert.)

Es wird ein Verfahren aufgezeigt werden, wie mit physikalischen Dimensionen unter Einschließung der mathematischen Funktionen so umgegangen werden kann, daß möglichst viel Information bezüglich der Dimension in Deklarationen enthalten ist, keine Typumwandlungen nötig sind und trigonometrische Funktionen dimensionsrichtig arbeiten. Da bei seiner Verwendung alle Kompatibilitätsprüfungen schon zur Übersetzungszeit durchgeführt werden, ist es im Gegensatz zu anderen Verfahren auch unter harten Echtzeitbedingungen anwendbar.

Ada ist nicht geeignet für das Rechnen mit physikalischen Dimensionen!

Physikalische Dimensionen sollen durch Ada-Typen dargestellt werden:

type Laenge          is new Float;
type Zeit            is new Float;
type Geschwindigkeit is new Float;

Die vordefinierten Multiplikations- und Divisionsoperatoren sind unbrauchbar, da sie innerhalb des Typs bleiben, ja es kann sogar zu groben Fehlinterpretationen führen, Typen auf diese naive Weise anzuwenden. Für Fehlerabschätzungen ist der relative Fehler Delta := Delta_x/x (eine reine Zahl) als dimensionierte Variable zu vereinbaren

Delta, X, Delta_X: Laenge;

damit

Delta := Delta_X/X;

übersetzbar ist, eine arge Irreführung zukünftiger Leser dieses Programms! (Ebenso unsinnig ist es, transzendente Funktionen wie exp für dimensionierte Argumente aufzurufen.)

Es gibt zwei Möglichkeiten, die Gleichung s = vt in Ada darzustellen:

S := Laenge (V) * Laenge (T);

oder

S := V * T;

wobei im letzteren Fall der "*"-Operator folgendermaßen zu definieren ist:

function "*" (Links: Geschwindigkeit; Rechts: Zeit) return Laenge;

Bei der ersten Art verliert man alle Möglichkeiten, den Compiler auf Grund der Typbindung die Richtigkeit der Operation durch Dimensionsvergleich der rechten und linken Seite einer Zuweisung prüfen zu lassen, und diese Prüfung ist doch der eigentliche Grund, warum unterschiedliche Typen für dimensionsmäßig verschiedene Größen überhaupt eingeführt werden sollen. Bei der zweiten Art ist selbst für nur wenige Dimensionen ein Wust von überladenen Funktionen zu definieren. Wer je selbst versucht hat, wenigstens für die Kinematik mit ihren drei Einheiten Länge, Zeit und Masse (geschweige denn für das volle SI-System mit seinen sieben Basisdimensionen Meter, Kilogramm, Sekunde, Ampere, Kelvin, Candela, Mol) ein Paket mit überladenen "*"- und "/"-Operatoren zu definieren, die in jeder Einheit wenigstens bis zur vierten Potenz gehen (die Berechnung des Betrags einer vektoriellen Beschleunigung erfordert die vierte Potenz der Zeitdimension im Nenner, und man glaubt kaum, welch ungewöhnliche Einheiten bei der Berechnung von Zwischenergebnissen auftreten), weiß, wovon hier die Rede ist: Die Anzahl der erforderlichen Funktionsdefinitionen geht leicht in die Hunderte.

Man könnte einwenden, diese Definition ist ja nur einmal zu machen und das fertige Paket nur zu withen und zu usen, ohne daß sich der Benutzer noch weitere Gedanken über die Komplexität des Paktes zu machen braucht, doch ist das Argument leider nicht ganz korrekt. Es berücksichtigt nur die einfache Multiplikation und Division (nebenbei bemerkt benötigt man auch in Ausdrücken wie nRT/p keine Klammerung, obwohl die Dimensionen der Zwischenergebnisse von der Reihenfolge der Auswertung abhängen, da die Operatoren auf der gleichen Rangstufe von links nach rechts ausgewertet werden). Operationen wie Exponentiation an und Wurzelziehen root (n, a)sind nämlich überhaupt nicht darstellbar. Hier muß immer mit Typumwandlungen gearbeitet werden.

Jones [1, 2] beschreibt eine solche Methode der Überladung der Operatoren mit privaten Typen. Allerdings wird sein Verfahren äußerst unhandlich, wenn Dimensionen gemischt werden (s := v*t;), besonders bei Verwendung von mehr als drei Dimensionen, und es schließt mathematische Funktionen nicht ein.

Will man trotzdem Dimensionsprüfungen in voller Allgemeinheit durchführen, stehen dafür grundsätzlich zwei Methoden zur Verfügung.

Hierzu gibt es eine Vielzahl von Veröffentlichungen. Gehani [3] vergleicht die Form der Gleichungen bei Verwendung von Ada-Typen mit der Form in einer Sprache, die Dimensionen direkt als Datenattribute definieren kann, und kommt zum Schluß, daß nur letztere Methode alle Probleme elegant und zufriedenstellend lösen kann, jedoch eine Sprachänderung von Ada erzwingt. Da er Sprachänderungen in dieser Richtung zur damaligen Zeit nicht für wahrscheinlich hielt (der Ada9X-Prozeß hat ihm Recht gegeben) und es auch noch keine Erfahrungen mit derartigen Sprachen gibt, macht er einen Vorschlag, wie die Korrektheit der physikalischen Gleichungen während der Laufzeit geprüft werden kann, der hier in leichter Anpassung an das SI-Gleichungssystem wiedergegeben wird:

type SI_Base_Unit is (Kilogramm, Meter, Second, Ampere,
                      Kelvin, Candela, Mol);

type Dimension is array (SI_Base_Unit) of Integer;  -- Sqrt: 1/2
                                                    -- nicht darstellbar
type Physical_Item is record
  Unit : Dimension;
  Value: Float;
end record;

Dies ist wohl die umfassendste Lösung, ohne Ada zu modifizieren (durch Spracherweiterung oder Präprozessor). Es läßt sich sogar die Exponentiation konsistent behandeln. Für das Wurzelziehen benötigt man allerdings eine rationale Arithmetik anstelle der ganzen Zahlen für die Potenzen der SI-Basiseinheiten.

Gehanis Methode leidet allerdings darunter, daß die Notation in Deklarationen und Anweisungen sehr von der gewohnten technisch-wissenschaftlichen abweicht. Dies läßt sich jedoch leicht beheben durch Verwendung privater Typen mit Diskriminanten wie:

type Physical_Item (kg, m, s, A, K, Cd, mol: Rational) is private;

subtype Meter  is Physical_Item (m => +1,
                                 kg | s | ... => +0);
subtype Second is Physical_Item (s => +1,
                                 kg | m | ... => +0);
subtype Ohm    is Physical_Item (kg => +1, m  => +2,
                                 s  => -3, A  => -2,
                                 ... => +0);

function Convert (Number: Float;
                  kg, m, s, A, K, Cd, mol: Rational := +0)
                  return Physical_Item;

function "+" (Left, Right: Physical_Item) return Physical_Item;

function "*" (Left,        Right: Physical_Item) return Physical_Item;
function "*" (Left: Float; Right: Physical_Item) return Physical_Item;

function "**" (Base: Physical_Item; Exponent: Rational)
               return Physical_Item;

S: Meter;
T: Second := Convert (10.0, s => +1);
G: constant Physical_Item (m => +1, s => -2,
                           kg | A | K | Cd | mol => +0) :=
      Convert (9.81, m => +1, s => -2);

S := 0.5 * G * T ** 2;

Statt der Untertypen für Meter etc. kann man auch Konstanten vereinbaren bei geeigneter Definition der Multiplikation:

Meter    : constant Physical_Item (m => +1, kg | s | ... => +0) :=
             Convert (    1.0, m => +1);
Kilometer: constant Physical_Item (m => +1, kg | s | ... => +0) :=
             Convert (1_000.0, m => +1);
Fuss     : constant Physical_Item (m => +1, kg | s | ... => +0) :=
             Convert (    0.3, m => +1);

Weit := 40.0E+3 * Kilometer;
Nah  :=  3.0    * Fuss;

Der Verzicht auf Vorbelegungswerte für die Diskriminanten erzwingt eine eindeutige Festlegung der Dimension bei jeder Deklaration. So elegant diese Methode auch erscheint, so liegt ihr Nachteil doch im Laufzeitaufwand, so daß sich ihre Anwendung unter harten Echtzeitbedingungen verbietet.

Schneider [4] präsentiert einen Überblick über bisherige Versuche, Dimensionen in Programmiersprachen einzubeziehen. Er führt hierbei drei Wege auf: Erstens den Weg, Prüfungen dem Übersetzer zu überlassen; zweitens die Methode, Prüfungen zur Laufzeit durchzuführen, was, wie schon gesagt, zu Ineffizienz führt; drittens schließlich die Verwendung eines Präprozessors. Er stellt einen solchen für Pascal entwickelten und in C implementierten Präprozessor vor. Für weitere Literaturangaben auch zu älteren Überlegungen zu diesem Thema sei auf seine Arbeit verwiesen.

Da sich die Entwicklung eines eigenen Präprozessors für die meisten Anwendungen wohl verbietet, ist somit ein echtzeitfähiges Verfahren gesucht, das erforderliche Typumwandlungen minimiert, aber trotzdem noch semantische Inhalte zu tragen geeignet ist. Allerdings sollte aus dem oben Gesagten klar geworden sein, daß hierbei Opfer zu bringen sind. Das Opfer, das hier gebracht werden wird, ist die Dimensionsprüfung in Zuweisungen, in denen alle Variablen einem kohärenten Einheitensystem angehören. Dieses Opfer sollte um so leichter zu bringen sein, als diese Prüfung für die Multiplikation und Division nur mit äußerstem Aufwand und gar nicht bei Verwendung von Exponentiation und Wurzelziehen durchzuführen ist. Der Gewinn dafür wird sein, daß die mathematischen Funktionen als vordefinierte Operationen der zu verwendenden Ada-Typen existieren, so daß stets nur ein Paket, das Paket Universum, mit einer with- und einer use-Klausel anzuschließen ist und nicht mehr mehrere Pakete mit einerseits den Typdefinitionen und andererseits den mathematischen Funktionen. Die Namen der zu definierenden Typen und Untertypen werden so gewählt werden, daß jedwede Zweifel bezüglich der Einheit einer Größe ausgeräumt werden, wohingegen aus einer Deklaration wie v: Geschwindigkeit; bei Verwendung vieler Einheitensysteme [m/s, ft/min, km/h, Kts] nebeneinander in einem Projekt nicht hervorginge, welche Einheit nun die richtige ist.

"Am Anfang war das Wort":

Den Ausgangspunkt bilden zwei generische Einheiten.

Wir nehmen an, daß eine mathematische Bibliothek gegeben ist mit allen gewünschten Funktionen. Im folgenden soll das Vorgehen nur an Hand der zwei Funktionen Sqrt und Sin exemplarisch dargestellt werden, da sie alle wesentlichen Merkmale enthalten.

generic

  type Real is digits <>;

package Generische_Mathematische_Bibliothek is

  PI: constant := 3.14159;

  function Sqrt (X: Real) return Real;

  function Sin  (X: Real) return Real;  -- Zyklus 2*PI
  function SinD (X: Real) return Real;  -- Zyklus 360 Grad

  -- Weitere Funktionen

  Unzulaessiges_Argument: exception;

end Generische_Mathematische_Bibliothek;

Sollte die Funktion Sinus in der Form

function Sin (X, Zyklus: Real) return Real;

vorliegen, so läßt sich daraus leicht obige Form herstellen.

Des weiteren kommt das eingangs gescholtene Verfahren mit privaten Typen zur Anwendung.

generic

  type Zahl is digits <>;

package Physikalische_Dimension is

  type Einheit is private;

  function "<"   (Links, Rechts: Einheit) return Boolean;
  function "<="  (Links, Rechts: Einheit) return Boolean;
  function ">"   (Links, Rechts: Einheit) return Boolean;
  function ">="  (Links, Rechts: Einheit) return Boolean;

  function "+"   (Rechts: Zahl) return Einheit;
  function "-"   (Rechts: Zahl) return Einheit;

  function "+"   (Rechts: Einheit) return Einheit;
  function "-"   (Rechts: Einheit) return Einheit;
  function "abs" (Rechts: Einheit) return Einheit;

  function "+"   (Links, Rechts: Einheit) return Einheit;
  function "-"   (Links, Rechts: Einheit) return Einheit;

  function "/"   (Links, Rechts: Einheit) return Zahl;

  function "*"   (Links: Zahl   ; Rechts: Einheit) return Einheit;
  function "*"   (Links: Einheit; Rechts: Zahl   ) return Einheit;
  function "/"   (Links: Einheit; Rechts: Zahl   ) return Einheit;

  function Masszahl (X: Einheit) return Zahl;

private

  type Einheit is new Zahl;

  pragma Inline ("<", "<=", ">", ">=", "+", "-", "abs", "*", "/");

end Physikalische_Dimension;

Die Typumwandlungen von Zahlliteralen zu dimensionierten Größen geschieht hier mit den unären Additionsoperatoren, die Umkehrung mit der Funktion Masszahl.

X: Einheit := -31.0;
Y: Einheit := +(1003.0 - 4.12);

Masszahl (X) = -31.0

Man beachte, daß die Multiplikation zweier dimensionsbehafteter Größen nicht definiert ist, ihre Division liefert eine reine Zahl.

Das Paket Urknall:

In einer Art Schöpfungsakt werden mit diesem Paket Generische_Mathematische_Bibliothek nun Grundtypen geschaffen, die alle gewünschten Operationen vererben können.

with Generische_Mathematische_Bibliothek,  -- und was sonst Vererbbares noch
     Physikalische_Dimension;              -- benötigt wird

package Urknall is

  type Ur_Float is digits project_defined;

  package Ur_Mathematische_Bibliothek is
     new Generische_Mathematische_Bibliothek (Ur_Float);

  -- Herausnehmen der Unterprogramme aus der Ausprägung, damit sie
  -- vererbbare Operationen des Typs Ur_Float werden:

  function Sqrt (X: Ur_Float) return Ur_Float
                renames Ur_Mathematische_Bibliothek.Sqrt;
  function Sin  (X: Ur_Float) return Ur_Float
                renames Ur_Mathematische_Bibliothek.Sin;
  function SinD (X: Ur_Float) return Ur_Float
                renames Ur_Mathematische_Bibliothek.SinD;

  -- Herausnehmen der nicht-vererbbaren Objekte:

  PI: constant := Ur_Mathematische_Bibliothek.PI;

  Unzulaessiges_Argument: exception
      renames Ur_Mathematische_Bibliothek.Unzulaessiges_Argument;

  -- Ein Typ ohne mathematische Operationen:

  type Ur_Float_ohne_Mathematik is digits project_defined;

  -- Ein alleinstehender physikalischer Typ ebenfalls ohne Mathematik:

  package Ur_Dimension is
     new Physikalische_Dimension (Ur_Float_ohne_Mathematik);

  type Ur_Einheit is new Ur_Dimension.Einheit;

end Urknall;

Die Genauigkeit der Grundtypen Ur_Float und Ur_Float_ohne_Mathematik wird natürlich von den Bedürfnissen des Projekts bestimmt. Beide können auch unterschiedliche Genauigkeiten haben, oder es kann mehrere unterschiedlich genaue Typen geben sowohl mit mathematischen Funktionen als auch ohne sie, in Gleit- wie auch Fließkommadarstellung. All dies führt nur zu weiteren Überladungen der Funktionen, hat jedoch keinen Einfluß auf das vorgestellte Verfahren.

Das Paket Universum:

Aus dem Urknall entwickelt sich das Universum, das alle benötigten Typen, Operationen, Ausnahmen und Konstanten zur Verfügung stellt. Der Urknall selbst ist nicht mehr zu sehen, das heißt, es ist streng verboten, dieses Paket in irgendeiner weiteren with-Liste zu verwenden. Leider gibt es in Ada keine syntaktische Regel, die Einhaltung dieses Verbots zu erzwingen. Hier hilft nur Disziplin der Programmierer und Code-Inspektion durch die Verantwortlichen.

Es wird folgende Übereinkunft getroffen:

Im Rahmen dieses Projekts werden alle Berechnungen innerhalb eines vorgegebenen kohärenten Einheitensystems durchgeführt.

Hier sei das SI-System gewählt. Hierfür wird ein Ada-Typ definiert, für alle häufig benötigten Einheiten gibt es Untertypen:

type    SI_Einheit        is new Urknall.Ur_Float;
subtype Meter             is     SI_Einheit;
subtype Sekunde           is     SI_Einheit;
subtype Meter_pro_Sekunde is     SI_Einheit;
subtype Newton            is     SI_Einheit;  -- kg*m/s**2
subtype Radiant           is     SI_Einheit;  -- 1
subtype Dimensionslos     is     SI_Einheit;  -- 1
...

Auf diese Weise kann bei der Variablendefinition semantische Information mitgeteilt werden.

g: Meter_pro_Sekunde_2 := 9.81;
t: Sekunde             := 10.0;
s: Meter               := 0.5 * g * t**2;

Der Programmierer ist allein verantwortlich, daß diese Formel physikalisch korrekt ist, es findet keine Typprüfung statt!

Wären die obigen Untertypen statt dessen eigene Typen, würde die Zuweisung mit Typumwandlungen verunstaltet, ohne das die Richtigkeit garantiert wird. Wo also bleibt der Vorteil?

s := 0.5 * Meter(g) * Meter(t)**2;

Der Untertyp Dimensionslos soll nur für wirklich dimensionslose Größen, das heißt für Größen mit der Dimension 1, verwendet werden:

Alpha: constant Dimensionslos :=
         e**2 / (4.0 * PI * eps_0 * h_quer * c);

Radiant und Dimensionslos sind zwar physikalisch dasselbe, doch wird durch den Namen Radiant ein Winkelmaß ausgedrückt.

Der Typ SI_Einheit selbst soll dort verwendet werden, wo die Variable zwar eine von 1 verschiedene Dimension besitzt, jedoch kein eigener Untertyp namentlich definiert worden ist:

Dioptrie: SI_Einheit;  -- 1/m

Als eine zwingende Konvention könnte eingeführt werden, daß in diesen Fällen stets so wie im Beispiel die Dimension im Kommentar anzugeben ist.

Nun werden möglicherweise für die Ein- und Ausgabe andere Einheiten benötigt. Alle diese anderen Einheiten werden durch eigene Typen (beziehungsweise durch Untertypen im Falle von anderen kohärenten Einheitensystemen) dargestellt, und es gibt Umwandlungsfunktionen für jede Einheit in die entsprechende SI-Einheit und umgekehrt.

type Kilometer is new Urknall.Ur_Einheit;

function Kilometer_zu_Meter (km: Kilometer) return Meter;
function Meter_zu_Kilometer ( m: Meter    ) return Kilometer;

type    Nautische_Einheit is new Urknall.Ur_Float_ohne_Mathematik;
subtype Nautische_Meile   is     Nautische_Einheit;
subtype Stunde            is     Nautische_Einheit;
subtype Knoten            is     Nautische_Einheit;  -- NMi/h

function Nautische_Meile_zu_Meter (NMi: Nautische_Meile)
           return Meter;
function Meter_zu_Nautische_Meile (m  : Meter)
           return Nautische_Meile;

function Stunde_zu_Sekunde (h: Stunde ) return Sekunde;
function Sekunde_zu_Stunde (s: Sekunde) return Stunde;

function Knoten_zu_Meter_pro_Sekunde (kts: Knoten)
           return Meter_pro_Sekunde;
function Meter_pro_Sekunde_zu_Knoten (mps: Meter_pro_Sekunde)
           return Knoten;

Es wurden absichtlich Typen ohne Mathematik gewählt, denn Berechnungen sollen ja nicht in diesen Typen stattfinden. Auf diese Weise garantiert das Typkonzept für die kritischen Fälle der Einheitenmischung die korrekte Wahl eines kohärenten Systems.

Die Typumwandlung im Ada-Stil

Kts: Knoten            := 10.0;
v  : Meter_pro_Sekunde := Meter_pro_Sekunde (Kts);  -- Unsinn

ist ebenfalls streng verboten.

Leider existiert wiederum kein syntaktisches Mittel, dies zu verhindern.

Eine Unzulänglichkeit der von Ur_Einheit abgeleiteten Typen ist, daß die Funktion Masszahl den Typ Ur_Float_ohne_Mathematik zurückliefert, und auch die unären Additionsoperatoren benötigen ihn. Dies führt dazu, daß leider auch dieser Typ mit allen seinen Operatoren sichtbar gemacht werden muß. (Dies ist die Hintergrundstrahlung vom Urknall.)

Nun zu den mathematischen Funktionen. Für Sqrt ist nichts weiter auszuführen. Mit dem Typ SI_Einheit ist auch Sqrt bekannt und

t := Sqrt (2.0 * s / g);

übersetzbar. Komplizierter ist der Fall für trigonometrische Funktionen. Dies wird im folgenden an Hand des Sinus ausgeführt. Das Vorgehen für den Cosinus und Tangens und die Umkehrfunktionen ist dann offensichtlich.

Es sollen die beiden folgenden Gleichungen darstellbar sein

x = x0 sin (phi)
x = x0 sin (2 PI Ny t)

wobei phi in Grad, Ny jedoch in Hertz gemessen wird, das Argument in der zweiten Zeile also dimensionslos ist (beziehungsweise die Dimension Radiant besitzt).

Der zweite Fall ist relativ einfach. Wie im Falle von Sqrt ist auch hier mit dem Typ SI_Einheit die passende Funktion Sin bekannt, so daß man

X := X0 * Sin (2.0 * PI * Ny * T);

schreiben kann. Störend wirkt nur, daß auch die verkehrte Funktion SinD, die (2 PI Ny t) in Grad interpretiert, ebenfalls eine Operation des Typs SI_Einheit ist. Es gibt mehrere Möglichkeiten, damit umzugehen. Erstens könnte man sagen, das störe nicht, vielleicht ist diese Funktion sogar in gewissen Fällen nützlich; sie bleibt absichtlich erhalten. Zweitens kann man sie zerstören, indem man die vererbte und damit implizit deklarierte Funktion durch eine neue gleichnamige und explizit deklarierte Funktion verdeckt, die beim Aufruf eine Ausnahme Unzulaessiger_Funktionsaufruf auslöst. Die dritte Möglichkeit verdeckt ebenfalls die unerwünschte Funktion, bildet sie jedoch durch eine Umbenennung auf Sin ab, so daß Sin und SinD identisch sind.

Zur Behandlung von Winkeln im Gradmaß wird wie für die Nicht-SI-Einheiten ein neuer Typ eingeführt, diesmal jedoch durch Ableitung vom Typ mit den mathematischen Funktionen:

type Grad is new Urknall.Ur_Float;

Jetzt existieren implizit deklarierte Funktionen

function Sin  (X: Grad) return Grad;
function SinD (X: Grad) return Grad;

von denen nur die zweite richtig ist. Auch hier gibt es die drei oben genannten Möglichkeiten, mit diesen Funktionen zu leben, doch erscheint nur die letzte sinnvoll, denn die Information, in Grad zu rechnen, wird ja schon vom Argumententyp vermittelt, so daß später bei der Anwendung sehr leicht vergessen wird, daß nur die Funktion mit dem Namen SinD zum gewünschten Ergebnis führt. Wenn schon Grad als eigener Typ definiert wird, muß auch konsequenterweise Sin in Grad arbeiten. Somit wird also Sin durch Umbenennung auf SinD abgebildet. Damit Konsistenz besteht innerhalb der Vielzahl überladener trigonometrischer Funktionen, die auf diese Weise entsteht, bleibt eigentlich auch im Falle der Funktionen mit Radiant als Argumententyp jetzt nur noch die dritte Möglichkeit, so daß das Ergebnis lautet:

Die Art der trigonometrischen Funktion (Grad beziehungsweise Radiant) wird allein durch den Argumententyp bestimmt.

Die Funktionsnamen Sin und SinD haben stets identische Wirkung. Vorzugsweise wird man so damit umgehen, stets nur Sin aufzurufen und die Verwendung des alternativen Namens SinD ganz zu verbieten. (In der Praxis hat sich gezeigt, daß die Existenz der Funktion SinD sehr schnell vergessen wird.)

Allerdings ist das gesteckte Ziel noch nicht ganz erreicht, denn das Ergebnis von Sin und SinD ist immer noch Grad, gebraucht wird jedoch SI_Einheit. Da diese Funktionen weiter nicht stören, bleiben sie erhalten; es wird nur zusätzlich ein neuer Satz Funktionen mit dem gewünschten Parameter- und Ergebnisprofil definiert:

function Sin  (X: Grad) return SI_Einheit;
function SinD (X: Grad) return SI_Einheit renames Sin;

(Die zweite Deklaration ist nur den Konsistenz halber mit aufgenommen worden, damit immer beide Namen gleichbedeutend verwendet werden können.) Damit ist das Ziel der Übersetzbarkeit von

X := X0 * SIN (Phi);

erreicht. Der Preis, der dafür zu zahlen ist, nämlich eine Vielzahl von überladenen Funktionen, erscheint angemessen, da sie alle eindeutig durch ihr Profil

function F (X: Radiant) return SI_Einheit;
function F (X: Grad   ) return SI_Einheit;
function F (X: Grad   ) return Grad;

function F_1 (X: SI_Einheit) return Radiant;
function F_1 (X: SI_Einheit) return Grad;
function F_1 (X: Grad      ) return Grad;

unterschieden sind (F_1 bezeichnet die Umkehrfunktion).

Eine Anmerkung zur direkten Sichtbarkeit aller in Universum enthaltenen Definitionen ist noch zu machen. In vielen Projekten ist aus gutem Grund die Verwendung von use-Klauseln wenn nicht strikt verboten, so doch zumindest nur in äußerst eingeschränktem Maße erlaubt. Da das Paket Universum jedoch, wie schon der Name sagt, ganz grundlegende Definitionen enthält, auf denen alles andere aufbaut, steht es gewissermaßen auf gleichem Niveau wie das vordefinierte Ada-Paket Standard, das das Ada-"Universum" darstellt. Aus diesem Grund sollte die use-Klausel hier erlaubt werden. Man könnte sogar so weit gehen zu fordern, daß Universum stets nur in Kombination einer with- und einer use-Klausel zu verwenden ist.

Siehe Auszüge aus der Spezifikation und dem Rumpf des Pakets Universum.

Zusammenfassung:

Zusammenfassend läßt sich sagen, daß sich diese Pakete bewährt haben trotz ihrer scheinbaren Komplexität durch die Vielzahl der überladenen Funktionen, besonders für sphärische Trigonometrie zur Umrechnung zwischen Geographischen Koordinaten und UTM-Koordinaten. Es ergeben sich folgende Vorteile:

Es gilt folgende Einschränkung der Leistung:


Definitionen der SI-Einheiten, historische Überblicke und weitere Informationen zum Thema sind beim National Institute of Standards and Technology (NIST) zu finden.


Ich möchte Herrn Holz von der Universität der Bundeswehr in Neubiberg danken für seinen Literaturhinweis.

Literatur:

[1] Do-While Jones, Dimensional Data Types,
     Dr. Dobb's Journal of Software Tools, 50-62, May 1987

[2] Do-While Jones, Ada in Action, John Wiley & Sons, Inc., 1989

[3] N. H. Gehani, Ada's Derived Types and Units of Measure,
     Software - Practice and Experience, Vol. 15(6), 555-569, June 1985

[4] H. J. Schneider, Physikalische Maßeinheiten und das Typkonzept moderner
     Programmiersprachen, Informatik-Spektrum (1988) 11: 256-263


Dieser Artikel ist erschienen in Ada Aktuell 1.1 (März 1993), Mitteilungen der Fachgruppe Ada-Deutschland in der Gesellschaft für Informatik.
Es gibt auch eine englische Version hiervon, die allerdings etwas älter ist und daher nicht so ausführlich.


Eine fertig benutzbare Version kann hier heruntergeladen werden.


Ada
Inhaltsverzeichnis