[Sprachentwurf] fun pl
Folgende Punkte waren uns besonders wichtig:
- Die Vereinheitlichung von Methoden und freien Funktionen: Die Verankerung von Methoden in Klassen erschien uns überflüssig; sie führt offensichtlich zu Inkonsistenz beim Aufruf, wenn fremde Module zusätzliche Operationen auf Objekten implementieren. Diese könnten zwar umständlich nachträglich in die Klasse injiziert werden, dies entspricht jedoch in keinster Weise der Kapselung als Grundidee hinter Klassen. Tatsächlich erscheint die traditionelle Umsetzung von Klassen aus meiner heutigen Sicht kein gutes Mittel der Kapselung, weil sie vorschreibt, welche Operationen aus Implementierungsgründen objekteigen sind, und welche sich alleine auf der Objektschnittstelle implementieren lassen.
- Ein nicht textbasiertes Modulsystem: Hierzu muss ich vermutlich nicht viel sagen, dass Text-#includes suboptimal bis nicht zu gebrauchen sind, hat wohl jeder hier zur Genüge selbst erfahren.
- Verbesserte Templates: Templates in C++ sind umständlich zu schreiben und erlauben ohne vollständige Instantiierung nur eine sehr eingeschränkte statische Prüfung. Concepts retten uns hoffentlich mit dem nächsten C++-Standard aus dieser misslichen Lage.
- Einfache Übersetzbarkeit: Die Grammatik haben wir nie formalisiert, sie existiert nur implizit im Parser-Prototyp. Das geht, weil zu jedem Zeitpunkt klar ist, welches Token folgen muss. Damit einher geht auch der nächste Punkt ...
- Trennung von Typ- und Werte-Kontexten: Um im Rahmen eines bequemen Modulsystems ohne jegliches Vorwissen durch Deklarationen vollständig übersetzen zu können, ist an jeder Stelle klar, ob ein Typ oder ein Wert gemeint ist. Damit erübrigen sich auch Mehrdeutigkeiten bei gleichnamigem Objekt und Objekttyp.
- Deterministische logische Objektkonstruktion und -destruktion: RAII funktioniert in C++ so gut, dass wir dem erstmal nichts hinzuzufügen hatten.
- Ausnahmebehandlung ebenso, leicht ergänzt.
Code: Alles auswählen
def a = 2 // Konstante vom Typ int
def b : int = 2 // Konstante vom Typ int mit expliziter Typangabe
var c : string // String-Variable
var d : float = uninitialized // Uninitialisierte float-Variable
d = b -> float // b zu float gecastet
Funktionen
Im Sinne der einfachen Übersetzbarkeit ist es auch, dass Funktionen ein eigenes Schlüsselwort erhalten, welches sowohl zur Definition als auch als Typkonstruktor dient.
Code: Alles auswählen
// Volle Typisierung. Parametervariablen sind standardmäßig konstant.
fun double(n : int) -> int
{
return 2 * n;
}
// Automatische Bestimmung des Rückgabetyps ist in der Regel trivial, also Rückgabetyp optional.
fun double(n : int)
{
return 2 * n;
}
// Wenn uns der Typ nicht interessiert, lassen wir ihn weg, und erhalten ein implizites Template.
fun double(n)
{
return 2 * n;
}
Code: Alles auswählen
// Kurze Funktionen lassen sich schöner schreiben.
fun double(n) = 2 * n
// Natürlich auch gültig:
fun double(n : int) = 2 * n
fun double(n : int) -> int = 2 * n
Zur Definition von Funktionstypen nutzen wir ganz intuitiv fun als Typkonstruktor:
Code: Alles auswählen
fun updateProgressBar(text : string, progress : double) { ... }
def callback = updateProgressBar
// callback hat Typ fun (string, double) bzw. fun (string, double) -> void
Klassen und Objekte
Da unsere Klassen im Kern nur Daten enthalten, haben wir uns für das C-Keyword struct entschieden:
Code: Alles auswählen
struct name
{
first : string // Implizit: def, auch var möglich.
last : string
}
Code: Alles auswählen
fun +name(first : string, last : string) -> name
{
// Konstruktion, keine Zuweisung, deshalb Reihenfolge erzwungen.
// Lokale Variablen an jeder Stelle möglich, Manipulation von Attributen nach erster Konstruktion derselben.
this.first = first;
this.last = last;
}
Code: Alles auswählen
// '+' vor Parameter erzwingt Kopie statt konstanter Referenz (by Value, in C++ also string statt const string &)
fun +name(+first : string, +last : string) -> name
{
// ~ analog zu std::move() in Anlehnung an die Destruktorsyntax, weil first und last danach höchst wahrscheinlich ungültig sein werden.
this.first = ~first;
this.last = ~last;
}
Der Vollständigkeit halber noch die Destruktion, die aber logischerweise genau wie in C++ fast immer ohne expliziten Destruktor korrekt laufen sollte:
Code: Alles auswählen
fun ~name()
{
// Tue nichts, Destruktion der Attribute läuft automatisch.
// Dieser Destruktor würde natürlich in einem echten Programm gar nicht erst explizit definiert.
}
Code: Alles auswählen
// & macht v zu manipulierbarer Referenz.
// Instantiierung von vector Template durch Instantiierungsoperator: Template => Template-Argumente
// Implizites Template, Syntax Java-inspiriert: ? leitet inplace die Definition eines Typparameters ein, der im Anschluss verwendet werden darf.
fun append(&v : vector => ?E, e : E)
{
// Verschiebe e ans Ende von v
}
// Benutzung:
var v : vector => int
append(v, 2)
Code: Alles auswählen
fun append(ref v : vector => typename E, copy e : E)
{
// Verschiebe e ans Ende von v
}