Perché for-each ha i due punti anziché "in"?


9

Dalla guida linguistica Java 5 :

Quando vedi i due punti (:) leggilo come "in".

Perché non usare inin primo luogo quindi?

Questo mi infastidisce da anni. Perché non è coerente con il resto della lingua. Ad esempio, in Java ci sono implements, extends, superper i rapporti tra i tipi invece di simboli come in C ++, Scala o Ruby.

In Java due punti utilizzati in 5 contesti . Tre dei quali sono ereditati da C. E altri due sono stati approvati da Joshua Bloch. Almeno, così diceva durante il discorso "Le controversie sulle chiusure" . Questo viene fuori quando critica l'uso di due punti per la mappatura in quanto incompatibile con la semantica per ogni. Il che per me sembra strano perché sono i modelli previsti per ogni abuso. Come list_name/category: elementso laberl/term: meaning.

Mi sono occupato di jcp e jsr, ma non ho trovato alcun segno di mailing list. Nessuna discussione su questo argomento trovata da google. Solo i neofiti confusi dal significato di colon in for.


Principali argomenti contro inforniti finora:

  • richiede una nuova parola chiave; e
  • complica il lexing.

Diamo un'occhiata alle definizioni grammaticali pertinenti :

dichiarazione
    : istruzione 'for' '(' forControl ')'
    | ...
    ;

forControl
    : enhancedForControl
    | forInit? ';' espressione? ';' forUpdate?
    ;

enhancedForControl
    : variabileModificatore * tipo variabileDeclaratoreId ':' espressione
    ;

Passaggio da :a innon portare ulteriore complessità e richiede nuova parola chiave.


1
La migliore fonte per scoprire le motivazioni dei progettisti del linguaggio sono spesso i progettisti stessi. Detto questo, questo è apparentemente solo zucchero sintattico su un iterabile; vedi stackoverflow.com/questions/11216994/…
Robert Harvey

Risposte:


8

I parser normali come vengono generalmente insegnati hanno uno stadio lexer prima che il parser tocchi l'input. Il lexer (anche "scanner" o "tokenizer") taglia l'input in piccoli token annotati con un tipo. Ciò consente al parser principale di utilizzare i token come elementi terminali anziché dover trattare ogni carattere come un terminale, il che porta a notevoli guadagni di efficienza. In particolare, il lexer può anche rimuovere tutti i commenti e gli spazi bianchi. Tuttavia, una fase di tokenizer separata significa che le parole chiave non possono essere utilizzate anche come identificatori (a meno che la lingua non supporti lo scostamento che è in qualche modo sfuggito al favore o prefisso tutti gli identificatori con un sigillo simile $foo).

Perché? Supponiamo di avere un tokenizer semplice che comprenda i seguenti token:

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

Il tokenizer corrisponderà sempre al token più lungo e preferirà le parole chiave rispetto agli identificatori. Quindi interestingsarà lasciato in sospeso come IDENT:interesting, ma insarà lasciato in sospeso come IN, mai come IDENT:interesting. Uno snippet di codice simile

for(var in expression)

verrà tradotto nel flusso di token

FOR LPAREN IDENT:var IN IDENT:expression RPAREN

Finora funziona. Ma ogni variabile inverrebbe lassata come parola chiave INanziché come variabile, il che spezzerebbe il codice. Il lexer non mantiene alcuno stato tra i token e non può sapere che di insolito dovrebbe essere una variabile tranne quando siamo in un ciclo for. Inoltre, il seguente codice dovrebbe essere legale:

for(in in expression)

Il primo insarebbe un identificatore, il secondo sarebbe una parola chiave.

Esistono due reazioni a questo problema:

Le parole chiave contestuali sono confuse, riutilizziamo invece le parole chiave.

Java ha molte parole riservate, alcune delle quali sono inutili tranne che per fornire messaggi di errore più utili ai programmatori che passano a Java dal C ++. L'aggiunta di nuove parole chiave interrompe il codice. L'aggiunta di parole chiave contestuali è fonte di confusione per un lettore del codice a meno che non abbiano una buona evidenziazione della sintassi e renda difficile l'implementazione degli strumenti perché dovranno utilizzare tecniche di analisi più avanzate (vedi sotto).

Quando vogliamo estendere la lingua, l'unico approccio sano è usare simboli che in precedenza non erano legali nella lingua. In particolare, questi non possono essere identificatori. Con la sintassi del ciclo foreach, Java ha riutilizzato la :parola chiave esistente con un nuovo significato. Con lambdas, Java ha aggiunto una ->parola chiave che in precedenza non poteva essere presente in alcun programma legale ( -->sarebbe comunque stata lasciata libera da ciò '--' '>'che è legale e ->potrebbe essere stata precedentemente lasciata libera come '-', '>', ma quella sequenza sarebbe stata respinta dal parser).

Le parole chiave contestuali semplificano le lingue, implementiamole

I Lexer sono indiscutibilmente utili. Ma invece di eseguire un lexer prima del parser, possiamo eseguirli in tandem con il parser. I parser bottom-up conoscono sempre l'insieme di tipi di token che sarebbero accettabili in una determinata posizione. Il parser può quindi richiedere al lexer di abbinare uno di questi tipi nella posizione corrente. In un ciclo for-each, il parser sarebbe nella posizione indicata dalla ·grammatica (semplificata) dopo che la variabile è stata trovata:

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

In quella posizione, i token legali sono SEMICOLONo IN, ma non IDENT. Una parola chiave insarebbe del tutto inequivocabile.

In questo esempio particolare, i parser top-down non avrebbero problemi neanche perché possiamo riscrivere la grammatica sopra

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

e tutti i token necessari per la decisione possono essere visualizzati senza backtracking.

Considera l'usabilità

Java ha sempre teso alla semplicità semantica e sintattica. Ad esempio, la lingua non supporta il sovraccarico dell'operatore perché renderebbe il codice molto più complicato. Quindi, quando decidiamo tra ine :per una sintassi per ogni ciclo, dobbiamo considerare quale è meno confuso e più evidente per gli utenti. Il caso estremo sarebbe probabilmente

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

(Nota: Java ha spazi dei nomi separati per nomi di tipo, variabili e metodi. Penso che questo sia stato un errore, soprattutto. Ciò non significa che la progettazione della lingua successiva debba aggiungere altri errori.)

Quale alternativa fornisce separazioni visive più chiare tra la variabile di iterazione e la raccolta iterata? Quale alternativa può essere riconosciuta più rapidamente quando dai un'occhiata al codice? Ho scoperto che i simboli di separazione sono migliori di una serie di parole quando si tratta di questi criteri. Altre lingue hanno valori diversi. Ad esempio Python spiega molti operatori in inglese in modo che possano essere letti in modo naturale e siano facili da capire, ma quelle stesse proprietà possono rendere abbastanza difficile comprendere un pezzo di Python a colpo d'occhio.


17

La sintassi per ogni ciclo è stata aggiunta in Java 5. Dovresti creare inuna parola chiave per la lingua, e l'aggiunta di parole chiave in una lingua in un secondo momento è qualcosa che eviti a tutti i costi perché rompe il codice esistente - improvvisamente tutte le variabili denominate in causano un'analisi errore. enumè stato abbastanza male in tal senso.


2
Sembra ... scomodo. Presuppone che i progettisti linguistici fossero abbastanza bravi da prevedere sin dall'inizio la maggior parte delle parole chiave richieste. Non sono nemmeno sicuro che sia necessario; compilatori decenti possono determinare se una parola chiave è una variabile in base al suo contesto.
Robert Harvey,

2
Non penso che Java abbia parole chiave contestuali come C #. Quindi, usare inavrebbe significato introdurre una nuova parola chiave, rompendo così la compatibilità all'indietro ( System.ino chiunque?) O introducendo un concetto nuovo di zecca precedentemente sconosciuto (parole chiave contestuali). Tutto per quale guadagno?
Jörg W Mittag,

2
Quale danno delle parole chiave contestuali?
user2418306

5
@ user2418306 L'aggiunta di una parola chiave non deve interrompere il codice esistente, a condizione che la lingua non venga analizzata con una fase lexer separata. In particolare, un "in" in for(variable in expression)non può mai essere ambiguo con nessun codice legale, anche se "in" può essere utilizzato per le variabili. Tuttavia, una fase lexer separata è abbastanza comune in molte toolchain del compilatore. Ciò renderebbe impossibile o almeno molto più difficile analizzare Java con alcuni generatori di parser comuni. Mantenere semplice la sintassi di una lingua è generalmente buono per tutti i soggetti coinvolti; non tutti hanno bisogno di mostruosità sintattiche come C ++ o Perl.
amon

1
@RobertHarvey: non dimenticarlo conste gotosono entrambe parole riservate in Java, ma non sono (ancora) utilizzate.
TMN,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.