Ah... we will be missing hiding behind the warm embrace of message passing/dynamic dispatch :P
Sent from my iPhone > On 20 Jan 2017, at 19:24, Pierre Monod-Broca via swift-evolution > <swift-evolution@swift.org> 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 <swift-evolution@swift.org> a écrit : >> >> >> >>> On Wed, Jan 18, 2017 at 6:58 PM, Tony Allevato <tony.allev...@gmail.com> >>> 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), >>> 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 >>>> <f...@gokarumi.com> 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 <tony.allev...@gmail.com> >>>> 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>, >>>> 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 <swift-evolution@swift.org> 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 <jgr...@apple.com> wrote: >>>> >>>> > On Jan 18, 2017, at 2:59 AM, Francisco Javier Fernández Toro via >>>> > swift-evolution <swift-evolution@swift.org> 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 about that? >>>> >>>> -Joe >>>> >>>> _______________________________________________ >>>> swift-evolution mailing list >>>> swift-evolution@swift.org >>>> https://lists.swift.org/mailman/listinfo/swift-evolution >>>> >> >> _______________________________________________ >> swift-evolution mailing list >> swift-evolution@swift.org >> https://lists.swift.org/mailman/listinfo/swift-evolution > _______________________________________________ > swift-evolution mailing list > swift-evolution@swift.org > https://lists.swift.org/mailman/listinfo/swift-evolution
_______________________________________________ swift-evolution mailing list swift-evolution@swift.org https://lists.swift.org/mailman/listinfo/swift-evolution