[swift-evolution] [Review] SE-0091: Improving operator requirements in protocols

Pyry Jahkola pyry.jahkola at iki.fi
Wed May 25 05:23:51 CDT 2016


> On 25 May 2016, at 12:21, Brent Royal-Gordon <brent at 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

-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.swift.org/pipermail/swift-evolution/attachments/20160525/c83992cf/attachment.html>


More information about the swift-evolution mailing list