Swift Equatable on a protocol

前端 未结 10 2103
时光说笑
时光说笑 2021-01-30 12:42

I don\'t think this can be done but I\'ll ask anyway. I have a protocol:

protocol X {}

And a class:

class Y:X {}
相关标签:
10条回答
  • 2021-01-30 13:28

    Not sure why you need all instances of your protocol to conform to Equatable, but I prefer letting classes implement their equality methods.

    In this case, I'd leave the protocol simple:

    protocol MyProtocol {
        func doSomething()
    }
    

    If you require that an object that conforms to MyProtocol is also Equatable you can use MyProtocol & Equatable as type constraint:

    // 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()
        }
    }
    

    This way you can keep your specification clear and let subclasses implement their equality method only if required.

    0 讨论(0)
  • 2021-01-30 13:29

    maybe this will be helpful for you:

    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
    
    0 讨论(0)
  • 2021-01-30 13:32

    All people who say that you can't implement Equatable for a protocol just don't try hard enough. Here is the solution (Swift 4.1) for your protocol X example:

    protocol X: Equatable {
        var something: Int { get }
    }
    
    // Define this operator in the global scope!
    func ==<L: X, R: X>(l: L, r: R) -> Bool {
        return l.something == r.something
    }
    

    And it works!

    class Y: X {
        var something: Int = 14
    }
    
    struct Z: X {
        let something: Int = 9
    }
    
    let y = Y()
    let z = Z()
    print(y == z) // false
    
    y.something = z.something
    pirnt(y == z) // true
    

    The only problem is that you can't write let a: X = Y() because of "Protocol can only be used as a generic constraint" error.

    0 讨论(0)
  • 2021-01-30 13:33

    The reason why you should think twice about having a protocol conform to Equatable is that in many cases it just doesn't make sense. Consider this example:

    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??")
    }
    

    You allude to type checking within the implementation of == but the problem is that you have no information about either of the types beyond them being Pets and you don't know all the things that might be a Pet (maybe you will add a Bird and Rabbit later). If you really need this, another approach can be modeling how languages like C# implement equality, by doing something like:

    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.")
    }
    

    At which point if you really wanted, you could implement == without implementing Equatable:

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

    One thing you would have to watch out for in this case is inheritance though. Because you could downcast an inheriting type and erase the information that might make isEqualTo not make logical sense.

    The best way to go though is to only implement equality on the class/struct themselves and use another mechanism for type checking.

    0 讨论(0)
提交回复
热议问题