2014-12-19 10 views
6

Diciamo che abbiamo:se lasciare che comporta in modo strano quando si specifica in modo esplicito tipo

let a:Int? = nil 

// block not executed - unwapping done, type is inferred 
if let unwrapped_a = a { 
    println(unwrapped_a) 
} 

// block not executed - unwrapping done, type is specified 
if let unwrapped_a:Int = a { 
    println(unwrapped_a) 
} 

// block gets executed - unwrapping not done, new local constant + assignment is done instead? 
if let not_unwrapped_a:Int? = a { 
    println(not_unwrapped_a) 
} 

Quindi devo supporre che Swift fa un imballato nel primo caso, ma un incarico nel secondo caso?

Questa sintassi non è un po 'troppo vicina per creare confusione? Voglio dire, sì, il compilatore ti avverte che stai usando un tipo opzionale quando lavori con not_unwrapped_a, ma ancora.

Aggiornamento:

Così, dopo la risposta di velocità relativa Velocity ho trovato un altro (ma in realtà la stessa) Caso strano:

if let not_unwrapped_a:Int???? = a { 
    println(not_unwrapped_a) 
} 

a saranno silenziosamente avvolto in un Int????. Quindi sarà un tipo di Int????? (cinque) - perché a era già un optional. E poi verrà scartato una volta.

+2

Echoing Antonio di seguito, l'ultimo caso mi sembra ridondante e non necessario. Lo scopo di "se let' è una scorciatoia per scartare gli optionals e solo eseguirlo quando non è nulla. Il terzo caso dice "se questo valore nil/non-nil è nullo o non-zero (ovviamente lo è), esegui il blocco indipendentemente", che è ciò che accade. È in pratica lo stesso che usare una normale istruzione 'if', ignorando la funzione di sicurezza' if let' fornisce quando fallisce con garbo su valori nulli. Dovresti comunque gestire nuovamente i valori 'nil' all'interno del blocco, il che vanifica lo scopo di entrambi gli optionals e il binding facoltativo. – mc01

risposta

5

Il caso 1 e il caso 2 sono identici - sono entrambi assegnazioni del contenuto di a a una nuova variabile. L'unica differenza è che stai lasciando Swift per dedurre il tipo di unwrapped_a nell'opzione 1, mentre stai dando manualmente l'opzione type 2. Il motivo principale per cui dovresti fare l'opzione 2 è se il valore di origine fosse ambiguo - ad esempio se si trattasse di una funzione sovraccaricata che potrebbe restituire più tipi.

Il caso 3 è piuttosto interessante.

Ogni volta che si ha un valore, Swift sarà sempre disposto ad aggiornarlo silenziosamente a un valore di avvolgimento facoltativo, se aiuta a rendere i tipi corrispondenti e il codice compilato. L'aggiornamento automatico rapido dei tipi è abbastanza raro (non aggiornerà implicitamente un Int16 ad un Int32 per esempio) ma i valori agli optionals sono un'eccezione.

Ciò significa che è possibile passare i valori ovunque è necessario un optional, senza dover preoccuparsi di avvolgerla:

func f(maybe: Int?) { ... } 

let i = 1 

// you can just pass a straight value: 
f(i) 

// Swift will turn this into this: 
f(Optional(i)) 

Quindi nel tuo ultimo esempio, hai detto Swift si desidera not_unwrapped_a di essere un Int?.Ma fa parte di un let che richiede a da scartare prima che sia assegnato ad esso.

Presentato con questo, l'unico modo in cui Swift può farlo funzionare è implicitamente avvolgere a in un altro opzionale, quindi è quello che fa. Ora è un opzionale contenente un opzionale contenente nil. Questo non è un optional a valore nullo - è un optional che contiene un valore (di un opzionale contenente nil). Unwrapping che ti dà un opzionale contenente nil. È sembra come non è successo niente. Ma lo ha fatto - è stato incartato una seconda volta, poi scartato una volta.

Si può vedere questo in azione se si compila il codice di esempio con swiftc -dump-ast source.swift. Vedrai la frase inject_into_optional implicit type='Int??’. Il Int?? è un opzionale contenente un opzionale.

Gli optionals contenenti opzionali non sono casi limite oscuri: possono accadere facilmente. Per esempio, se mai per ... in un array contenente opzioni, o usato un pedice per ottenere un valore da un dizionario che conteneva opzioni, allora gli optionals degli optionals sono stati coinvolti in quel processo.

Un altro modo di pensare a questo è se si pensa di if let x = y { } come una specie di * come una funzione, if_let, definiti come segue:

func if_let<T>(optVal: T?, block: T->()) { 
    if optVal != nil { 
     block(optVal!) 
    } 
} 

Ora immaginate se avete fornito un block che ha preso un Int? - che è, T sarebbe un Int?. Quindi T? sarebbe un Int??. Quando hai passato un normale Int? nello stesso if_let insieme a quel blocco, Swift lo aggiornava implicitamente su un Int?? per farlo compilare. Questo è essenzialmente ciò che sta accadendo con lo if let not_unwrapped_a:Int?.

Sono d'accordo, l'aggiornamento opzionale implicito a volte può essere sorprendente (ancora più sorprendente è che Swift aggiornerà le funzioni che restituiscono optionals, vale a dire se una funzione prende un (Int)->Int?, aggiornerà un (Int)->Int per restituire un opzionale). Ma presumibilmente il sentimento è la confusione potenziale ne vale la pena per la convenienza in questo caso.

* solo tipo di

4

Lo scopo del collegamento facoltativo è di controllare un facoltativo per non nulla, scartare e assegnare a un non facoltativo del tipo incluso nell'opzionale. Quindi secondo me il primo caso è il modo corretto di usarlo - l'unica variazione che vorrei usare è l'aggiunta di un cast opzionale (utile ad esempio quando l'opzionale contiene AnyObject).

Non vorrei usare il secondo caso, preferendo un cast optional:

if let unwrapped_a = a as? Int { ... } 

a meno che, come notato da @drewag nei commenti, il tipo è specificato in modo esplicito per evitare ambiguità quando non è chiaro.

A mio parere, il terzo caso dovrebbe generare un errore di compilazione. Non vedo alcun utilizzo del collegamento facoltativo per assegnare un facoltativo a un altro facoltativo. L'associazione facoltativa non è un'assegnazione in linea generica (come ad esempio if let x = 5 {...}), quindi concettualmente non dovrebbe funzionare se il lato sinistro è un optional - dovrebbe essere gestito allo stesso modo come se il lato destro non fosse opzionale (per cui la compilazione fallisce invece).

+3

È perfettamente valido e ragionevole utilizzare il secondo caso se si decide che è importante che il tipo sia lì per aiutare con la leggibilità. A volte non è chiaro quale sia il tipo di variabile quando si guarda al contesto localizzato e qualche annotazione aggiuntiva, anche se non necessaria, può essere utile per la leggibilità. Farlo come cast opzionale implicherebbe che non sia già di tipo 'Int'. Questo può rendere il codice più confuso. – drewag

+0

@drewag: Sono d'accordo che renderebbe il codice meno ambiguo, e sì, un cast viene solitamente interpretato come "può essere di un altro tipo". Tuttavia, e questa è solo una * preferenza personale *, non la userei perché penso che il codice circostante dovrebbe risolvere l'ambiguità. Comunque sto aggiornando la risposta, quello che dici è importante – Antonio

+0

In questo caso è confuso e forse anche inutile ma non penso che dovrebbe essere un errore del compilatore, Swift sta semplicemente applicando la sua logica di aggiornamento implicita per i valori agli optionals.Errori di involucro speciali potrebbero causare ancora più confusione attraverso l'incoerenza. –

Problemi correlati