¿Por qué cada uno tiene dos puntos en lugar de "adentro"?

9

De la guía de lenguaje Java 5 :

Cuando vea los dos puntos (:) léalo como "en".

¿Por qué no usar inen primer lugar entonces?

Esto me ha estado molestando durante años. Porque es inconsistente con el resto del lenguaje. Por ejemplo, en Java hay implements, extends, superpara las relaciones entre los tipos en lugar de símbolos como en C ++, Scala o Ruby.

En Java dos puntos utilizados en 5 contextos . Tres de los cuales son heredados de C. Y otros dos fueron respaldados por Joshua Bloch. Al menos, eso fue lo que dijo durante la charla "La controversia de los cierres" . Esto surge cuando critica el uso de dos puntos para el mapeo como inconsistente con la semántica para cada uno. Lo que me parece extraño porque son los patrones esperados abusados ​​para cada uno. Me gusta list_name/category: elementso laberl/term: meaning.

Estuve husmeando en jcp y jsr, pero no encontré ninguna señal de la lista de correo. Google no encontró discusiones sobre este asunto. Solo los novatos confundidos por el significado de colon en for.


Principales argumentos en contra inprovistos hasta ahora:

  • requiere nueva palabra clave; y
  • complica el lexing

Veamos definiciones gramaticales relevantes :

declaración
    : instrucción 'for' '(' forControl ')'
    El | ...
    ;

forControl
    : EnhancedForControl
    El | forInit? ';' ¿expresión? ';' forUpdate?
    ;

EnhancedForControl
    : variableModificador * tipo variableDeclaratorId ':' expresión
    ;

Cambiar de :a inno trae complejidad adicional o requiere una nueva palabra clave

usuario2418306
fuente
1
La mejor fuente para descubrir las motivaciones de los diseñadores de idiomas son a menudo los propios diseñadores. Dicho esto, aparentemente esto es solo azúcar sintáctico sobre un iterable; ver stackoverflow.com/questions/11216994/…
Robert Harvey

Respuestas:

8

Los analizadores normales, como generalmente se enseñan, tienen una etapa lexer antes de que el analizador toque la entrada. El lexer (también "escáner" o "tokenizador") corta la entrada en pequeños tokens que están anotados con un tipo. Esto permite que el analizador principal use tokens como elementos terminales en lugar de tener que tratar a cada personaje como un terminal, lo que conduce a ganancias de eficiencia notables. En particular, el lexer también puede eliminar todos los comentarios y espacios en blanco. Sin embargo, una fase de tokenizador separada significa que las palabras clave no se pueden usar también como identificadores (a menos que el idioma admita la eliminación de caracteres que ha caído en desuso o prefija todos los identificadores con un sigilo $foo).

¿Por qué? Supongamos que tenemos un tokenizador simple que comprende los siguientes tokens:

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

El tokenizer siempre coincidirá con el token más largo y preferirá las palabras clave sobre los identificadores. Entonces interestingserá lexed como IDENT:interesting, pero inserá lexed como IN, nunca como IDENT:interesting. Un fragmento de código como

for(var in expression)

será traducido a la secuencia de tokens

FOR LPAREN IDENT:var IN IDENT:expression RPAREN

Hasta ahora, eso funciona. Pero cualquier variable insería lexed como la palabra clave en INlugar de una variable, lo que rompería el código. El lexer no mantiene ningún estado entre los tokens, y no puede saber que innormalmente debería ser una variable, excepto cuando estamos en un bucle for. Además, el siguiente código debe ser legal:

for(in in expression)

El primero insería un identificador, el segundo sería una palabra clave.

Hay dos reacciones a este problema:

Las palabras clave contextuales son confusas, reutilicemos las palabras clave en su lugar.

Java tiene muchas palabras reservadas, algunas de las cuales no tienen uso, excepto para proporcionar mensajes de error más útiles a los programadores que cambian a Java desde C ++. Agregar nuevas palabras clave rompe el código. Agregar palabras clave contextuales es confuso para un lector del código a menos que tenga un buen resaltado de sintaxis, y hace que las herramientas sean difíciles de implementar porque tendrán que usar técnicas de análisis más avanzadas (ver más abajo).

Cuando queremos extender el lenguaje, el único enfoque sensato es usar símbolos que anteriormente no eran legales en el idioma. En particular, estos no pueden ser identificadores. Con la sintaxis del bucle foreach, Java reutilizó la :palabra clave existente con un nuevo significado. Con lambdas, Java agregó una ->palabra clave que no podía aparecer previamente en ningún programa legal ( -->aún estaría lex como lo '--' '>'que es legal, y ->podría haber sido previamente lexed as '-', '>', pero esa secuencia sería rechazada por el analizador).

Las palabras clave contextuales simplifican los idiomas, impleméntelos

Los Lexers son indiscutiblemente útiles. Pero en lugar de ejecutar un lexer antes del analizador, podemos ejecutarlos en conjunto con el analizador. Los analizadores ascendentes siempre conocen el conjunto de tipos de tokens que serían aceptables en cualquier ubicación. El analizador puede solicitar al lexer que coincida con cualquiera de estos tipos en la posición actual. En un ciclo for-each, el analizador estaría en la posición indicada ·en la gramática (simplificada) después de que se haya encontrado la variable:

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

En esa posición, los tokens legales son SEMICOLONo IN, pero no IDENT. Una palabra clave insería completamente inequívoca.

En este ejemplo en particular, los analizadores de arriba hacia abajo tampoco tendrían un problema, ya que podemos reescribir la gramática anterior para

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

y todos los tokens necesarios para la decisión se pueden ver sin retroceder.

Considerar usabilidad

Java siempre ha tendido a la simplicidad semántica y sintáctica. Por ejemplo, el lenguaje no admite la sobrecarga del operador porque haría el código mucho más complicado. Entonces, al decidir entre iny :para una sintaxis de bucle for-each, tenemos que considerar cuál es menos confuso y más evidente para los usuarios. El caso extremo probablemente sería

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

(Nota: Java tiene espacios de nombres separados para nombres de tipos, variables y métodos. Creo que esto fue un error, principalmente. Esto no significa que el diseño del lenguaje posterior tenga que agregar más errores).

¿Qué alternativa proporciona separaciones visuales más claras entre la variable de iteración y la colección iterada? ¿Qué alternativa se puede reconocer más rápidamente cuando echas un vistazo al código? Descubrí que los símbolos de separación son mejores que una cadena de palabras cuando se trata de estos criterios. Otros idiomas tienen valores diferentes. Por ejemplo, Python deletrea muchos operadores en inglés para que se puedan leer de forma natural y sean fáciles de entender, pero esas mismas propiedades pueden dificultar la comprensión de una pieza de Python de un vistazo.

amon
fuente
17

La sintaxis de bucle for-each se agregó en Java 5. Tendría que hacer inuna palabra clave de idioma, y ​​agregar palabras clave a un idioma más adelante es algo que evita a toda costa porque rompe el código existente; de ​​repente, todas las variables nombradas in causan un análisis error. enumfue lo suficientemente malo en ese sentido.

Michael Borgwardt
fuente
2
Eso parece ... inconveniente. Presupone que los diseñadores de idiomas fueron lo suficientemente buenos como para pronosticar la mayoría de las palabras clave requeridas desde el principio. No estoy seguro de que sea necesario; los compiladores decentes pueden determinar si una palabra clave es o no una variable por su contexto.
Robert Harvey
2
No creo que Java tenga palabras clave contextuales como C #. Por lo tanto, el uso inhubiera significado introducir una nueva palabra clave, rompiendo así la compatibilidad con versiones anteriores ( System.in¿alguien?) O introduciendo un concepto nuevo y desconocido (palabras clave contextuales). ¿Todo para qué ganancia?
Jörg W Mittag
2
¿Qué daño tienen las palabras clave contextuales?
user2418306
55
@ user2418306 Agregar una palabra clave no tiene que romper el código existente, siempre que el idioma no se analice con una fase lexer separada. En particular, un "in" in for(variable in expression)nunca puede ser ambiguo con ningún código legal, incluso si "in" puede usarse para variables. Sin embargo, una fase lexer separada es bastante común en muchas cadenas de herramientas de compilación. Esto haría imposible o al menos mucho más difícil analizar Java con algunos generadores de analizadores comunes. Mantener la sintaxis de un lenguaje simple suele ser bueno para todos los involucrados; No todos necesitan monstruosidades sintácticas como C ++ o Perl.
amon
1
@RobertHarvey: No lo olvides consty gotoambas son palabras reservadas en Java, pero aún no se usan.
TMN