Warum für jeden hat Doppelpunkt statt "in"?

9

Aus dem Java 5- Sprachführer :

Wenn Sie den Doppelpunkt (:) sehen, lesen Sie ihn als "in".

Warum dann gar nicht erst verwenden in?

Das nervt mich seit Jahren. Weil es nicht mit dem Rest der Sprache übereinstimmt. Zum Beispiel in Java gibt es implements, extends, superfür die Beziehungen zwischen den Typen anstelle von Symbolen wie in C ++, Scala oder Ruby.

In Java Doppelpunkt in 5 Kontexten verwendet . Drei davon sind von C. geerbt. Die anderen beiden wurden von Joshua Bloch gebilligt. Zumindest sagte er dies während des Gesprächs über die Kontroverse um die Schließung . Dies tritt auf, wenn er die Verwendung eines Doppelpunkts für die Zuordnung als inkonsistent mit jeder Semantik kritisiert. Was mir seltsam erscheint, weil es die für jeden Missbrauch erwarteten Muster sind. Wie list_name/category: elementsoder laberl/term: meaning.

Ich habe mich in jcp und jsr umgesehen, aber keine Anzeichen einer Mailingliste gefunden. Keine Diskussionen zu diesem Thema von Google gefunden. Nur Neulinge verwirrt durch die Bedeutung von Doppelpunkt in for.


Hauptargumente gegen bisher ingeliefert:

  • erfordert neues Schlüsselwort; und
  • erschwert das lexen.

Schauen wir uns relevante Grammatikdefinitionen an :

Erklärung
    : 'for' '(' forControl ')' Anweisung
    | ...
    ;;

forControl
    : advancedForControl
    | forInit? ';' Ausdruck? ';' forUpdate?
    ;;

EnhancedForControl
    : variableModifier * Typ variableDeclaratorId ':' Ausdruck
    ;;

Ändern Sie von :, inum keine zusätzliche Komplexität zu erzielen, oder erfordern Sie ein neues Schlüsselwort.

user2418306
quelle
1
Die beste Quelle, um die Motivationen von Sprachdesignern herauszufinden, sind oft die Designer selbst. Das heißt, dies ist anscheinend nur syntaktischer Zucker über eine iterable; siehe stackoverflow.com/questions/11216994/…
Robert Harvey

Antworten:

8

Normale Parser, wie sie im Allgemeinen gelehrt werden, haben eine Lexerstufe, bevor der Parser die Eingabe berührt. Der Lexer (auch "Scanner" oder "Tokenizer") zerlegt die Eingabe in kleine Token, die mit einem Typ versehen sind. Auf diese Weise kann der Hauptparser Token als Terminalelemente verwenden, anstatt jedes Zeichen als Terminal behandeln zu müssen, was zu spürbaren Effizienzgewinnen führt. Insbesondere kann der Lexer auch alle Kommentare und Leerzeichen entfernen. Eine separate Tokenizer-Phase bedeutet jedoch, dass Schlüsselwörter nicht auch als Bezeichner verwendet werden können (es sei denn, die Sprache unterstützt das Abstreifen, das etwas in Ungnade gefallen ist, oder stellt allen Bezeichnern ein Siegel wie vor $foo).

Warum? Nehmen wir an, wir haben einen einfachen Tokenizer, der die folgenden Token versteht:

FOR = 'for'
LPAREN = '('
RPAREN = ')'
IN = 'in'
IDENT = /\w+/
COLON = ':'
SEMICOLON = ';'

Der Tokenizer stimmt immer mit dem längsten Token überein und bevorzugt Schlüsselwörter gegenüber Bezeichnern. Also interestingwird lexed als IDENT:interesting, aber inwird lexed als IN, niemals als IDENT:interesting. Ein Code-Snippet wie

for(var in expression)

wird in den Token-Stream übersetzt

FOR LPAREN IDENT:var IN IDENT:expression RPAREN

Bisher funktioniert das. Aber jede Variable inwürde als Schlüsselwort lexiert und INnicht als Variable, die den Code beschädigen würde. Der Lexer behält keinen Status zwischen den Token bei und kann nicht wissen, dass dies innormalerweise eine Variable sein sollte, außer wenn wir uns in einer for-Schleife befinden. Außerdem sollte der folgende Code legal sein:

for(in in expression)

Der erste inwäre eine Kennung, der zweite ein Schlüsselwort.

Es gibt zwei Reaktionen auf dieses Problem:

Kontextbezogene Schlüsselwörter sind verwirrend. Verwenden wir stattdessen Schlüsselwörter.

Java hat viele reservierte Wörter, von denen einige nur dazu dienen, Programmierern, die von C ++ zu Java wechseln, hilfreichere Fehlermeldungen zukommen zu lassen. Durch das Hinzufügen neuer Schlüsselwörter wird der Code unterbrochen. Das Hinzufügen von kontextbezogenen Schlüsselwörtern ist für einen Leser des Codes verwirrend, es sei denn, sie verfügen über eine gute Syntaxhervorhebung, und die Implementierung von Tools ist schwierig, da fortgeschrittenere Analysetechniken verwendet werden müssen (siehe unten).

Wenn wir die Sprache erweitern möchten, besteht der einzig vernünftige Ansatz darin, Symbole zu verwenden, die zuvor in der Sprache nicht legal waren. Insbesondere können dies keine Bezeichner sein. Mit der foreach-Schleifensyntax hat Java das vorhandene :Schlüsselwort mit einer neuen Bedeutung wiederverwendet . Mit Lambdas fügte Java ein ->Schlüsselwort hinzu, das zuvor in keinem legalen Programm vorkommen konnte ( -->würde immer noch als legal lexiert '--' '>'und ->möglicherweise zuvor als lexiert '-', '>', aber diese Sequenz würde vom Parser abgelehnt).

Kontextbezogene Schlüsselwörter vereinfachen Sprachen, lassen Sie uns sie implementieren

Lexer sind unbestreitbar nützlich. Aber anstatt einen Lexer vor dem Parser auszuführen, können wir sie zusammen mit dem Parser ausführen. Bottom-up-Parser kennen immer die Token-Typen, die an einem bestimmten Ort akzeptabel sind. Der Parser kann dann den Lexer auffordern, einen dieser Typen an der aktuellen Position abzugleichen. In einer for-each-Schleife befindet sich der Parser an der Position, die ·in der (vereinfachten) Grammatik angegeben ist, nachdem die Variable gefunden wurde:

for_loop = for_loop_cstyle | for_each_loop
for_loop_cstyle = 'for' '(' declaration · ';' expression ';' expression ')'
for_each_loop = 'for' '(' declaration · 'in' expression ')'

An dieser Stelle sind die legalen Token SEMICOLONoder IN, aber nicht IDENT. Ein Schlüsselwort inwäre völlig eindeutig.

In diesem speziellen Beispiel hätten Top-Down-Parser auch kein Problem, da wir die obige Grammatik umschreiben können

for_loop = 'for' '(' declaration · for_loop_rest ')'
for_loop_rest =  · ';' expression ';' expression
for_loop_rest = · 'in' expression

und alle für die Entscheidung notwendigen Token können ohne Rückverfolgung angezeigt werden.

Betrachten Sie die Benutzerfreundlichkeit

Java tendierte immer zur semantischen und syntaktischen Einfachheit. Zum Beispiel unterstützt die Sprache das Überladen von Operatoren nicht, da dies den Code weitaus komplizierter machen würde. Wenn wir uns also zwischen inund :für eine für jede Schleife bestimmte Syntax entscheiden, müssen wir berücksichtigen, welche weniger verwirrend und für Benutzer offensichtlicher ist. Der Extremfall wäre wahrscheinlich

for (in in in in())
for (in in : in())

(Hinweis: Java verfügt über separate Namespaces für Typnamen, Variablen und Methoden. Ich denke, dies war meistens ein Fehler. Dies bedeutet nicht, dass das spätere Sprachdesign weitere Fehler hinzufügen muss .)

Welche Alternative bietet klarere visuelle Trennungen zwischen der Iterationsvariablen und der iterierten Sammlung? Welche Alternative erkennt man schneller, wenn man sich den Code ansieht? Ich habe festgestellt, dass das Trennen von Symbolen bei diesen Kriterien besser ist als eine Wortfolge. Andere Sprachen haben andere Werte. Zum Beispiel formuliert Python viele Operatoren auf Englisch, damit sie natürlich gelesen werden können und leicht zu verstehen sind. Dieselben Eigenschaften können es jedoch ziemlich schwierig machen, ein Stück Python auf einen Blick zu verstehen.

amon
quelle
17

Die for-each-Schleifensyntax wurde in Java 5 hinzugefügt. Sie müssten inein Sprachschlüsselwort erstellen, und das spätere Hinzufügen von Schlüsselwörtern zu einer Sprache ist etwas, das Sie um jeden Preis vermeiden, da es den vorhandenen Code beschädigt - plötzlich verursachen alle genannten Variablen in eine Analyse Error. enumwar in dieser Hinsicht schon schlimm genug.

Michael Borgwardt
quelle
2
Das scheint ... unpraktisch. Dies setzt voraus, dass die Sprachdesigner von Anfang an gut genug waren, um die meisten erforderlichen Keywords vorherzusagen. Ich bin mir nicht sicher, ob es überhaupt notwendig ist. Anständige Compiler können anhand ihres Kontexts bestimmen, ob ein Schlüsselwort eine Variable ist oder nicht.
Robert Harvey
2
Ich glaube nicht, dass Java kontextbezogene Schlüsselwörter wie C # hat. Die Verwendung inhätte also bedeutet, entweder ein neues Schlüsselwort einzuführen und damit die Abwärtskompatibilität zu brechen ( System.inirgendjemand?) Oder ein zuvor unbekanntes brandneues Konzept einzuführen (kontextbezogene Schlüsselwörter). Alles für welchen Gewinn?
Jörg W Mittag
2
Welchen Schaden haben die kontextbezogenen Schlüsselwörter?
user2418306
5
@ user2418306 Das Hinzufügen eines Schlüsselworts muss den vorhandenen Code nicht beschädigen, vorausgesetzt, die Sprache wird nicht mit einer separaten Lexer-Phase analysiert. Insbesondere kann ein "in" in for(variable in expression)niemals mit einem Rechtscode mehrdeutig sein, selbst wenn "in" für Variablen verwendet werden kann. Eine separate Lexer-Phase ist jedoch in vielen Compiler-Toolchains weit verbreitet. Dies würde es unmöglich oder zumindest weitaus schwieriger machen, Java mit einigen gängigen Parser-Generatoren zu analysieren. Die Syntax einer Sprache einfach zu halten, ist normalerweise für alle Beteiligten gut. Nicht jeder braucht syntaktische Monstrositäten wie C ++ oder Perl.
Amon
1
@ RobertHarvey: Vergiss das nicht constund gotosind beide reservierte Wörter in Java, werden aber (noch) nicht verwendet.
TMN