Ich habe die abkürzende Arc-Syntax für eine Funktion mit einem Argument mit der Version in Lists on a stack verglichen. In Arc wäre z.B. dieser Code mit 8 Token normal:
[a (b X _)]
Das ist eine Funktion mit einem Argument (das implizit "`_`" heißt), die `b` mit den Argumenten `X` und `_` aufruft und das Argebnis als Argument für den Aufruf von `a` nutzt. Das gleiche in Lists on a Stack:
(a b X)
Das sind nur 5 Token. Da Kürze ein fundamentales Ziel von Arc ist, könnte man nun behaupten, dass ein ständig implizit genutzter Stack sehr gut geeignet ist, um das Ziel von Arc zu erreichen. Ich habe damit nur ein Problem:
Ich finde, dass bei der Arc-Version schneller erkennbar ist, was der Code macht.
Der Grund dafür ist, dass die Arc-Syntax der Intention des Codes entspricht. Bei der Lists on a stack-Version ist nicht ersichtlich, ob zum Beispiel `b` vielleicht eine Funktion ohne Argumente ist und `a` zwei Argumente annimmt (den Rückgabewert von `b` sowie den Inhalt der Variablen `X`). Ob die gezeigte Funktion überhaupt Argumente vom Stack liest, ist ebenfalls nicht ersichtlich.
Klar, mit vernünftigen Bezeichnern und im Kontext wäre das eher ersichtlich. Und klar, man kann ja in Lists on a stack ein Argument benennen, wo dies die Lesbarkeit des Codes verbessert, dadurch wird auch noch etwas klarer, was passiert:
| K (a b X K)
Aber es bleibt dabei immer noch manches unklar (obwohl wir auch hier bereits 8 Token zählen). Das ist wohl einfach der Fluch einer stackbasierten Sprache. Der Schlüsselpunkt bei der Sache ist aber, dass Syntax und Semantik in der Arc-Version sich entsprechen. Lists on a stack kann dies als stackbasierte Sprache nicht erzwingen, da sie zusätzliche Freiheitsgrade bietet, die dem entgegenstehen. Aber es spricht ja nichts dagegen, dem Programmierer die Option zu geben, je nach Fall diese Freiheitsgrade aufzugeben, um die Intention von Code expliziter zu machen, zumal hierbei die Möglichkeit besteht, dies zum Vorbeugen von stackbasierten Bugs zu nutzen. Ich schlage hierfür folgende Syntax vor:
{...code... N}
Code, der so umschlossen ist, soll ganz normal ausgeführt werden, mit einem Unterschied: Die Untergrenze des Stacks wird für die Dauer der Ausführung verschoben, und zwar so, dass nur noch `N` Elemente sichtbar sind. Der entsprechende Code in unserem Beispiel wäre also:
(a {b X 1})
Der Code "`b X`" sieht also genau ein weiteres Stack-Element (eben jenes, das wir oben beispielhaft `K` nannten). Es wird hier also explizit angegeben, wie viel vom Stack der Aufruf von `b` nutzt/verbraucht.
Dadurch kann also bei Bedarf explizit gemacht werden, wie viele Stack-Elemente eine Funktion nutzt. Die Syntax für sich genommen bietet immer noch nicht die gleiche Menge an Informationen wie der betreffende Arc-Code, aber wir haben ein Mittel, um Code klarer zu strukturieren und Fehlern vorzubeugen, ohne dafür die Möglichkeiten eines globalen Stacks aufzugeben.
Wie kann ich dieses Feature nutzen, um Fehlern vorzubeugen? Wenn ich eine Funktion (nennen wir sie `azubi`) an eine Funktion (nennen wir sie `chef`) übergebe, kann ich `azubi` in einem Kontext ausführen lassen, in dem sie nur auf die Stack-Elemente zugreifen kann, die für sie vorgesehen sind, wärend interne Stack-Werte von `chef` dadurch nicht durcheinander gebracht werden können. Wenn man das bei der Implementierung von `map` nutzt, sieht das so aus:
map: | sub # Lst -> New-Lst
(unless (nil? .)
(recons ({sub 1}) recurse^))
Hier wird also `sub` davor geschützt, irrtümlicherweise zu viel vom Stack zu entfernen - allerdings nicht davor, zu viel darauf zurück zu lassen; ein solches Feature wäre aber auch ohne weiteres möglich und müsste so aussehen:
{OUT ...code... IN}
Dabei gibt `IN` an, wie viele Argumente der Code vom Stack lesen darf, und `OUT` bestimmt wieviele Elemente am Ende auf dem Stack liegen müssen. Dann wird bei `map` also genau spezifiziert, welches Verhalten von `sub` erwartet wird:
map: | sub # Lst -> New-Lst
(unless (nil? .)
(recons ({1 sub 1}) recurse^))
In einer auf Performance optimierten Version können diese Prüfungen natürlich alle entfallen. Sie dienen nur dazu, die Intention von Code explizit zu machen und beim Entwickeln auch zu prüfen, dass diese Anforderungen eingehalten werden.