2016-07-14 30 views
10

sto usando map() funzione della matrice a-in ciclo come questo:Uso trascinamento chiusura nel ciclo for-in

let numbers = [2, 4, 6, 8, 10] 

for doubled in numbers.map { $0 * 2 } // compile error 
{ 
    print(doubled) 
} 

che produce errore di compilazione:

Use of unresolved identifier 'doubled'

Tuttavia, se metti parentesi per la funzione map(), funziona perfettamente. cioè

for doubled in numbers.map ({ $0 * 2 }) 
{ 
    print(doubled) 
} 

mia domanda è, perché non sarebbe compilatore differenziare blocco di codice di funzione e anello di trascinamento, supponendo che questo causa il problema?

risposta

9

Questo perché ci sarebbe ambiguità sul contesto in cui la funzione di trascinamento dovrebbe operare. Una sintassi alternativa che funziona è:

let numbers = [2, 4, 6, 8, 10] 

for doubled in (numbers.map { $0 * 2 }) // All good :) 
{ 
    print(doubled) 
} 

direi che questo è probabilmente perché il 'in' operatore ha una precedenza maggiore di funzioni di uscita.

Spetta a te che pensi sia più leggibile.

+0

Questo è un elegante trucco. –

8

Grammatica di chiusura del trailing genera una nota ambiguità tra un corpo dell'istruzione (nel tuo caso, questo è il ciclo for, ma si applica anche ad altre istruzioni) e il corpo della chiusura finale. Sebbene sia tecnicamente possibile risolvere questo problema, compiler designers decided to prohibit trailing closure syntax in controlling portions of various high-level statements:

While it would be possible to tell what is intended in some cases by performing arbitrary lookahead or by performing type checking while parsing, these approaches have significant consequences for the architecture for the compiler. As such, we've opted keep the parser simple and disallow this.

Per comprendere la natura del conflitto, considerare questo esempio:

for v in expr { /* code 1 */ } { /* code 2 */ } 

Il parser deve fare una scelta riguardo code 1 blocco. Si potrebbe usarlo come una chiusura posteriore della expr e trattare code 2 come il corpo del ciclo for, oppure utilizzare code 1 come il corpo del ciclo for, mentre il trattamento code 2 come un gruppo indipendente di istruzioni racchiuse tra parentesi graffe - un classico shift-reduce conflict .

Risolvere tali conflitti è molto costoso. Essenzialmente, il parser deve continuare a guardare avanti attraverso più token, fino a quando solo un'interpretazione ha senso, o il parser esaurisce i token (nel qual caso prende una decisione arbitraria in un modo o nell'altro, richiedendo ai programmatori di disambiguare il loro programma quando quella scelta non è ciò che volevano).

L'aggiunta di parentesi rimuove l'ambiguità. Proposte sono state considerate per rimuovere l'ambiguità aggiungendo una parola chiave obbligatoria per separare la parte di controllo del loop dal suo corpo, cioè

// The syntax of rejected proposal 
for doubled in numbers.map { $0 * 2 } do { 
    print(doubled) //     ^^ 
} 

aggiunta di un extra do parola "attacca" il blocco di sinistra, se del caso, l'espressione e rende il blocco a destra il corpo dell'istruzione loop. Questo approccio ha un grosso svantaggio, perché è un cambio di rottura. Questo è il motivo per cui questa proposta è stata respinta.

+0

Grazie per la spiegazione dettagliata. –

+0

@ M.Hassan Siete i benvenuti! Ho pensato che dal momento che hai già un bel work-around, dovrei concentrarmi sulla natura del problema dal punto di vista dello scrittore di compilatori. – dasblinkenlight

2

La sintassi è ambigua (vedere dasblinkenlight's answer).Per una sintassi alternativa:

let numbers = [2, 4, 6, 8, 10] 

numbers.map { $0 * 2 }.forEach { 
    print(doubled) 
} 

o

let numbers = [2, 4, 6, 8, 10] 
let doubledNumbers = numbers.map { $0 * 2 } 

for doubled in doubledNumbers { 
    print(doubled) 
} 
+2

Questo non è un approccio funzionale. È solo un metodo (Una funzione per Anyach, se esistesse una cosa del genere, restituirebbe qualcosa di utile, non fare affidamento su un effetto collaterale.) Faccio solo il punto perché continuo a vedere 'forEach' pop up in luoghi in cui è inappropriato (nella maggior parte dei casi,' for-in' è il miglior Swift), e sospetto che "perché è il modo funzionale" è una delle ragioni. In questo caso, alla fine di una catena mappa/filtro, può essere utile, ma non perché si basa sulla programmazione funzionale (è il contrario); solo perché la sintassi a volte si allinea con le catene mappa/filtro un po 'più bello. –

+0

@RobNapier Sono d'accordo che 'forEach' non è puramente funzionale (anche se molti programmatori lo chiamano funzionale perché è possibile passare una funzione ad esso e la parola" funzionale "non è esattamente ben definita). Tuttavia, non sono d'accordo che 'for-in' è meglio di' forEach'. Come è meglio "for-in"? – Sulthan

+2

Onora return/break/continue (il trattamento 'forEach' di' return' rischia di sorprendere molti sviluppatori). Non causa l'acquisizione della chiusura. Corrisponde al resto del controllo del flusso Swift (if, do, while, repeat). Non penso che sia un caso che il libro di The Swift Programming Language spieghi 'map' e' for-in', ma non citi mai 'forEach'. Vedi anche i commenti di clattner su come "forEach (a malapena) paga per se stesso" perché puoi passare ad esso una funzione non anonima. https://lists.swift.org/pipermail/swift-evolution/Week-of-Mon-20151214/003388.html. –