2015-10-30 54 views
11

Ho questa funzione:Aggiungere vincoli ai parametri generici in estensione

func flatten<Key: Hashable, Value>(dict: Dictionary<Key, Optional<Value>>) -> Dictionary<Key, Value> { 
    var result = [Key: Value]() 
    for (key, value) in dict { 
     guard let value = value else { continue } 
     result[key] = value 
    } 
    return result 
} 

Come si può vedere, si trasforma un dizionario [Key: Value?] in un [Key: Value] uno (senza l'opzione).

Desidero estendere la classe con un nuovo metodo solo per le classi il cui valore è un Optional di qualsiasi tipo, ma non riesco ad aggiungere vincoli ai parametri generici del dizionario.

Questo è quello che ho provato:

extension Dictionary where Value: Optional<Any> { 
    func flatten() -> [Key: Any] { 
     var result = [Key: Any]() 
     for (key, value) in self { 
      guard let value = value else { continue } 
      result[key] = value 
     } 
     return result 
    } 
} 

ma non riesce con l'errore:

Type 'Value' constrained to non-protocol type 'Optional<Any>' 

risposta

16

provare questo codice nel campo da giuoco:

// make sure only `Optional` conforms to this protocol 
protocol OptionalEquivalent { 
    typealias WrappedValueType 
    func toOptional() -> WrappedValueType? 
} 

extension Optional: OptionalEquivalent { 
    typealias WrappedValueType = Wrapped 

    // just to cast `Optional<Wrapped>` to `Wrapped?` 
    func toOptional() -> WrappedValueType? { 
    return self 
    } 
} 

extension Dictionary where Value: OptionalEquivalent { 
    func flatten() -> Dictionary<Key, Value.WrappedValueType> { 
    var result = Dictionary<Key, Value.WrappedValueType>() 
    for (key, value) in self { 
     guard let value = value.toOptional() else { continue } 
     result[key] = value 
    } 
    return result 
    } 
} 

let a: [String: String?] = ["a": "a", "b": nil, "c": "c", "d": nil] 
a.flatten() //["a": "a", "c": "c"] 

Poiché non è possibile specificare un tipo esatto nella clausola where di un'estensione di protocollo, in un modo è possibile rilevare esattamente loIl tipoè quello di rendere UNICO Optional conforme a un protocollo (ad esempio OptionalEquivalent).

Al fine di ottenere il tipo di valore avvolto del Optional, ho definito un typealias WrappedValueType nel protocollo personalizzato OptionalEquivalent e poi ha fatto un prolungamento della opzionale, assgin il Wrapped-WrappedValueType, allora si può ottenere il tipo nel metodo flatten .

Nota che il metodo sugarCast è solo quello di lanciare il Optional<Wrapped> a Wrapped? (che è esattamente la stessa cosa), per consentire l'utilizzo guard dichiarazione.

UPDATE

Grazie al commento di Rob Napier 's Ho semplificato & rinominato il metodo sugarCast() e rinominato il protocollo per renderlo più comprensibile.

+0

è più facile implementa 'sugarCast()' come 'return self'. Personalmente raccomando di chiamare questo protocollo 'OptionalConvertible' e' sugarCast() '' toOptional() ', ma anche il tuo modo di procedere è corretto. –

+0

Hai ragione, aggiornerò il mio codice – cezheng

+0

Questo è un modo molto creativo per risolvere i limiti generici. Probabilmente lo userò in più posti. Grazie – redent84

2

Puoi farlo in un modo più semplice. Questo funziona con Swift 4:

extension Dictionary { 
    func flatten<Wrapped>() -> [Key: Wrapped] where Value == Optional<Wrapped> { 
     return filter { $1 != nil }.mapValues { $0! } 
    } 
} 

Se non vi piace l'uso di funzioni di ordine superiore o bisogno di compatibilità con le versioni precedenti di Swift si può fare anche questo:

extension Dictionary { 
    func flatten<Wrapped>() -> [Key: Wrapped] where Value == Optional<Wrapped> { 
     var result: [Key: Wrapped] = [:] 
     for (key, value) in self { 
      guard let value = value else { continue } 
      result[key] = value 
     } 
     return result 
    } 
} 
+1

Più facile e leggibile rispetto alla risposta accettata – moskis