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

Pyry Jahkola pyry.jahkola at iki.fi
Wed May 25 04:07:25 CDT 2016


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.)

> On 25 May 2016, at 11:15, Brent Royal-Gordon via swift-evolution <swift-evolution at swift.org> wrote:
> 
>>> As Brent pointed out in his reply, without multiple dispatch, you don't really benefit from privileging the lhs argument, and in fact you can end up in situations where the behavior is surprising if you don't implement both orders. For example, in your (NSString, NSURL) example, each class would have to be extended to explicitly support comparison with the other in order to support commutativity if equality was an instance method. I'm not sure that's any better for those particular types than just having the operators global in the first place.
>> 
>> I assume you’d still have to implement it with NSObject as the “other” type, like you do in Objective-C. You’d just return false in that case.
> 
> It is rather unfortunate, though, that your `(Self, Self)` operator essentially becomes `(Self, NSObject)` once `NSObject` conforms. I mean, Swift will at least *tell* you this is happening and force you to write an `other as? Self` test, but the way the type of the right-hand side gets pinned down while the left-hand side stays unspecified is just strange.
> 
> (Incidentally, I think rather than returning `false` here, you probably ought to say:
> 
> 	guard let other = other as? Self else {
> 		return super == other
> 	}
> 
> Would you be able to use `super` in that way? You probably ought to, if we're planning to use left-side dispatch.)
> 
> Meanwhile, a multi dispatch solution would send two otherwise unrelated types to `NSObject`'s implementation, which would `===` the two instances, discover they were not identical, and return `nil`. More specific implementations would not even have to think about this case; the method dispatch would just do it for them. I don't know if that solution is feasible, but if it is, it seems clearly correct—it simply gives you the right behavior every single time with no hand-wringing about type mismatches.
> 
> (On the other hand, if we *do* use left-side dispatch, we might be able to solve the `isEqual` interop problems complained about upthread: if `==` is implicitly given an `@objc(isEqual:)` attribute, and `isEqual` definitions or calls are fix-it-ed to `==`, the problem essentially solves itself.)

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)`.

When it comes to existentials, the usual case is like you said above; you want to check if you can cast the `rhs` to `Self`. But that usual case excludes the complication of class clusters where the instances of two different subclasses of a common superclass may compare equal.

— Pyry

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


More information about the swift-evolution mailing list