Hommage à Tim Toady

Vorgestellt sei, man müsste eine Programmiersprache schreiben. Dann müsste der Designer auch die eine oder andere Entscheidung treffen, etwa ob es mehrere Wege geben sollte, ein und dasselbe zu formulieren, oder nur genau einen.

In Pocket speichern vorlesen Druckansicht 63 Kommentare lesen
Lesezeit: 13 Min.
Von
  • Michael Wiedeking

Stellen Sie sich einmal vor, Sie müssten eine Programmiersprache schreiben. Oder – um es nicht zu übertreiben – einfach nur Programmierrichtlinien. Dann müssten Sie die eine oder andere Entscheidung treffen, die weitreichende Konsequenzen haben kann. Beispielsweise müssten Sie festlegen, ob es mehrere Wege geben sollte, ein und dasselbe zu formulieren, oder nur genau einen.

Grundsätzlich stellt sich natürlich die Frage, ob die von Perl es tatsächlich richtig machen, mit ihrem TIMTOWTDI: "There is more than one way to do it". Das hat dort auch Kultur, sodass man Dinge so verschieden zum Ausdruck bringen kann, dass man diese auf Anhieb nicht für das Gleiche hält. Der Grund für die verschiedenen syntaktischen Wege könnte an dem Umstand liegen, dass Larry Wall, der Schöpfer von Perl, von Hause aus Linguist ist. Und Perl-Programmierer wissen, dass es unterschiedlicher Mechanismen bedarf, um unterschiedliche Sachverhalte gleichermaßen gut darzustellen.

Das sehen die von Python übrigens etwas anders und haben ihr TIMTOWTDIBSCINABTE, also das TIMTOWTDI, aber mit "but sometimes consistency is not a bad thing either". Bei aller Freiheitsliebe sollen also bestimmte Dinge lieber auf nur eine, konsistente Weise umgesetzt werden. Das spiegelt sich zum Beispiel darin wider, dass der Programmierer dazu gezwungen wird, durch Einrücken Struktur in den Programmtext zu bringen.

Aber wer hat denn nun Recht? Und wie würden Sie sich entscheiden?

Die deutsche Sprache beispielsweise erlaubt es uns, Dinge so verschiedenartig auszudrücken, dass einem alle, die Deutsch lernen müssen, eigentlich Leid tun können. Möchte man zum Beispiel ein Fenster geschlossen wissen, kann man das auf sehr verschiedene Arten bewerkstelligen: Von "Mach doch mal das Fenster zu!" und "Schließ' doch mal das Fenster!" über "Kannst du mal das Fenster schließen?" und "Muss das Fenster auf sein?" bis hin zu "Ganz schön kalt hier!" lässt sich alles mehr oder weniger unmissverständlich als Aufforderung zum Fensterschließen interpretieren.

Nur eine solche Vielseitigkeit erlaubt es überhaupt – aus informationstechnischer Sicht vollkommen überflüssig – Gedichte zu schreiben, die sich reimen oder metrisch vollkommen sind. "Tief gemauert in der Erden, steht die Form in Ton gebrannt" sieht nicht wirklich aus wie gutes Deutsch, ist aber wohl einer der bekanntesten (und am meisten parodierten) Gedichtsanfänge überhaupt. Das möchte man doch als Domänenexperte für Gedichte auf keinen Fall missen, setzt aber ein Grundverständnis von Deutsch voraus, das einem Anfänger dann doch mitunter Probleme bereitet.

Bekanntermaßen sind auch Compiler nicht die "hellsten" Dinge auf Erden, und so ist es nur verständlich, dass man mit einer überschaubaren Grammatik eine verhältnismäßig einfache Sprache zu definieren versucht, mit der sich alle anvisierten Programme beschreiben lassen. Neben der Syntax bedarf es dann noch einer Semantik, die klärt, was denn genau die einzelnen Sprachelemente für die auszuführende Maschine bedeuten.

So bekommen wir mit einer Programmiersprache in der Regel ein if, ein while und diverse andere Konstrukte geboten, die unterschiedliche Zwecke erfüllen. Meist gibt es aber nur eine einzige Form eines if, und alle scheinen damit zufrieden.

if (a < b) {
doThis()
}

In Perl allerdings gibt es noch ein unless, mit dem man genau den gegenteiligen Fall beschreiben kann.

unless (a >= b) {
doThis()
}

Sprachlich gesehen ist das überhaupt kein Problem, denn ein "wenn nicht" ist gar nicht so unüblich; programmtechnisch aber schon, wenn man sich jahrelang darin geübt hat, andersherum zu denken. Und doch gibt es Problemstellungen, die sich damit besser beschreiben werden lassen.

In Perl kann man übrigens diese Bedingungen auch hinten anstellen, wie wir das auch im Deutschen mit "wenn" beziehungsweise "wenn nicht" oder "es sei denn" gelegentlich tun:

doThis() if (a < b)
doThis() unless (a >= b)

Das ist eine völlig andere Vorgehens- und Denkweise und fordert jemandem, der das noch nicht kennt, ein bisschen zusätzliche Denkleistung ab. Jetzt könnte man geneigt sein – so man das Nachstellen an und für sich nicht schon in Frage stellt – nur das if zuzulassen, denn das kennt man schließlich. Dem würde ich auch grundsätzlich zustimmen, wenn es sich bei den beiden Konstrukten if und unless nicht um etwas völlig Verschiedenes handeln würde.

Das Problem ist hier die Konnotation, die Bedeutung, die neben der eigentlichen mitschwingt. Der Duden erwähnt in diesem Zusammenhang den Mond, der ohne Nebenbedeutung – also denotativ – als "Erdtrabant, der durch das von ihm reflektierte Sonnenlicht oft die Nächte erhellt", beschrieben wird. Konnotativ schwingt beim heillosen Romantiker dann aber noch die laue Sommernacht mit, in der die Angebetete mit Liebesgedichten angeschmachtet wird.

So ist eine for-Schleife auch nicht das Gleiche wie eine while-Schleife. Derweil Letztere nur eine Abbruchbedingung hat und keine Vorschriften mitschwingen, wie es zu dem prüfenden Zustand kommt, sagt eine for-Schleife – wie ihrem Namen leicht zu entnehmen ist – relativ klar, für was der Schleifenrumpf wiederholt wird. Dementsprechend halte ich es für ein Sakrileg, in der for-Schleife die Schleifenvariable im Rumpf zu manipulieren.

Niklaus Wirth hat bei der Definition von Oberon auch geglaubt, er könne auf die for-Schleife, die es sowohl in seinem Pascal als auch in seinem Modula gegeben hat, verzichten. Das sahen die Anwender allerdings anders, und so wurde die for-Schleife nachträglich mit Oberon-2 doch noch eingeführt.

In Java und C# hat sich im Zusammenhang mit ungültigen Parametern, die zu einer IllegalArgumentException führen, das Wächter-Idiom eingebürgert.

if (x < 0) {
throw new IllegalArgumentException("Es muss x ≥ 0 gelten");
}

Wäre es hier nicht klarer, wenn ich den gewünschten Fall nicht negieren müsste?

unless (x >= 0) {
throw new IllegalArgumentException("Es muss x ≥ 0 gelten");
}

Ich will ja nicht wissen, ob ein bestimmter Fall eingetreten ist, um einen Fehler zu melden, sondern ich brauche eine bestimmte Bedingung, um arbeiten zu können; und dann und nur dann, wenn diese nicht erfüllt ist, muss ich schweren Herzens einen Fehler melden.

Nebenbei bemerkt könnte man diese spitzfindige Unterscheidung auch dazu benutzen, um dem Compiler mitzuteilen, was der für den Ablauf wahrscheinlichere Fall ist. Damit könnte das unless ein Hinweise darauf sein, dass bei der Branch-Prediction der Fall eher unerwartet ist. Aber Performanz soll uns an dieser Stelle nicht wirklich interessieren, denn das lenkt nur unnötig ab.

Während ich mich hier also eher für Vielseitigkeit ausspreche, würde ich mir an anderer Stelle eher weniger Flexibilität wünschen. So treffe ich in Java und C# häufig beim Vergleich mit Konstanten auf die Formulierung

"abc".equals(s)

anstatt der von mir erwarteten Schreibweise

s.equals("abc")

Die meisten darauf Angesprochenen schauen mich dann etwas irritiert an und wissen gar nicht, worum es mir geht. Ich versuche dann zu erklären, dass man doch wissen wolle, welchen Wert s habe, welchen Wert "abc" hat, wüsste ich ja. Wenn equals eine Äquivalenzrelation ist, was in der Spezifikation von Object gefordert wird, dann muss natürlich wegen der dadurch geforderten Symmetrie aus a.equals(b) immer b.equals(a) folgen und umgekehrt. Aber es sagt eben etwas anderes aus, ob ich wissen will, ob a einer bestimmten Eigenschaft genügt oder eben b dieser Eigenschaft genügen soll.

Gelegentlich bekomme ich noch als Grund angegeben, dass auf diese Weise, wenn s = null gilt, keine NullPointerException geworfen wird. Das finde ich übrigens keinesfalls besser, denn es bedeutet, dass der Programmierer keinen blassen Schimmer hat, ob s definiert ist oder nicht. Und er möchte es auch nicht wissen, und er möchte es offensichtlich auch dem Leser nicht explizit mitteilen, dass er glaubt s könne null sein.

(s != null) && s.equals("abc")

wäre also in diesem Fall nicht nur deutlich präziser, sondern auch dementsprechend informativer.

Übrigens ist die "falsche" Abfrage ein Relikt aus C-Zeiten, in dem man zu dieser Maßnahme griff, um den Fehler einer Zuweisung statt eines Vergleichs mit Hilfe des Compilers sichtbar zu machen. Denn ein

if (c = 'a')

statt des Vergleichs mit (c == 'a') führt dazu, dass der Ausdruck immer true ist, weil nach der Zuweisung deren Wert mit 0 verglichen und dies im Fall von 'a' immer als true interpretiert wird. Das Umdrehen des unbeabsichtigten Ausdrucks zu

if ('a' = c)

führt nämlich zu der Compiler-Meldung, dass es sich bei 'a' nicht um einen L-Wert, also um einen, dem man etwas zuweisen kann, handelt. Dieser Fehler kann bei Sprachen wie Java oder C#, die bei Bedingungen Boolesche Ausdrücke verlangen, nur noch auftreten, wenn man es mit Booleschen Variablen zu tun hat und diese unsachgemäß prüft; in allen anderen Fällen wird der Fehler erkannt und macht diese Schreibweise damit für alle Sprachen dieser Art obsolet.

Wo also genau die Grenze gezogen wird, ist entweder dem Sprachdesigner oder dem Entwickler überlassen. Jeder kann für sich überlegen, was ihm am brauchbarsten erscheint. Der erste setzt allerdings Standards, während Letzterer die Artenvielfalt nach belieben fördern kann.

Ohne mich unbeliebt machen zu wollen, würde ich dazu tendieren, so viel wie möglich in der Sprache festzulegen, denn die Art ein Programmierproblem zu lösen, kann schon verschieden genug sein. So müsste man sich nicht durch Nebensächlichkeiten ablenken lassen, die übrigens nachweislich die Aufnahmefähigkeit stark einschränken können – auf Wunsch suche ich auch die entsprechenden Quellen dazu raus.

Davon kann man sich auch sehr leicht selbst überzeugen: Unter der Annahme, dass der Schleifenzähler nicht im Schleifenrumpf benötigt wird, sind die nachfolgenden Zeilen zwar identisch, aber in der Regel brauchen Leser für die verschiedenen Varianten unterschiedlich lange, um herauszufinden, wie viele Male die Schleife durchlaufen wird.

for (int i = 0; i < n; i++)
for (int i = n; i > 0; --i)
for (int i = 1; i <= n; ++i)
for (int i = n - 1; i >= 0; i--)

Zu jenen ablenkenden Nebensächlichkeiten gehört beispielsweise das Format von Programmen. So zeichnen Brian Kernigan und Dennis Ritchie für ein unglaublich großes, sinnlos verbranntes Vermögen verantwortlich, das für völlig unnötige Diskussionen und Umformatierungen draufgegangen ist, weil sie nicht festgelegt haben, wo genau die geschweiften Klammern hinkommen. Und obwohl sie in ihrem legendären C-Buch diese Klammern immer an die gleiche Stelle gesetzt haben, wussten es andere besser und haben damit Glaubenskriege entfacht. Die Skriptsprache Tcl hatte das Problem beispielsweise deshalb nie, weil eine Klammer in der "falschen" Zeile einfach etwas völlig anderes bedeutete.

Desgleichen wäre es schön gewesen, wenn Martin Odersky, der Designer von Scala, vehementer gesagt hätte, wo beim Typen-Doppelpunkt die Leerzeichen hinkommen: wie bei einem Label ohne Leerzeichen vor dem Doppelpunkt

x: Int

oder wie in der Mathematik – denn man darf ja nicht vergessen, wo die Typen und Algorithmen herkommen – davor und dahinter

x : Int

Und wenn es irgend geht, dann sollte das noch schnell festgelegt werden, bevor auch das unzählige verbrannte Manntage, -monate, -jahre oder gar -jahrzehnte und -jahrhunderte kostet.

Solche Dinge müssten eindeutig festgelegt werden, wie Leerzeichen vor einem Komma oder Semikolon, die dort einfach seit mehreren hundert Jahren nicht hingehören, aber immer wieder auftauchen. Letzten Endes ist es mir egal, warum es so oder anders sein soll, wenngleich eine plausible Begründung zu haben immer schön ist. Aber grundsätzlich wäre es gut, wenn es alle möglichst gleich machten, und noch besser, wenn es der Compiler – so er kann – selbstständig prüfen würde.

Gewöhnen kann man sich nämlich an fast alles, nur das Umgewöhnen fällt meist sehr, sehr schwer.

PS: Um es etwas konkreter zu machen, würde mich interessieren, was Sie alles mit sich machen ließen. Beispielhaft und der Einfachheit halber würde ich wissen wollen, ob Sie es dulden würden, wenn Sie der Compiler dazu zwingen würde

a) nur eine Anweisung pro Zeile zu schreiben,
b) kein Leerzeichen vor, aber mindestens eines nach einem Komma zu setzen,
c) kein Leerzeichen hinter einer öffnenden oder vor einer schließenden Klammer zu setzen,
d) Klassennamen mit einem Großbuchstaben zu beginnen,
e) alles passend einzurücken (wenn das auch der Editor machen könnte) und
f) auf die allgemein Formatfreiheit zu verzichten.

Über Kommentare dazu würde ich mich sehr freuen. ()