If you directly implement Equatable
on a protocol, it will not longer be usable as a type, which defeats the purpose of using a protocol. Even if you just implement ==
functions on protocols without Equatable
conformance, results can be erroneous. See this post on my blog for a demonstration of these issues:
https://khawerkhaliq.com/blog/swift-protocols-equatable-part-one/
The approach that I have found to work best is to use type erasure. This allows making ==
comparisons for protocol types (wrapped in type erasers). It is important to note that while we continue to work at the protocol level, the actual ==
comparisons are delegated to the underlying concrete types to ensure correct results.
I have built a type eraser using your brief example and added some test code at the end. I have added a constant of type String
to the protocol and created two conforming types (structs are the easiest for demonstration purposes) to be able to test the various scenarios.
For a detailed explanation of the type erasure methodology used, check out part two of the above blog post:
https://khawerkhaliq.com/blog/swift-protocols-equatable-part-two/
The code below should support the equality comparison that you wanted to implement. You just have to wrap the protocol type in a type eraser 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 an instance of the protocol type is expected.
Hope this helps.
isEqual
option (from Java) but was hoping to keep it simple because I have a case where I have different classes that represent the same contextural thing and therefore I would them to be regarded as equal even though they are different implementations. – Deutoplasm