2015-10-30 22 views
11

mam tej funkcji:Dodaj do ograniczenia parametrów generycznych w rozszerzeniu

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 
} 

Jak widać, przekształca słownika [Key: Value?] do [Key: Value] jeden (bez opcjonalnego).

Chciałem rozszerzyć klasę Dictionary o nową metodę tylko dla klas o wartości Optional dowolnego typu, ale nie mogę dodać ograniczeń do ogólnych parametrów słownika.

To co próbowałem:

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 
    } 
} 

Ale nie powiedzie się z powodu błędu:

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

Odpowiedz

16

wypróbować ten kod w Playground:

// 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"] 

Ponieważ nie można określić dokładny typ w klauzuli where rozszerzenia protokołu, w jeden sposób można dokładnie wykryćTypma sprawić, że Optional UNIQUELY jest zgodny z protokołem (na przykład OptionalEquivalent).

W celu uzyskania owinięte typ wartość Optional, ja zdefiniował typealias WrappedValueType w protokole niestandardowego OptionalEquivalent a następnie dokonał rozszerzenia Opcjonalnie, assgin na Wrapped do WrappedValueType, wtedy można uzyskać typu w metodzie spłaszczyć .

Należy zauważyć, że metoda sugarCast jest po prostu rzucić Optional<Wrapped> do Wrapped? (co jest dokładnie to samo), aby umożliwić korzystanie guard oświadczenie.

UPDATE

Dzięki komentarzu Rob Napier „s Mam uproszczone & przemianowany metody sugarCast() i przemianowany protokół aby uczynić ją bardziej zrozumiałe.

+0

można łatwiej implementuj 'sugarCast()' jako 'return self'. Osobiście polecam wywoływanie tego protokołu 'OpcjonalnyConvertible' i' sugarCast() '' toOptional() ', ale twój sposób też jest w porządku. –

+0

Masz rację, zaktualizuję mój kod – cezheng

+0

To bardzo kreatywny sposób obejścia ogólnych ograniczeń. Prawdopodobnie użyję go w wielu miejscach. Dzięki – redent84

2

Możesz to zrobić w prostszy sposób. Działa to z Swift 4:

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

Jeśli nie podoba ci się użycie funkcja wyższego rzędu lub potrzebujesz kompatybilności z poprzednimi wersjami Swift można to zrobić również:

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

Bardziej łatwe i czytelne niż zaakceptowana odpowiedź – moskis