2014-11-01 11 views
9

Ho avuto difficoltà a implementare correttamente il protocollo ForwardIndexType per un enum, in particolare la gestione del caso finale (vale a dire per l'ultimo elemento senza un successore). Questo protocollo non è veramente coperto nel libro Swift Language.Implementazione di un enum ForwardIndexType

Ecco un semplice esempio

enum ThreeWords : Int, ForwardIndexType { 
    case one=1, two, three 

    func successor() ->ThreeWords { 
      return ThreeWords(rawValue:self.rawValue + 1)! 
    } 
} 

La funzione successor() restituirà il valore enumeratore successivo, eccetto per l'ultimo elemento, dove sarà sicuro con un'eccezione, perché non v'è alcun valore dopo .three

Il ForwardTypeProtocol non consente a successor() di restituire un valore condizionale, quindi non sembra esserci alcun modo di segnalare che non vi è alcun successore.

Ora, usando questo in un ciclo for per scorrere l'intervallo chiuso di tutti i possibili valori di un'enumerazione si corre in un problema per il caso fine:

for word in ThreeWords.one...ThreeWords.three { 
    print(" \(word.rawValue)") 
} 
println() 

//Crashes with the error: 

fatal error: unexpectedly found nil while unwrapping an Optional value 

Swift chiama inspiegabilmente la funzione successor() di il valore finale dell'intervallo, prima di eseguire le istruzioni nel ciclo for. Se il campo viene lasciato socchiusa ThreeWords.one..<ThreeWords.three quindi il codice viene eseguito correttamente, la stampa 1 2

Se modifico la funzione successore in modo che non cerca di creare un valore più grande di .three come questo

func successor() ->ThreeWords { 
     if self == .three { 
      return .three 
     } else { 
      return ThreeWords(rawValue:self.rawValue + 1)! 
     } 
    } 

Poi il per loop non si blocca, ma manca anche l'ultima iterazione, stampando come se la gamma fosse semiaperta 1 2

La mia conclusione è che c'è un errore in iterazione del ciclo di swift; non dovrebbe chiamare successor() sul valore finale di un intervallo chiuso. In secondo luogo, il ForwardIndexType dovrebbe essere in grado di restituire un optional, per poter segnalare che non esiste un successore per un certo valore.

Qualcuno ha avuto più successo con questo protocollo?

risposta

2

Infatti, sembra che successor sarà chiamato sull'ultimo valore.

Si potrebbe desiderare di file a bug, ma per ovviare a questo si potrebbe semplicemente aggiungere un valore sentinella di agire come un successore.

2

Sembra, ... operatore

func ...<Pos : ForwardIndexType>(minimum: Pos, maximum: Pos) -> Range<Pos> 

chiamate maximum.successor(). Costruisce Range<T> come

Range(start: minimum, end: maximum.successor()) 

Quindi, se si desidera utilizzare come enumRange.Index, è necessario definire la prossima dell'ultimo valore.

enum ThreeWords : Int, ForwardIndexType { 
    case one=1, two, three 
    case EXHAUST 

    func successor() ->ThreeWords { 
     return ThreeWords(rawValue:self.rawValue + 1) ?? ThreeWords.EXHAUST 
    } 
} 
+1

che risucchia perché ora tutte le mie istruzioni switch enum sono infettati con un caso predefinito richiesto – nielsbot

2

Questa è una vecchia domanda, ma vorrei riassumere alcune cose e postare un'altra possibile soluzione.

Come @ Jtbandes e @rintaro già detto intervallo chiuso creato con l'operatore start...end viene internamente creato con start..<end.successor() AFAIK questo è un comportamento intenzionale Swift.

In molti casi è possibile anche utilizzare un Interval in cui si pensava di usare un Range o dove Swift ha dichiarato un gamma per impostazione predefinita. Il punto qui è che gli intervalli non sono raccolte.

Quindi questo è non possibile con Intervalli

for word in ThreeWords.one...ThreeWords.three {...} 

================

Per il seguente Suppongo che il frammento di codice era solo un caso di debug per verificare i valori.

Per dichiarare un intervallo è necessario specificare esplicitamente il tipo. O un HalfOpenInterval (..<) o un ClosedInterval (...)

var interval:ClosedInterval = ThreeWords.one...ThreeWords.four 

Ciò richiede di rendere il vostro enumerazione Comparable. Anche se è IntComparable già, è ancora necessario aggiungere alla lista eredità

enum ThreeWords : Int, ForwardIndexType, Comparable { 
    case one=1, two, three, four 

    func successor() ->ThreeWords { 
     return ThreeWords(rawValue:self.rawValue + 1)! 
    } 
} 

E infine l'enumerazione necessità di conformarsi alle Comparable. Questo è un approccio generico dal momento che il conteggio è inoltre conforme al protocollo RawRepresentable

func <<T: RawRepresentable where T.RawValue: Comparable>(lhs: T, rhs: T) -> Bool { 
    return lhs.rawValue < rhs.rawValue 
} 

Come ho scritto non si può scorrere su di esso in un ciclo più, tuttavia è possibile un rapido controllo incrociato con un interruttore:

var interval:ClosedInterval = ThreeWords.one...ThreeWords.four 
switch(ThreeWords.four) { 
    case ThreeWords.one...ThreeWords.two: 
     print("contains one or two") 
    case let word where interval ~= word: 
     print("contains: \(word) with raw value: \(word.rawValue)") 
    default: 
     print("no case") 
} 

stampe "contiene: quattro con valore grezzo: 4"