> On 25 May 2016, at 12:21, Brent Royal-Gordon <br...@architechies.com> wrote:
> 
>> Brent, I think it's even slightly more complicated than that. Think e.g. how 
>> two subclass instances of NSArray should compare equal even if they've got 
>> different types. (I really dislike class inheritance for these reasons, but 
>> below is my take on how we could better live with the issue in Swift.)
> 
> If you're referring to this:
> 
>>>     guard let other = other as? Self else {
>>>             return super == other
>>>     }
> 
> I probably should have said `#Self`, meaning e.g. `NSArray` if you're 
> implementing `NSArray.==`. What you really want to test for there is the 
> *static* type of `self`, not the dynamic type.
> 
> If you're referring to the multiple dispatch thing, I actually think that 
> will handle subclassing perfectly well. If you have a class hierarchy like 
> this, with each class implementing its own `(Self, Self)` equality operation:
> 
>       NSObject
>               NSArray
>                       MyArray
>                       MyOtherArray
> 
> Then using `==` on `MyArray` and `MyOtherArray` should use `(NSArray, 
> NSArray).==()`, which presumably would compare the length and elements to 
> test for equality.

It's kind of hard to explain without working code so here's a sample to play 
with: 
http://swiftlang.ng.bluemix.net/#/repl/c1ddd24113169ab82df118660c8e0de6ea24e48d32997c327638a88dc686e91f
 
<http://swiftlang.ng.bluemix.net/#/repl/c1ddd24113169ab82df118660c8e0de6ea24e48d32997c327638a88dc686e91f>.
 Use the `#if true` line to toggle between the implementations. The core of the 
issue—which I think will also happen if we ever gain the ability to "open" 
existentials—is at the point where we cast the right-hand side to Self:

struct AnyEquatable : Equatable {
    let value: Any
    let isEqual: (AnyEquatable) -> Bool
    init<T : Equatable>(_ value: T) {
        self.value = value
        self.isEqual = {r in
            guard let other = r.value as? T.EqualSelf else { return false }
            return value == other
        }
    }
}

func == (l: AnyEquatable, r: AnyEquatable) -> Bool {
    return l.isEqual(r)
}

See the cast `r.value as? T.EqualSelf`, or `r.value as? T` like it would go for 
the stdlib Equatable. When `T` is MyArray or MyOtherArray and the erased type 
of `r.value` is not, the cast will fail.

>> For the very example of Equatable and Foundation classes, we would get the 
>> right behaviour for NSObject's `isEqual` by changing the definition of 
>> Equatable into:
>> 
>>    protocol Equatable {
>>        associatedtype EqualSelf = Self // the default is ok pretty much 
>> always
>>        func == (lhs: Self, rhs: EqualSelf) -> Bool
>>        func != (lhs: Self, rhs: EqualSelf) -> Bool
>>    }
>> 
>> This way, the compiler would always be looking for the `(Self, NSObject) -> 
>> Bool` shape of operation, which actually picks up statically the correct 
>> overload for `lhs.isEqual(rhs)`.
> 
> But you would need to do this for all operators. A protocol that requires `<` 
> or `+` would need to de-privilege the right-hand side in exactly the same way.


That's true. Maybe if we want to take this issue into account we should name 
`EqualSelf` more generally. Or then we can just shrug away the problem and 
document it. It's not something I tend to face in real work, partly because I 
try to avoid inheritance as much as possible, but I know it's there.

— Pyry

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

Reply via email to