64 votes

Swift Equatable sur un protocole

Je ne pense pas que cela peut être fait, mais je vais la poser quand même. J'ai un protocole:

protocol X {}

Et une classe:

class Y:X {}

Dans le reste de mon code je me réfère à tout en utilisant le protocole X. Dans ce code, je voudrais être en mesure de faire quelque chose comme:

let a:X = ...
let b:X = ...
if a == b {...}

Le problème est que si j'essaie de mettre en oeuvre Equatable:

protocol X: Equatable {}
func ==(lhs:X, hrs:X) -> Bool {
    if let l = lhs as? Y, let r = hrs as? Y {
        return l.something == r.something
    }
    return false
} 

L'idée de l'essayer et de permettre l'utilisation d' == tout en cachant les implémentations derrière le protocole.

Swift n'aime pas cela, mais parce que Equatable a Self références et de ne plus me permettre de l'utiliser comme un type. Seulement comme un argument générique.

Si quelqu'un a trouvé un moyen pour appliquer un opérateur à un protocole sans le protocole de devenir inutilisable en tant que type?

50voto

Khawer Khaliq Points 216

Si vous mettre directement en œuvre l' Equatable sur un protocole, il ne sera pas plus être utilisable comme un type, qui va à l'encontre de l'objectif de l'utilisation d'un protocole. Même si vous venez de mettre en oeuvre == fonctions sur les protocoles sans Equatable de conformité, les résultats peuvent être erronés. Voir ce post sur mon blog pour une démonstration de ces questions:

https://khawerkhaliq.com/blog/swift-protocols-equatable-part-one/

La solution que j'ai trouvé le mieux est d'utiliser le type de l'effacement. Cela permet de formuler == comparaisons pour les types de protocole (enveloppé dans le type de gommes à effacer). Il est important de noter que, tandis que nous continuons à travailler au niveau du protocole, le réel == comparaisons sont délégués de la sous-types de béton pour garantir des résultats corrects.

J'ai construit un type de gomme à l'aide de votre exemple et ajouté un peu de code de test à la fin. J'ai ajouté une constante de type String pour le protocole et a créé deux conformes types (les structures sont les plus faciles pour fins de démonstration) pour être en mesure de tester les différents scénarios.

Pour une explication détaillée du type d'effacement de la méthodologie utilisée, vérifiez la partie deux de ce qui précède post de blog:

https://khawerkhaliq.com/blog/swift-protocols-equatable-part-two/

Le code ci-dessous devrait soutenir la comparaison d'égalité que vous avez voulu mettre en œuvre. Vous avez juste à envelopper le type de protocole dans un type de gomme à l'instance.

protocol X {
    var name: String { get }
    func isEqualTo(_ other: X) -> Bool
    func asEquatable() -> AnyEquatableX
}

extension X where Self: Equatable {
    func isEqualTo(_ other: X) -> Bool {
        guard let otherX = other as? Self else { return false }
        return self == otherX
    }
    func asEquatable() -> AnyEquatableX {
        return AnyEquatableX(self)
    }
}

struct Y: X, Equatable {
    let name: String
    static func ==(lhs: Y, rhs: Y) -> Bool {
        return lhs.name == rhs.name
    }
}

struct Z: X, Equatable {
    let name: String
    static func ==(lhs: Z, rhs: Z) -> Bool {
        return lhs.name == rhs.name
    }
}

struct AnyEquatableX: X, Equatable {
    var name: String { return value.name }
    init(_ value: X) { self.value = value }
    private let value: X
    static func ==(lhs: AnyEquatableX, rhs: AnyEquatableX) -> Bool {
        return lhs.value.isEqualTo(rhs.value)
    }
}

// instances typed as the protocol
let y: X = Y(name: "My name")
let z: X = Z(name: "My name")
let equalY: X = Y(name: "My name")
let unequalY: X = Y(name: "Your name")

// equality tests
print(y.asEquatable() == z.asEquatable())           // prints false
print(y.asEquatable() == equalY.asEquatable())      // prints true
print(y.asEquatable() == unequalY.asEquatable())    // prints false

A noter que depuis le type de gomme est conforme au protocole, vous pouvez utiliser des instances du type gomme n'importe où une instance du type de protocole est prévu.

Espérons que cette aide.

19voto

Scott H Points 1079

La raison pour laquelle vous devriez penser deux fois avant d'avoir un protocole conforme à l' Equatable , c'est que dans de nombreux cas, il n'a tout simplement pas de bon sens. Considérons cet exemple:

protocol Pet: Equatable {
  var age: Int { get }
}

extension Pet {
  static func == (lhs: Pet, rhs: Pet) -> Bool {
    return lhs.age == rhs.age
  }
}

struct Dog: Pet {
  let age: Int
  let favoriteFood: String
}

struct Cat: Pet {
  let age: Int
  let favoriteLitter: String
}

let rover: Pet = Dog(age: "1", favoriteFood: "Pizza")
let simba: Pet = Cat(age: "1", favoriteLitter: "Purina")

if rover == simba {
  print("Should this be true??")
}

Vous faites allusion à la vérification de type à l'intérieur de la mise en œuvre de l' == mais le problème, c'est que vous n'avez aucune information à propos des types au-delà de leur cours d' Pets et vous ne savez pas toutes les choses qui pourraient être un Pet (peut-être que vous allez ajouter un Bird et Rabbit plus tard). Si vous avez vraiment besoin de ceci, une autre approche peut être de la modélisation de la façon dont les langages tels que C# mettre en œuvre l'égalité, en faisant quelque chose comme:

protocol IsEqual {
  func isEqualTo(_ object: Any) -> Bool
}

protocol Pet: IsEqual {
  var age: Int { get }
}

struct Dog: Pet {
  let age: Int
  let favoriteFood: String

  func isEqualTo(_ object: Any) -> Bool {
    guard let otherDog = object as? Dog else { return false }

    return age == otherDog.age && favoriteFood == otherDog.favoriteFood
  }
}

struct Cat: Pet {
  let age: Int
  let favoriteLitter: String

  func isEqualTo(_ object: Any) -> Bool {
    guard let otherCat = object as? Cat else { return false }

    return age == otherCat.age && favoriteLitter == otherCat.favoriteLitter
  }
}

let rover: Pet = Dog(age: "1", favoriteFood: "Pizza")
let simba: Pet = Cat(age: "1", favoriteLitter: "Purina")

if !rover.isEqualTo(simba) {
  print("That's more like it.")
}

À quel point, si vous le voulez bien, vous pourriez mettre en oeuvre == sans la mise en œuvre de Equatable:

static func == (lhs: IsEqual, rhs: IsEqual) -> Bool { return lhs.isEqualTo(rhs) }

Une chose que vous avez à regarder dehors pour dans ce cas est l'héritage que. Parce que vous pourriez abattu un hériter du type et de supprimer les informations qui pourraient faire de isEqualTo pas logique.

La meilleure façon de le faire est de ne mettre en œuvre l'égalité sur la class/struct eux-mêmes et d'utiliser un autre mécanisme de vérification de type.

8voto

peut-être que cela vous sera utile:

 protocol X:Equatable {
    var name: String {get set}

}

extension X {
    static func ==(lhs: Self, rhs: Self) -> Bool {
        return lhs.name == rhs.name
    }
}

struct Test : X {
    var name: String
}

let first = Test(name: "Test1")
let second = Test(name: "Test2")

print(first == second) // false
 

5voto

redent84 Points 4215

Pas sûr pourquoi vous avez besoin de toutes les instances de votre protocole pour se conformer à l' Equatable, mais je préfère laisser les classes de mettre en œuvre l'égalité des méthodes.

Dans ce cas, je vous laisse le protocole simple:

protocol MyProtocol {
    func doSomething()
}

Si vous avez besoin qu'un objet conforme à l' MyProtocol également Equatable vous pouvez utiliser MyProtocol & Equatable type de contrainte:

// Equivalent: func doSomething<T>(element1: T, element2: T) where T: MyProtocol & Equatable {
func doSomething<T: MyProtocol & Equatable>(element1: T, element2: T) {
    if element1 == element2 {
        element1.doSomething()
    }
}

De cette façon, vous pouvez garder votre cahier des charges clair et de laisser les sous-classes de mettre en œuvre leur égalité méthode uniquement si nécessaire.

3voto

Scott H Points 1079

J'ai toujours le conseiller à l'encontre de la mise en œuvre de == utilisation du polymorphisme. C'est un peu une odeur de code. Si vous voulez donner le cadre de l'utilisateur de quelque chose, il peut tester l'égalité avec alors vous devriez vraiment être un distributeur de struct, pas un protocol. Cela ne veut pas dire qu'il ne peut pas être l' protocols qui sont des distributeurs de l' structs si:

struct Info: Equatable {
  let a: Int
  let b: String

  static func == (lhs: Info, rhs: Info) -> Bool {
    return lhs.a == rhs.a && lhs.b == rhs.b
  }
}

protocol HasInfo {
  var info: Info { get }
}

class FirstClass: HasInfo {
  /* ... */
}

class SecondClass: HasInfo {
  /* ... */
}

let x: HasInfo = FirstClass( /* ... */ )
let y: HasInfo = SecondClass( /* ... */ )

print(x == y) // nope
print(x.info == y.info) // yep

Je pense que ce plus efficacement communique votre intention, qui est en gros "vous avez ces choses et vous ne savez pas si ce sont les mêmes choses, mais vous ne savez qu'ils ont le même ensemble de propriétés et vous pouvez tester si ces propriétés sont les mêmes." C'est assez proche de la façon dont je voudrais mettre en œuvre qu' Money exemple.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X