Viele Sprachen besitzen Möglichkeiten, um mit Spracheigenschaften Listen und Arrays zu verwalten. Die Einfachheit, mit der Listen in Prolog gehandhabt werden, gefiel mir besonders gut, es stellt sich also die Frage, ob eine so einfache Syntax in eine C-ähnliche Sprache zu retten ist.
Die Strukturierung erfolgt in C durch Klammernpaare, davon finden sich vier Paare auf der Tastatur.
| Klammerung | Symbol | Bedeutung |
|---|---|---|
| geschweifte Klammerung | {} | Strukturierung von Algorithmen, Klassen, Enumerations, Beschreibung von unbenannten Arrays oder Strukturen |
| eckige Klammerung | [] | Operator zum Zugriff auf Arrays |
| spitze Klammerung | <> | Typübergabe in C++ bei Templates, Symbole zum Vergleich auf Größer bzw. Kleiner |
| runde Klammerung | ( ) | Operator, um Funktionsaufrufe zu kennzeichnen, als End-Markierung für Schlüsselwörter, Prioritätenvorgabe |
Innerhalb der Klammernpaare werden die einzelnen Ausdruckselemente unterschieden durch zwei Trennsymbole: Das Semikolon unterbricht Ausdrücke und beginnt einen neuen Ausdruck. Eine schwächere Trennung nimmt das Komma hingegen vor: Es trennt Teilausdrücke, beginnt jedoch keinen neuen Ausdruck. Der Rückgabewert des Gesamtausdrucks entspricht dem Ausdruck, der vor dem Komma steht. Es gibt keine Rückgabewerte für die nachfolgenden Ausdrücke:
Beispiel:
float f; f = 1,2; // Syntaktisch korrekt; float wird 1.0 zugewiesen. // Der Ausdruck 1,2 liefert nur den Wert // vor dem Komma zurück. int a = 47, b = 11; if( a == 47 ) a = 8, b=15; // a wird 8, b wird 15 zugewiesen,
Die Verzweigung ist ohne Klammern syntaktisch korrekt, da es sich nur um einen einzigen Ausdruck handelt und eine einzelne Anweisung darf in C ohne geschweifte Klammern verwendet werden.
Die Frage ist nun, wie man einen Umstieg von diesen Sprachen möglichst unkompliziert gestaltet, in dem man die Strukturierung möglichst nicht verändert, aber dem zum Trotz mehr Funktionalität in die Strukturierung einbettet. Es werden neue Definitionen für die Klammerung benötigt, deren Semantik eindeutiger und einfacher ist und deren Beschreibung die vorhandenen Fälle, für die das jeweilige Klammernpaar derzeit benutzt wird, möglichst gut abdeckt. Dazu benötigen wir einen flexibleren Datentyp, den der Compiler nach belieben interpretieren darf: Als Liste, als Array oder auch als Struktur. Dieser Datentyp kann nur als Beschreibung einem anderen Datentyp zugewiesen werden. Es handelt sich also um eine interne Aufzählung, die entsprechend der Erwartungshaltung durch den abhängigen Operator als Array, Liste oder Struktur interpretiert wird. Diesen kurzlebigen, noch unspezifizierten Datentyp werde ich im fortlaufenden Text als „Quantum“ bezeichnen.
| Klammerung | Symbol | Bedeutung |
|---|---|---|
| geschweifte Klammerung | {} | Beschreibt ein Array oder Menge, als Postfix-Operator direkter Array-Zugriff |
| eckige Klammerung | [] | beschreibt eine Liste, als Postfix-Operator gesteuerter Zugriff auf Listen oder Array |
| spitze Klammerung | <> | Vergleich auf Größer und Kleiner |
| runde Klammerung | ( ) | Beschreibt ein Quantum |
Dazu passend wird das Semikolon als Operator definiert, der Ausdrücke unabhängig voneinander hält (Oder-Verknüpfung), Der Komma-Operator erklärt eine Abhängigkeit der Ausdrucke zueinander (Und-Verknüpfung). Eine Funktion ist damit eine Folge von unabhängigen Anweisungen innerhalb eines Arrays. Beispiele:
int a = 4; // a muss 4 sein, entspricht C-Syntax, das Quantum // besteht aus einer Bedingung, die erfüllt sein muss. if( a == 4 ) { … } // a muss 4 oder 5 oder 6 sein, entspricht C-Syntax, das Quantum // aus einer einer Bedingung if( a == 4 || a == 5 || a == 6 ) { … } // a ist integer, { 4, 5, 6 } ist ein Array, Typverletzung if( a == ( 4; 5; 6 ) ) { … } // a muss 4, 5 und 6 sein, Resultat immer falsch // ( a == 4 && a ==5 && a == 6 ) if( a & ( 4, 5, 6 ) ) { … } // Es existiert eine Schnittmenge zwischen a und allen Werten // der Menge ( a & 4 && a & 5 && a & 6 ) if( a & ( 4, 5, 6 ) ) { … } // a, b und c müssen 4 sein. if( {a, b, c} == 4 ) { … }
Operatoren werden mehrfach ausgeführt, wenn sie auf ein Quantum treffen und entsprechend miteinander verknüpft. Dies gilt auch für nicht vergleichende Operatoren:
long a = 2; a *= { 3, 4, 5 }; // Multiplikation mit einer Menge von Zahlen<code> Dies entspricht dem Ergebnis von: <code c> a *= 3; a *= 4; a *= 5;
Die Operation wird eine nach der anderen seriell abgearbeitet, das Ergebnis des letzten Operators wird zurückgegeben, hier: 120. In C wird das Ergebnis des ersten Operators zurückgegeben.
long a = 2;
a *= { 3; 4; 5 }; // Multiplikation mit einem Array
entspricht:
a = { a*3; a*4; a*5 }
<note>DAS IST QUATSCH</note>
Hier wird die Berechnung unabhängig voneinander für alle Array-Mitglieder durchgeführt. Es wird gewissermaßen ein Skalar mit einem Vektor multipliziert. Ergebnis und Rückgabewert: { a*3; a*4; a*5 }. In diesem Fall wird mit dem *= Operator die Zuweisung wieder auf a gesetzt, mit dem Resultat, dass das Skalar mit einem Vektor gleichgesetzt wird: Es findet eine Typ-Verletzung statt, der Sourcecode ist damit semantisch falsch. Hierfür wäre ein zur Laufzeit dynamischer Datentyp („object“) erforderlich, der zuerst als Skalar dient und anschließend den Datentyp Array aus 3 long-Werten besitzen würde, anderenfalls muss man sich auf den gewünschten Faktor beschränken:
a *= { 3, 4, 5 }[ 1 ]; // entspricht a*=4;
Interessant sind derartige Konstruktionen mit dem '.'-Operator.
MyClass Var1, Var2;
Var1.
{
Function1();
Function2();
}
Dies entspricht dem with()-Konstrukt, dass in JavaScript oder C# verwendet wird und in C++ noch gar nicht realisiert wurde. Dieser Ausdruck besitzt jedoch im Gegensatz zu dem with-Konstrukt auch eine Rückgabe-Struktur, entsprechend der beiden Rückgabewerte von Funktion1 und Funktion2.
Eine andere Möglichkeit stellt folgender Ausdruck dar:
( Var1, Var2 ).Function1();
Diese Zusammenfassung ruft für jedes Element des Quantums Function1 auf und liefert den Funktionsrückgabewert für Var2 zurück. Folgender Ausdruck liefert ein Array mit den Funktionsrückgabewerten zurück:
{ Var1; Var2 }.Function1();
Der Ausdruck
{ Var1; Var2 }.
{
Function1();
Function2();
}
erzeugt dementsprechend ein zweidimensionales Array, in dem sich für Var1 und Var2 die Rückgabewerte für Funktion1() und Function2() finden. Da das Ergebnis des „.“-Operator hier jedoch nicht weiter verwendet wird, gibt die Erwartungshaltung vor, dass keine Rückgabe-Arrays erzeugt werden müssen.
Bisher kamen die Listen nicht mehr zur Sprache: Ihre Verwendung und ihr Rückgabewert ist entsprechend den Arrays, sie werden lediglich über eckige Klammern beschrieben:
[ Var1; Var2 ].Funktion2();
Hierzu lediglich der Hinweis, dass der Rückgabewert keine Liste darstellt, sondern ein Quantum. Es steht fest, wie groß die Liste ist, entsprechend groß ist das Rückgabe-Quantum. Das Quantum kann anschließend auf ein Array oder eine Liste zugewiesen werden. Die Erwartungshaltung gibt ja den Datentyp, also Array oder Liste, vor. In dem genannten Beispiel wird das Ergebnis allerdings nicht abgefragt, muss also auch nicht erzeugt werden.