Por que cada um tem dois pontos em vez de "in"?

9

No guia da linguagem Java 5 :

Quando vir os dois pontos (:), leia-os como "em".

Por que não usar inem primeiro lugar, então?

Isso me incomoda há anos. Porque é inconsistente com o resto do idioma. Por exemplo, em Java há implements, extends, superpara as relações entre os tipos em vez de símbolos como em C ++, Scala ou Ruby.

Em Java dois pontos utilizados em 5 contextos . Três dos quais são herdados de C. E outros dois foram endossados ​​por Joshua Bloch. Pelo menos, foi o que ele falou durante a palestra "A controvérsia do fechamento" . Isso ocorre quando ele critica o uso de dois pontos no mapeamento como inconsistente com a semântica de cada uma. O que para mim parece estranho, porque são os padrões esperados para cada abuso. Como list_name/category: elementsou laberl/term: meaning.

Eu bisbilhotei o jcp e o jsr, mas não encontrei sinal de lista de discussão. Nenhuma discussão sobre este assunto foi encontrada pelo google. Apenas novatos confusos com o significado de dois pontos em for.


Principais argumentos contra infornecidos até agora:

  • requer nova palavra-chave; e
  • complica lexing.

Vejamos as definições gramaticais relevantes :

declaração
    : declaração 'for' '(' forControl ')'
    | ...
    ;

forControl
    : EnhancedForControl
    | forInit? ';' expressão? ';' forUpdate?
    ;

EnhancedForControl
    : variableModifier * tipo variableDeclaratorId ':' expression
    ;

Mude de :para innão trazer complexidade adicional ou requer nova palavra-chave.

user2418306
fonte
11
A melhor fonte para descobrir as motivações dos designers de linguagem são geralmente os próprios designers. Dito isto, aparentemente isso é apenas açúcar sintático sobre um iterável; Veja stackoverflow.com/questions/11216994/…
Robert Harvey

Respostas:

8

Analisadores normais, como geralmente são ensinados, têm um estágio de lexer antes que o analisador toque na entrada. O lexer (também "scanner" ou "tokenizer") divide a entrada em pequenos tokens anotados com um tipo. Isso permite que o analisador principal use tokens como elementos terminais em vez de precisar tratar cada caractere como um terminal, o que leva a ganhos de eficiência perceptíveis. Em particular, o lexer também pode remover todos os comentários e espaços em branco. No entanto, uma fase separada do tokenizer significa que as palavras-chave também não podem ser usadas como identificadores (a menos que o idioma suporte stropping que tenha caído em desuso, ou prefixe todos os identificadores com um símbolo $foo).

Por quê? Vamos supor que temos um tokenizador simples que compreende os seguintes tokens:

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

O tokenizer sempre corresponderá ao token mais longo e preferirá palavras-chave a identificadores. Assim interestingserá lexado como IDENT:interesting, mas inserá lexado como IN, nunca como IDENT:interesting. Um trecho de código como

for(var in expression)

será traduzido para o fluxo de token

FOR LPAREN IDENT:var IN IDENT:expression RPAREN

Até agora, isso funciona. Mas qualquer variável inseria lexada como a palavra-chave em INvez de uma variável, o que quebraria o código. O lexer não mantém nenhum estado entre os tokens e não pode saber que ingeralmente deve ser uma variável, exceto quando estamos em um loop for. Além disso, o código a seguir deve ser legal:

for(in in expression)

O primeiro inseria um identificador, o segundo seria uma palavra-chave.

Há duas reações a esse problema:

Palavras-chave contextuais são confusas, vamos reutilizar palavras-chave.

Java possui muitas palavras reservadas, algumas das quais não têm utilidade, exceto por fornecer mensagens de erro mais úteis aos programadores que mudam para C ++ em Java. A adição de novas palavras-chave quebra o código. A adição de palavras-chave contextuais é confusa para o leitor do código, a menos que ele tenha um bom destaque de sintaxe e dificulte a implementação de ferramentas, pois elas terão que usar técnicas de análise mais avançadas (veja abaixo).

Quando queremos estender o idioma, a única abordagem sensata é usar símbolos que antes não eram legais no idioma. Em particular, estes não podem ser identificadores. Com a sintaxe do loop foreach, o Java reutilizou a :palavra-chave existente com um novo significado. Com as lambdas, o Java adicionou uma ->palavra - chave que não poderia ocorrer anteriormente em nenhum programa jurídico ( -->ainda seria lexada como '--' '>'legal e ->pode ter sido lexada como '-', '>', mas essa sequência seria rejeitada pelo analisador).

Palavras-chave contextuais simplificam idiomas, vamos implementá-los

Lexers são indiscutivelmente úteis. Mas, em vez de executar um lexer antes do analisador, podemos executá-lo em conjunto com o analisador. Os analisadores de baixo para cima sempre sabem o conjunto de tipos de token que seriam aceitáveis ​​em qualquer local. O analisador pode solicitar ao lexer que corresponda a qualquer um desses tipos na posição atual. Em um loop for-each, o analisador estaria na posição indicada ·na gramática (simplificada) depois que a variável fosse encontrada:

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

Nessa posição, os tokens legais são SEMICOLONou IN, mas não IDENT. Uma palavra-chave inseria totalmente inequívoca.

Neste exemplo em particular, os analisadores de cima para baixo também não teriam problemas, pois podemos reescrever a gramática acima para

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

e todos os tokens necessários para a decisão podem ser vistos sem retroceder.

Considere a usabilidade

O Java sempre tendeu à simplicidade semântica e sintática. Por exemplo, o idioma não suporta sobrecarga do operador, pois isso tornaria o código muito mais complicado. Portanto, ao decidir entre ine :para uma sintaxe de loop para cada um, precisamos considerar qual é menos confuso e mais aparente para os usuários. O caso extremo provavelmente seria

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

(Nota: Java possui espaços de nomes separados para nomes de tipos, variáveis ​​e métodos. Acho que isso foi um erro, principalmente. Isso não significa que o design posterior da linguagem precise adicionar mais erros.)

Qual alternativa fornece separações visuais mais claras entre a variável de iteração e a coleção iterada? Qual alternativa pode ser reconhecida mais rapidamente quando você olha o código? Descobri que os símbolos de separação são melhores do que uma sequência de palavras quando se trata desses critérios. Outros idiomas têm valores diferentes. Por exemplo, o Python explica muitos operadores em inglês para que possam ser lidos naturalmente e fáceis de entender, mas essas mesmas propriedades podem dificultar bastante a compreensão de um pedaço do Python de relance.

amon
fonte
17

A sintaxe para cada loop foi adicionada no Java 5. Você precisaria criar inuma palavra-chave de linguagem e adicionar palavras-chave a uma linguagem posteriormente é algo que você evita a todo custo, pois quebra o código existente - de repente todas as variáveis ​​nomeadas in causam uma análise erro. enumfoi ruim o suficiente a esse respeito.

Michael Borgwardt
fonte
2
Isso parece ... inconveniente. Pressupõe que os designers de idiomas tenham sido bons o suficiente para prever a maioria das palavras-chave necessárias desde o início. Não tenho certeza se é necessário; compiladores decentes podem determinar se uma palavra-chave é ou não uma variável por seu contexto.
Robert Harvey
2
Eu não acho que Java tenha palavras-chave contextuais como o C #. Portanto, usar insignificaria introduzir uma nova palavra-chave, quebrando a compatibilidade com versões anteriores ( System.inalguém está?) Ou introduzir um conceito novinho em folha anteriormente desconhecido (palavras-chave contextuais). Tudo para que ganho?
Jörg W Mittag
2
Que mal das palavras-chave contextuais?
user2418306
5
@ user2418306 A adição de uma palavra-chave não precisa quebrar o código existente, desde que o idioma não seja analisado com uma fase separada do lexer. Em particular, um "in" in for(variable in expression)nunca pode ser ambíguo com qualquer código legal, mesmo que "in" possa ser usado para variáveis. No entanto, uma fase separada do lexer é bastante comum em muitas cadeias de ferramentas do compilador. Isso tornaria impossível ou pelo menos muito mais difícil analisar o Java com alguns geradores de analisadores comuns. Manter a sintaxe de um idioma simples geralmente é bom para todos os envolvidos; nem todo mundo precisa de monstruosidades sintáticas como C ++ ou Perl.
amon
11
@RobertHarvey: Não se esqueça disso conste gotosão palavras reservadas em Java, mas ainda não foram usadas.
TMN