// Check if the two values are Equatable and equal
func isEqual(_ lhs: Any, _ rhs: Any) -> Bool {
func f<LHS>(lhs: LHS) -> Bool {
if let typeInfo = Wrapped<LHS>.self as? AnyEquatable.Type {
return typeInfo.isEqual(lhs: lhs, rhs: rhs)
}
return false
}
return _openExistential(lhs, do: f)
}
protocol AnyEquatable {
static func isEqual(lhs: Any, rhs: Any) -> Bool
}
enum Wrapped<T> { }
extension Wrapped: AnyEquatable where T: Equatable {
static func isEqual(lhs: Any, rhs: Any) -> Bool {
guard let l = lhs as? T, let r = rhs as? T else {
return false
}
return l == r
}
}
I tried to modify the implementation to take advantage of features introduced in Swift 5.7, like unlocked existentials for all protocols and implicitly opened existential. I ended up with this implementation:
// Check if the two values are equatable and equal
func isEqual(_ lhs: Any, _ rhs: Any) -> Bool {
guard let lhs = lhs as? any Equatable else { return false }
func f<LHS: Equatable>(_ lhs: LHS) -> Bool {
guard let rhs = rhs as? LHS else { return false }
return lhs == rhs
}
return f(lhs)
}
Could this be made even simpler?
For context, this is the call site and suggestions on how to simplify could extend there as well:
func equalToPrevious(_ node: Node) -> Bool {
guard let previous = node.previousComponent as? Self else { return false }
let m1 = Mirror(reflecting: self)
let m2 = Mirror(reflecting: previous)
for pair in zip(m1.children, m2.children) {
guard pair.0.label == pair.1.label else { return false }
let p1 = pair.0.value
let p2 = pair.1.value
if !isEqual(p1, p2) { return false }
}
return true
}
func isEqual<A: Equatable>(_ a: A, _ b: Any) -> Bool {
(b as? A).map { a == $0 } ?? false
}
func isEqual(_ a: Any, _ b: Any) -> Bool {
(a as? any Equatable).map { isEqual($0, b) } ?? false
}
The two functions work in tandem. I was hoping that the compiler could select the first overload where at least a is Equatable but apparently it's not the case, it always calls the second one for some reason. But seems to work for any a and b.
You don't have to use an extension, but I think it's better to have it encapsulated. If you want correct overload resolution, don't use Any. There's generally no reason to use Any instead of some Any.
Ah, that's even shorter, nice. I don't know why some Any is better (let alone sounds weird in English ) but it still doesn't help the compiler with selecting a shorter path.
A word of caution, optional values with incomparable base types that are nil are always equal. So an implementation that would take this into account would be more wordy.
And yes, extensions are always better, you don't want to pollute the global name space, but in this case I was just following the original.
That does not match the behavior of Equatable in the presence of subclasses. Specifically, == may return true when comparing an instance of a subclass against an instance of a superclass (or another subclass). But the generic approach returns false when the second type cannot be converted to the first.
For example, given the following setup:
func isEqual<LHS: Equatable>(_ lhs: LHS, _ rhs: some Any) -> Bool {
lhs == rhs as? LHS
}
class A: Equatable {
var x: Int = 0
static func == (lhs: A, rhs: A) -> Bool {
return lhs.x == rhs.x
}
}
class B: A {}
class C: A {}
We get these results:
let a = A()
let b = B()
let c = C()
print(a == b, isEqual(a, b)) // true true
print(b == a, isEqual(b, a)) // true false
print(b == c, isEqual(b, c)) // true false
The second test could be fixed by testing lhs as? RHS as well.
But I can't think of a solution for the siblings case. If there's a (generic) way to type cast an object to it's super class, then we can try going up the hierarchy and try to cast again until we exhaust all ancestor types.