1

Can anyone shed a light as to why this doesn't work? I get an error Binary operator '==' cannot be applied to operands of type 'Self' and 'CustomEquatable'

protocol CustomEquatable {
    func isEqualTo(_ other: CustomEquatable) -> Bool
}

extension CustomEquatable where Self: Equatable {
    func isEqualTo(_ other: CustomEquatable) -> Bool {
        return self == other
    }
}
Valentin
  • 1,731
  • 2
  • 19
  • 29
  • 1
    The issue is that just because `self` is equatable, and `other` is a `CustomEquatable` (which implies that it too is `Equatable`), that doesn't mean that the type of `self` is the same as the type of `other`. For example, if both `Int` and `String` were extended to conform to `CustomEquatable`, then `1.isEqualTo("a")` would call `1 == "a"`, which does not type check, because `==` requires LHS and RHS to have the same type. – Alexander Oct 22 '18 at 18:18
  • To provide a solution, we would need to know what you're trying to achieve with this `CustomEquatable` protocol. Is this your attempt to try and resolve the dreaded "protocol 'Equatable' can only be used as a generic constraint because it has Self or associated type requirements" error? – Alexander Oct 22 '18 at 18:19
  • @Alexander yes, yes I am. – Valentin Oct 22 '18 at 18:21
  • 1
    Here's a proper implementation of an `AnyEquatable` type eraser: https://stackoverflow.com/a/46288607/3141234 – Alexander Oct 22 '18 at 18:22
  • The trick is that the initializer is generic, but the type is not. – Alexander Oct 22 '18 at 18:22
  • 1
    @Alexander: *"other is a CustomEquatable (which implies that it too is Equatable)"* – Unless I am mistaken, `other` can be a *different* type adopting CustomEquatable, and need not be Equatable. – Martin R Oct 22 '18 at 18:23
  • @MartinR Oh yes, indeed, you're correct. It just won't have this default impl provided, but that's irrelevant. My reasoning thereafter is correct, however. The type of `self` and `other` will differ, leading to an illegal call of `==`. – Alexander Oct 22 '18 at 18:25

2 Answers2

2

Let's start with your CustomEquatable protocol, without the extension:

protocol CustomEquatable {
    func isEqualTo(_ other: CustomEquatable) -> Bool
}

Let's define some types to use for experiments:

struct A: Equatable {
    let name: String
}

struct B: Equatable {
    let id: Int
}

Suppose we then want A and B to conform to CustomEquatable. Then we have four cases to consider:

  • What does a1.isEqualTo(a2) mean (where a1 and a2 are both of type A)?
  • What does b1.isEqualTo(b2) mean (where b1 and b2 are both of type B)?
  • What does a.isEqualTo(b) mean (where a is an A and b is a B)?
  • What does b.isEqualTo(a) mean (where b is a B and a is an A)?

For the first two cases, possible answers are that a1.isEqualTo(a2) if and only if a1 == a2 and b1.isEqualTo(b2) if and only if b1 == b2.

For the second two cases, we have to decide if there's a way for an A to equal a B. The simplest solution (I think) is that an A can never equal a B.

So we can write the conformances like this:

extension A: CustomEquatable {
    func isEqualTo(_ other: CustomEquatable) -> Bool {
        return (other as? A) == self
    }
}

extension B: CustomEquatable {
    func isEqualTo(_ other: CustomEquatable) -> Bool {
        return (other as? B) == self
    }
}

The only difference in these two conformances is the cast-to type (on the right side of as?). So we can factor out the conformances into a protocol extension like this:

extension CustomEquatable where Self: Equatable {
    func isEqualTo(_ other: CustomEquatable) -> Bool {
        return (other as? Self) == self
    }
}

With this protocol extension, we can make A and B conform to CustomEquatable without implementing isEqualTo for each:

extension A: CustomEquatable { }
extension B: CustomEquatable { }

To test the code:

let a1 = A(name: "a1")
let a2 = A(name: "a2")
let b1 = B(id: 1)
let b2 = B(id: 2)

a1.isEqualTo(a1) // true
a1.isEqualTo(a2) // false
b1.isEqualTo(b1) // true
b1.isEqualTo(b2) // false
a1.isEqualTo(b1) // false
b1.isEqualTo(a1) // false
rob mayoff
  • 375,296
  • 67
  • 796
  • 848
1

Please watch WWDC 2015 Protocol-Oriented Programming in Swift from 37:25

This is almost literally taken from the video. You have to conditional downcast other to Self.
If it's the same type you can use == otherwise both objects are not equal anyway.

protocol CustomEquatable {
    func isEqualTo(_ other: CustomEquatable) -> Bool
}
extension CustomEquatable where Self: Equatable {
    func isEqualTo(_ other: CustomEquatable) -> Bool {
        if let other = other as? Self { return self == other }
        return false
    }
}
vadian
  • 274,689
  • 30
  • 353
  • 361