2014-10-10 5 views
5

Diciamo che ho una funzione che ritorna facoltativa. nil se l'errore e il valore se il successo:rapido, unwrapping opzionale, inversione se condizione

func foo() -> Bar? { ... } 

posso utilizzare seguente codice per lavorare con questa funzione:

let fooResultOpt = foo() 
if let fooResult = fooResultOpt { 
    // continue correct operations here 
} else { 
    // handle error 
} 

Tuttavia ci sono alcuni problemi con questo approccio per qualsiasi codice non banale:

  1. Gestione errori eseguita alla fine ed è facile perdere qualcosa. È molto meglio, quando il codice di gestione degli errori segue la chiamata di funzione.

  2. Il codice di operazioni corretto è rientrato di un livello. Se abbiamo un'altra funzione da chiamare, dobbiamo indentare un'altra volta.

Con C di solito si potrebbe scrivere qualcosa di simile:

Bar *fooResult = foo(); 
if (fooResult == null) { 
    // handle error and return 
} 
// continue correct operations here 

ho trovato due modi per raggiungere simile stile di codice con Swift, ma non mi piace neanche.

let fooResultOpt = foo() 
if fooResult == nil { 
    // handle error and return 
} 
// use fooResultOpt! from here 
let fooResult = fooResultOpt! // or define another variable 

Se scriverò "!" ovunque, sembra solo un cattivo gusto per i miei gusti. Potrei introdurre un'altra variabile, ma anche questo non sembra buono. Idealmente mi piacerebbe vedere il seguente:

if !let fooResult = foo() { 
    // handle error and return 
} 
// fooResult has Bar type and can be used in the top level 

Mi sono perso qualcosa nella specifica O c'è qualche altro modo di scrivere del buon codice Swift cercando?

risposta

2

Le ipotesi sono corrette: non c'è una sintassi "negato se-lascia" in Swift.

Sospetto che una ragione potrebbe essere l'integrità della grammatica. In tutto Swift (e comunemente in altri linguaggi ispirati a C), se si dispone di un'istruzione che può associare simboli locali (ad esempio denominare nuove variabili e assegnare loro valori) e che può avere un corpo di blocco (ad esempio se, mentre, per), quelli i collegamenti sono collegati a detto blocco. Lasciando invece che un'istruzione di blocco leghi i simboli al suo ambito racchiudente, sarebbe incoerente.

È comunque una cosa ragionevole da pensare, tuttavia: mi raccomando filing a bug e vedo cosa fa Apple al riguardo.

2

Questo è ciò che il pattern matching è tutto, ed è lo strumento pensato per questo lavoro:

let x: String? = "Yes" 

switch x { 
case .Some(let value): 
    println("I have a value: \(value)") 
case .None: 
    println("I'm empty") 
} 

La forma if-let è solo una comodità per quando non hai bisogno di entrambe le gambe.

+0

Grazie, ma la soluzione non si discosta dalla sintassi if (lasciare value = x) {. ..} else {...} e ho tutti gli inconvenienti che ho menzionato nella domanda. – vbezhenar

1

Se ciò che si sta scrivendo è un insieme di funzioni che eseguono la stessa sequenza di trasformazione, ad esempio quando si elabora un risultato restituito da una chiamata REST (controllare la risposta non nil, controllare lo stato, controllare l'errore app/server, analizzare risposta, ecc.), quello che vorrei fare è creare una pipeline che ad ogni passaggio trasforma i dati di input e alla fine restituisce nil o un risultato trasformato di un determinato tipo.

ho scelto l'operatore >>> personalizzato, che indica visivamente il flusso di dati, ma ovviamente si sentono liberi di scegliere il proprio:

infix operator >>> { associativity left } 

func >>> <T, V> (params: T?, next: T -> V?) -> V? { 
    if let params = params { 
     return next(params) 
    } 
    return nil 
} 

L'operatore è una funzione che riceve in ingresso un valore di un certo tipo e una chiusura che trasforma il valore in un valore di un altro tipo. Se il valore non è nullo, la funzione richiama la chiusura, passa il valore e restituisce il valore restituito. Se il valore è nil, l'operatore restituisce nil.

Un esempio è probabilmente necessario, quindi supponiamo che ho un array di interi, e voglio eseguire le seguenti operazioni in sequenza:

  • somma tutti gli elementi dell'array
  • calcolare la potenza di 2
  • dividere per 5 e restituire la parte intera e la restante
  • somma quanto sopra 2 numeri insieme

Queste sono le 4 funzioni:

func sumArray(array: [Int]?) -> Int? { 
    if let array = array { 
     return array.reduce(0, combine: +) 
    } 

    return nil 
} 

func powerOf2(num: Int?) -> Int? { 
    if let num = num { 
     return num * num 
    } 
    return nil 
} 

func module5(num: Int?) -> (Int, Int)? { 
    if let num = num { 
     return (num/5, num % 5) 
    } 
    return nil 
} 

func sum(params: (num1: Int, num2: Int)?) -> Int? { 
    if let params = params { 
     return params.num1 + params.num2 
    } 
    return nil 
} 

e questo è come userei:

let res: Int? = [1, 2, 3] >>> sumArray >>> powerOf2 >>> module5 >>> sum 

Il risultato di questa espressione è o nullo o un valore del tipo definito nella ultima funzione di la pipeline, che nell'esempio precedente è un Int.

Se avete bisogno di fare meglio la gestione degli errori, è possibile definire un enum come questo:

enum Result<T> { 
    case Value(T) 
    case Error(MyErrorType) 
} 

e sostituire tutte optionals nelle funzioni di cui sopra con Result<T>, tornando Result.Error() invece di nil.

+0

Grazie per la risposta, questo è un buon approccio, ma sembra essere molto non-imperativo e difficile da estendere e modificare in generale. Potrebbe essere molto adatto per alcuni casi in cui questo flusso di dati sarà naturale. – vbezhenar

+0

Sì, non è un modo generalizzato e deriva dalla programmazione funzionale. Forse non è la soluzione giusta per il tuo problema specifico, ma spero che lo troverai utile in futuro ;-) – Antonio

1

Ho trovato un modo migliore delle alternative, ma utilizza le funzionalità del linguaggio in modo non consigliato.

esempio utilizzando il codice dalla questione:

let fooResult: Bar! = foo(); 
if fooResult == nil { 
    // handle error and return 
} 
// continue correct operations here 

fooResult potrebbe essere utilizzato come normale variabile e non è necessario per utilizzare "?" o "!" suffissi.

documentazione Apple dice:

optional Implicitamente da scartare sono utili quando il valore di un optional è confermata ad esistere immediatamente dopo opzionale viene inizialmente definita e può sicuramente considerarsi implicito in ogni punto in seguito. L'uso principale degli optionals implicitamente non aperti in Swift è durante l'inizializzazione della classe, come descritto in Riferimenti Unowned e Proprietà facoltative non esplicitamente liberate.

0

Come circa la seguente:

func foo(i:Int) ->Int? { 
    switch i { 
    case 0: return 0 
    case 1: return 1 
    default: return nil 
    } 
} 

var error:Int { 
    println("Error") 
    return 99 
} 

for i in 0...2 { 
    var bob:Int = foo(i) ?? error 
    println("\(i) produces \(bob)") 
} 

risultati nel seguente output:

0 produces 0 
1 produces 1 
Error 
2 produces 99