Swift Equableable Protocol

I don’t think it can be done, but I’ll ask anyway. I have a protocol:

protocol X {} 

And the class:

 class Y:X {} 

In the rest of my code, I refer to everything using the X protocol. In this code, I would like to do something like:

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

The problem is that if I try to implement 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 } 

The idea is to try and allow the use of == , hiding implementations behind the protocol.

Swift doesn't like this because Equatable has Self links, and it will no longer allow me to use it as a type. Only as a general argument.

So, did anyone find a way to apply the operator to the protocol without the protocol becoming unusable as a type?

+8
swift protocols
source share
6 answers

maybe this will be useful 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 
+3
source share

The reason you should think twice about Equatable is because in many cases this 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 refer to type checking in the implementation == , but the problem is that you have no information that either of them is not Pet , and you do not know everything that Pet can be (maybe you will add Bird and Rabbit later ) If you really need it, another approach might be to model 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 more like it.") } 

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

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

One thing you would have to observe in this case is inheritance. Since you can flush the inheriting type and delete information that may make isEqualTo not logical.

The best thing is to only implement equality for the class / structure itself and use a different type checking mechanism.

+3
source share

If you directly implement Equatable in a protocol, it will no longer be used as the type that wins for the purpose of using the protocol. Even if you simply implement == functions in protocols without Equatable , the results may be erroneous. See this blog post for a demonstration of these issues:

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

The approach that I found works best is to use type erasure. This allows comparisons == for protocol types (wrapped in eraser types). It is important to note that while we continue to work at the protocol level, actual comparisons == delegated to the base concrete types to ensure the correct results.

I built a type resistor using your brief example, and added the end code at the end. I added a constant of type String to the protocol and created two corresponding types (structures are the simplest for demonstration purposes) in order to be able to test various scripts.

A detailed description of the erasure methodology used can be found in the second part of the blog post above:

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

The code below should support the comparison of equality that you wanted to implement. You just need to wrap the protocol type in an instance of the type 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 

Note that since the type eraser conforms to the protocol, you can use instances of the type eraser anywhere where the protocol type instance is expected.

Hope this helps.

+3
source share

Not sure why all instances of your protocol should be Equatable , but I prefer classes to implement their equality methods.

In this case, I would leave the protocol simple:

 protocol MyProtocol { func doSomething() } 

If you require that an object that matches MyProtocol is also Equatable , you can use MyProtocol & Equatable as a 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 clear your specifications and allow subclasses to implement their equality method only if necessary.

+2
source share

I would still not recommend using == using polymorphism. This is a bit of a code smell. If you want to give the user of the framework something that he can verify equality, then you really have to vending a struct , not protocol . This does not mean that it cannot be the protocol that the struct crowns, though:

 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 

I think it conveys your intentions more efficiently, that basically "you have these things and you don’t know if they are the same, but you know that they have the same set of properties and you can check the properties are the same. " This is pretty close to how I could implement this Money example.

+2
source share

You need to implement a protocol extension with restrictions for your class type. Inside this extension, you must implement the Equatable operator.

 public protocol Protocolable: class, Equatable { // Other stuff here... } public extension Protocolable where Self: TheClass { public static func ==(lhs: Self, rhs:Self) -> Bool { return lhs.name == rhs.name } } public class TheClass: Protocolable { public var name: String public init(named name: String) { self.name = name } } let aClass: TheClass = TheClass(named: "Cars") let otherClass: TheClass = TheClass(named: "Wall-E") if aClass == otherClass { print("Equals") } else { print("Non Equals") } 

But let me recommend you add an operator implementation to your class. Keep It Simple; -)

+1
source share

All Articles