Konzepte für Programmiersprachen
Wer kennt es nicht: da programmiert man ein paar Jährchen rum und plötzlich wird man von dem Größenwahn überwältigt, eine neue, eigene Programmiersprache zu entwickeln.
Im Folgenden habe ich ein paar Ideen für – mehr oder weniger – neue Konzepte für eine solche Programmiersprache aufgeschrieben, an der ich vllt. irgendwann mal arbeiten werde.
Tuples und Tuple Types
Tuples sind ein Datentyp, der quasi beliebige zusammengesetzte Daten speichern kann. Beispielsweise könnte man einen Menschen so beschreiben:
me := (name: "Paul", age: 19, gender: 'M);
Wir können also dictionary-Einträge in einem Tuple haben. Allerdings können wir auch Tuple als einfache Listen verwenden, z. B. so:
primes := (2, 3, 5, 7);
Es kommt sogar noch besser: Wir können beide Formate auch kombinieren, z. B. für folgendes:
lotto := (1, 4, 9, 16, 25, super: 36);
Häufig nutzt man in einer Anwendung viele gleichartige Tuple. Hier gibt es die Möglichkeit der Tuple Types (ein wenig ähnlich den struct
s in C), einem Meta-Datentyp, der leichter als eine eigene Klasse ist. Das o.g. Beispiel können wir also wie folgt formalisieren:
Human := ((String: name, Int: age, Tok: gender));
Nun könnten wir einen neuen Human erzeugen, ähnlich wie oben:
me := Human(name: "Paul", age: 19, gender: 'M);
(Hierbei nutze ich die ganze Zeit eine weitere nette Funktion: Implicit Declaration. Anstatt für jede Variable den Typ explizit anzugeben, wird diese beim ersten Mal mit dem :=
Operator und nicht mit dem gewöhnlichen =
Operator zugewiesen, was die automatisierte Typbestimmung beinhaltet.)
Custom Operators
Okay wir kennen alle die Standardoperatoren +
, -
, *
, /
, %
, **
, &&
, ||
, sowie die Vergleichsoperatoren (ALC-Ops, Algorithmic, Logical, Comparative Operators).
Doch hier kommt die neue Idee: Custom Operators.
Mit \name expr
wird der einseitige Custom Op name
auf dem Ausdruck ausgeführt, was dem Funktionsaufruf (expr).op_name()
entspricht.
Mit expr0 \name expr1
wird der beidseitige Custom Op name
auf dem Ausdruck ausgeführt, was dem Funktionsaufruf (expr0).op_name(expr1)
entspricht.
Da die Standardoperatoren auch über magische Methoden definiert wurden, kann man das auch entsprechend nutzen, also z. B. 2 \add 3
statt 2+3
. Ist aber unübersichtlich und sollte in den meisten Fällen gelassen werden. ^^
At-Expressions
Der Ausdruck @expr
gibt null
zurück; der Ausdruck @expr0 expr1
den Wert des Ausdrucks expr1
. In beiden Fällen wird der Ausdruck unmittelbar hinter dem At-Zeichen nicht ausgeführt.
Das ist nützlich für folgendes:
-
Dokumentation, z. B. durch ein
@param("arg1", "description")
vor einer Funktions- oder Klassendefinition, -
"At-Off", d.h. das gezielte Ausschalten eines einzelnen Ausdrucks; anders als Kommentare erreicht man zielsicher einen einzelnen Ausdruck, wobei der Operator aus naheliegenden Gründen eine hohe Bindung hat, sodass häufig Klammern erforderlich sein werden, wenn man nicht absolutes Chaos erzeugen will.
Expression-only Functions
Niemand macht sich gerne die Mühe, für eine einfache Funktion einen ganzen Block mit Return-Befehl zu schreiben. Dafür gibt es eine einfache Lösung: Expression-only Functions.
Damit schreibt man nicht folgendes:
fn add2 (Int: number) -> Int {
return number + 2;
};
Stattdessen kann man folgendes schreiben:
fn add2 (Int: number) -> Int
=> number + 2;
Das ist doch schon gleich viel lesbarer. :)
Partial Application
Funktionen mit einem Mal ausführen ist nun aber auch wirklich langweilig! Daher gibt es einen Partial Application-Operator. Damit erhält man eine neue Funktion mit leicht veränderter Typensignatur, denn einige Parameter wurden bereits mit festen Argumenten besetzt.
Ein Beispiel:
Die folgenden beiden Code-Schnipsel sind vom Ergebnis her äquivalent.
add(1, 2)
add<>(Int: left, 2)(1)
Das sieht jetzt erstmal vllt. noch unspektakulär aus, aber man kann ja das Ergebnis von add<>(Int: left, 2)
zwischenspeichern, an andere Funktionen übergeben u.v.m., sodass dies ein recht mächtiges Werkzeug sein dürfte.
Literal Lambdas
Sehr nützlich sind sicher auch Literal Lambdas. Wieso viel bei der Definition von Lambdas viel drum herum reden, wenn es auch einfach geht? Der Ausdruck println("hello, world")
beschreibt eine Funktion, die die println
Funktion aufruft.
Ausführen kann man das ganze auch ganz einfach: `println("hello, world")`()
Variablen in dem Lambda-Ausdruck werden in dem Kontext ausgewertet, indem die Lambda-Funktion ausgeführt wird. Möchte man Variablen verwenden, d.h. nicht von einer bestimmten Benennung der lokalen Variablen abhängig sein, nutzt man den Diamantenoperator von eben:
func := `2 * x + 3`<>(Int: x)
println(func(-1))