This is very interesting and all Swift developers will need to be aware of it, if it isn’t a bug.
One thing that I think is a little worrying is that if, rather than the == operator, you had a CustomEquatable protocol which defined the equal(to:) function, that function would be dynamically dispatched. You would think that operator witnesses should be resolved in the same way. I believe that should even work for the dynamic Self; we have already verified at compile-time that an overload exists (even though it may not be the most appropriate one to execute). So you would get: let a = Sub() let b = Sub() let c = Super() (a as Super) == b // Both values have (dynamic) type Sub, calls ==(Sub,Sub) a == c // One type is not a Sub, must compare using ==(Super, Super) - Krl > On 20 Jan 2017, at 20:24, Pierre Monod-Broca via swift-evolution > <[email protected]> wrote: > > The way I understand it, it's a bad idea to override == and != (or any infix > operator) for Sub if Super has them and that's why the default implementation > from Equatable only generates !=(Super, Super) and not !=(Sub, Sub) (and > there is no ==(Sub, Sub) generated either). > > And it's a bad idea because (without dynamic dispatch on both operands) it > leads to unexpected behavior. > > Considering : > ``` > func ==(lhs: Super, rhs: Super) -> Bool { > print("Super") > return true > } > > func ==(lhs: Sub, rhs: Sub) -> Bool { > print("Sub") > return false > } > > let a = Sub() > let b = Sub() > a == b // Sub > a as Super == b // Super > a == b as Super // Super > à as Super == b as Super // Super > ``` > > One would compare the same objects and don't get the same result. > > Instead you have to check the dynamic type yourself. > > > Pierre > > Le 20 janv. 2017 à 10:45, Francisco Javier Fernández Toro via swift-evolution > <[email protected] <mailto:[email protected]>> a écrit : > >> >> >> On Wed, Jan 18, 2017 at 6:58 PM, Tony Allevato <[email protected] >> <mailto:[email protected]>> wrote: >> Ok, this actually does feel a bit strange. The behavior you're seeing seems >> to be a consequence of >> [SE-0091](https://github.com/apple/swift-evolution/blob/master/proposals/0091-improving-operators-in-protocols.md >> >> <https://github.com/apple/swift-evolution/blob/master/proposals/0091-improving-operators-in-protocols.md>), >> but it looks like you're seeing different behavior than what I described in >> the "Class types and inheritance" section of that proposal. >> >> If Sub has `==(Sub, Sub)` implemented as a *static* function, I just tried >> it and it's *ignored* (`==(Super, Super)` gets called instead), even when >> the two actual arguments are known to be statically of type Sub. I think >> this is because of the way that proposal was implemented: when it sees that >> `Sub` extends `Super`, which conforms to `Equatable`, it appears that it's >> only looking for static overloads of `==` that are satisfied at the *point >> of conformance*, which would be `==(Super, Super)` (because `Super` conforms >> to `Equatable where Self == Super`). The wording of the proposal makes this >> case: "Then, we say that we do not consider an operator function if it >> implements a protocol requirement, because the requirement is a >> generalization of all of the operator functions that satisfy that >> requirement." >> >> Contrarily, if you provide `==(Sub, Sub)` as a global function instead of a >> static one, it *does* get called. I think in this case, the type checker >> gets the whole set of candidate operators (which, unlike above, includes the >> global `==(Sub, Sub)`), and it gets used because it's a more specific match? >> >> >> FWIW, I've just changed both `==` functions to make them global, the the >> outcome is still the same, its using `==(Super,Super)` to resolve >> `!=(Sub,Sub) >> >> Can someone from the core team chime in and say whether this is intentional >> behavior? It feels wrong that simply changing the location where the >> operator is defined would change the behavior like this. >> >> FWIW, to avoid these sharp edges, there's no need to implement `==` for >> subtypes; since you have to use an overridable `equals` method anyway, just >> have the base type implement `==` to delegate to it, and then have subtypes >> override `equals` alone. >> >> >> On Wed, Jan 18, 2017 at 9:36 AM Francisco Javier Fernández Toro >> <[email protected] <mailto:[email protected]>> wrote: >> Yeah guys, you are right, my code is busted, I was trying to point something >> different out: >> >> The next code is showing the possible issue. In theory to make a class >> Equatable, you just have to mark it with the Equatable protocol and >> implement `==` as a static function or as a global one. >> >> If you don't override the equal method and you just invoke your super class >> equality method you'll get something like this: >> >> ``` >> class Superclass : Equatable { >> let foo: Int >> >> init(foo: Int) { self.foo = foo } >> >> func equal(to: Superclass) -> Bool { >> return foo == to.foo >> } >> >> static func == (lhs: Superclass, rhs: Superclass) -> Bool { >> return lhs.equal(to: rhs) >> } >> } >> >> class Subclass: Superclass { >> let bar: Int >> init(foo: Int, bar: Int) { >> self.bar = bar >> super.init(foo: foo) >> } >> >> func equal(to: Subclass) -> Bool { >> return bar == to.bar && super.equal(to: to) >> } >> >> static func == (lhs: Subclass, rhs: Subclass) -> Bool { >> return lhs.equal(to: rhs) >> } >> } >> >> class SubclassWithDifferentOperator: Subclass { >> static func != (lhs: SubclassWithDifferentOperator, rhs: >> SubclassWithDifferentOperator) -> Bool { >> return !(lhs.equal(to: rhs)) >> } >> } >> >> let a = Subclass(foo: 1, bar: 1) >> let b = Subclass(foo: 1, bar: 2) >> >> (a == b) != (a != b) // Prints: false, not expected >> >> let x = SubclassWithDifferentOperator(foo: 1, bar: 1) >> let y = SubclassWithDifferentOperator(foo: 1, bar: 2) >> >> (x == y) != (x != y) // Prints: true, expected >> ``` >> >> So, after adding a couple of `print` statement in those equal method what I >> can see is that for Subclass, when you are need to call `!=` what Swift is >> doing is using `func ==(Superclass, Superclass)` and apply `!` as Tony has >> pointed out. >> >> What I cannot understand is why is not using `func == (Subclass, Subclass)` >> >> I hope it makes more sense now. >> >> --- >> Fran Fernandez >> >> On Wed, Jan 18, 2017 at 6:13 PM, Tony Allevato <[email protected] >> <mailto:[email protected]>> wrote: >> This seems to work for me: >> >> ``` >> class Super: Equatable { >> let x: Int >> init(x: Int) { >> self.x = x >> } >> func equals(_ rhs: Super) -> Bool { >> return x == rhs.x >> } >> static func ==(lhs: Super, rhs: Super) -> Bool { >> return lhs.equals(rhs) >> } >> } >> >> class Sub: Super { >> let y: Int >> init(x: Int, y: Int) { >> self.y = y >> super.init(x: x) >> } >> override func equals(_ rhs: Super) -> Bool { >> if let rhs = rhs as? Sub { >> return y == rhs.y && super.equals(rhs) >> } >> return false >> } >> } >> >> let a = Sub(x: 1, y: 1) >> let b = Sub(x: 1, y: 2) >> let c = Sub(x: 1, y: 1) >> >> a == b // false, expected >> a == c // true, expected >> a != b // true, expected >> a != c // false, expected >> ``` >> >> Additionally, when I made the change Joe suggested, your code also worked, >> so maybe there was an error when you updated it? >> >> FWIW, the default implementation of != just invokes !(a == b) >> <https://github.com/apple/swift/blob/master/stdlib/public/core/Equatable.swift#L179-L181 >> >> <https://github.com/apple/swift/blob/master/stdlib/public/core/Equatable.swift#L179-L181>>, >> so I believe it's *impossible* (well, uh, barring busted RAM or processor I >> guess) for it to return the wrong value for the same arguments if you only >> implement ==. >> >> >> >> On Wed, Jan 18, 2017 at 8:52 AM Francisco Javier Fernández Toro via >> swift-evolution <[email protected] >> <mailto:[email protected]>> wrote: >> Thank you for your answer Joe, >> >> you are right the equal(to:) wasn't a valid override, but even after using >> the one you've proposed, the behavior is not the expected one >> >> >> let a = Subclass(foo: 1, bar: 1) >> let b = Subclass(foo: 1, bar: 2) >> >> (a == b) != (a != b) // Prints true >> >> let x = SubclassWithDifferentOperator(foo: 1, bar: 1) >> let y = SubclassWithDifferentOperator(foo: 1, bar: 2) >> >> (x == y) != (x != y) // Prints false >> >> As you can see above if a subclass does not implement the global function >> !=, the equal operation seems to be broken. >> >> --- >> >> Fran Fernandez >> >> On Wed, Jan 18, 2017 at 5:44 PM, Joe Groff <[email protected] >> <mailto:[email protected]>> wrote: >> >> > On Jan 18, 2017, at 2:59 AM, Francisco Javier Fernández Toro via >> > swift-evolution <[email protected] >> > <mailto:[email protected]>> wrote: >> > >> > Hi, >> > >> > I've found that when you have a class hierarchy which implements >> > Equatable, if you want to have the != operator working as expected, you >> > need to override it, it's not enough with ==. >> > >> > If you don't define you own subclass != operator, Swift compiler will use >> > the super class to resolve that operation. >> > >> > Is there any reason for that? >> >> The `equal(to:)` method inside `Subclass` is not a valid override of >> `Superclass` because its argument only accepts `Subclass` instances, but the >> parent method needs to work with all `Superclass` instances. If you write it >> as an override, it should work: >> >> class Subclass: Superclass { >> let bar: Int >> init(foo: Int, bar: Int) { >> self.bar = bar >> super.init(foo: foo) >> } >> >> override func equal(to: Superclass) -> Bool { >> if let toSub = to as? Subclass { >> return bar == toSub.bar && super.equal(to: to) >> } >> return false >> } >> } >> >> We should probably raise an error, or at least a warning, instead of >> silently accepting your code as an overload. Would you be able to file a bug >> on bugs.swift.org <http://bugs.swift.org/> about that? >> >> -Joe >> >> _______________________________________________ >> swift-evolution mailing list >> [email protected] <mailto:[email protected]> >> https://lists.swift.org/mailman/listinfo/swift-evolution >> <https://lists.swift.org/mailman/listinfo/swift-evolution> >> >> >> _______________________________________________ >> swift-evolution mailing list >> [email protected] <mailto:[email protected]> >> https://lists.swift.org/mailman/listinfo/swift-evolution >> <https://lists.swift.org/mailman/listinfo/swift-evolution> > _______________________________________________ > swift-evolution mailing list > [email protected] > https://lists.swift.org/mailman/listinfo/swift-evolution
_______________________________________________ swift-evolution mailing list [email protected] https://lists.swift.org/mailman/listinfo/swift-evolution
