2017-08-30 3 views
1

j'ai écrit cette fonction d'utilité Swift 4:Comment puis-je traduire cette fonction d'utilitaire dans une fonction d'extension?

C'est utilisé comme ceci:

insert("foo", into: &myDictionary, at: "bar") 

... mais je veux l'utiliser comme ceci:

myDictionary.insert("foo", at: "bar") 

J'ai essayé de le déclarer comme ceci:

extension Dictionary where Value == Set<AnyHashable> { 
    mutating func insert(_ value: Value.Element, at key: Key) { // Error here 
     if let _ = self[key] { 
      self[key]?.insert(value) 
     } else { 
      var set = Set<Value.Element>() // Error here 
      set.insert(value) 
      self[key] = set 
     } 
    } 
} 

... mais je reçois les erreurs suivantes:

/path/to/Sequence Extensions.swift:2:41: error: 'Element' is not a member type of 'Dictionary.Value' 
    mutating func insert(_ value: Value.Element, at key: Key) { 
            ~~~~~^
Swift.Set:608:22: note: did you mean 'Element'? 
    public typealias Element = Element 
        ^
Swift._IndexableBase:3:22: note: did you mean '_Element'? 
    public typealias _Element = Self.Element 

/path/to/Sequence Extensions.swift:6:23: error: type 'Value.Element' does not conform to protocol 'Hashable' 
      var set = Set<Value.Element>() 
        ^

Répondre

3

Malheureusement, Swift ne prend pas en charge parameterised extensions (la possibilité d'introduire des variables de type dans les déclarations d'extension), de sorte que vous ne pouvez pas actuellement exprimer directement la notion de "une extension avec une contrainte à certains Set<T>". Cependant, c'est une partie du manifeste des génériques, donc j'espère que c'est quelque chose qui fait son chemin dans une future version de la langue.

Même si votre extension avec Value contraint à Set<AnyHashable> compilé, ce ne serait pas terriblement utile. Vous devez d'abord convertir le dictionnaire souhaité en un [Key: Set<AnyHashable>] temporaire, puis appeler la méthode de mutation, puis le reconvertir en son type d'origine (en utilisant as!). Ceci est dû au fait que l'extension est sur un Dictionary avec des valeurs Set hétérogènes. Il aurait été parfaitement légal que la méthode d'extension insère éléments arbitrairesHashable dans l'une des valeurs du dictionnaire. Mais ce n'est pas ce que vous vouliez exprimer.

Dans les cas simples, je dirais qu'il n'y a pas besoin d'une extension en premier lieu. Vous pouvez simplement dire:

var dict = [String: Set<String>]() 
dict["key", default: []].insert("someValue") 

en utilisant la surcharge de descripteur s » Dictionary qui prend une valeur par défaut, tel que présenté dans SE-0165.

Si vous voulez toujours une extension, je vous conseille simplement de la rendre plus générique. Par exemple, au lieu de limiter Value à Set; le contraindre au protocole SetAlgebra (auquel Set est conforme).

Il représente les types qui peuvent effectuer des opérations set-comme et découle également de ExpressibleByArrayLiteral ce qui signifie que vous pouvez mettre en œuvre votre méthode en utilisant la syntaxe exacte comme ci-dessus:

extension Dictionary where Value : SetAlgebra { 

    mutating func insert(_ value: Value.Element, at key: Key) { 
     self[key, default: []].insert(value) 
    } 
} 

Bien une chose supplémentaire à considérer ici est la Comportement de copie en écriture des types de collection de Swift tels que Set. Dans la méthode ci-dessus, le dictionnaire sera interrogé pour une clé donnée, donnant soit un ensemble existant pour cette clé, soit un nouveau vide. Votre value sera ensuite inséré dans cet ensemble temporaire, et il sera réinséré dans le dictionnaire.L'utilisation d'un temporaire signifie que si l'ensemble est déjà dans le dictionnaire, le value ne sera pas inséré dans le dictionnaire, le tampon de l'ensemble sera copié en premier afin de préserver la sémantique des valeurs; ce qui pourrait être un problème de performance (ceci est exploré plus en détail dans this Q&A et this Q&A).

Cependant, cela étant dit, je cherche actuellement à résoudre ce problème pour Dictionarysubscript(_:default:)in this pull request, de sorte que l'ensemble peut être muté en place.

Jusqu'à ce que fixe, la solution est d'abord supprimer l'ensemble du dictionnaire avant de muter:

extension Dictionary where Value : SetAlgebra { 

    mutating func insert(_ value: Value.Element, at key: Key) { 
     var set = removeValue(forKey: key) ?? [] 
     set.insert(value) 
     self[key] = set 
    } 
} 

Dans ce cas, l'utilisation d'une extension est pleinement justifiée.

Il est à noter que l'utilisation d'une contrainte de protocole ici est la solution générale (ou solution de contournement dans certains cas) au problème de ne pas avoir d'extensions paramétrées. Il vous permet de réaliser les espaces réservés dont vous avez besoin en tant que types associés de ce protocole. Voir this Q&A pour un exemple de la façon dont vous pouvez créer votre propre protocole à cette fin.

1

Vous pouvez le faire en utilisant un protocole pour identifier des ensembles:

protocol SetType 
{ 
    associatedtype Element:Hashable 
    init() 
    mutating func insert(_ : Element) -> (inserted: Bool, memberAfterInsert: Element) 
} 

extension Set:SetType 
{} 

extension Dictionary where Value : SetType 
{ 
    mutating func insert(_ value:Value.Element, at key:Key) 
    { 
     var valueSet:Value = self[key] ?? Value() 
     valueSet.insert(value) 
     self[key] = valueSet 
    } 
} 

var oneToMany:[String:Set<String>] = [:] 

oneToMany.insert("Dog", at: "Animal") 
oneToMany.insert("Cat", at: "Animal") 
oneToMany.insert("Tomato", at: "Vegetable") 

Cela produira un dictionnaire de jeux:

["Animal": Set(["Dog", "Cat"]), "Vegetable": Set(["Tomato"])] 

Une mise en œuvre plus appropriée utiliserait la même valeur de retour comme un ensemble de insérer la fonction() cependant:

extension Dictionary where Value : SetType 
{ 
    @discardableResult 
    mutating func insert(_ value:Value.Element, at key:Key) -> (inserted: Bool, memberAfterInsert: Value.Element) 
    { 
     var valueSet:Value = self[key] ?? Value() 
     let result = valueSet.insert(value) 
     if result.inserted 
     { self[key] = valueSet } 
     return result 
    } 
} 

[EDIT] Je viens de lire tout de une réponse de Hamish il s'est rendu compte qu'il avait déjà donné la même réponse (essentiellement) et a utilisé SetAlgebra (que je ne connaissais pas) qui fait la même chose que le SetType I "réinventé". Vous devriez accepter la réponse de Hamish.