It's worth mentioning that with equal(to:) the dispatch is dynamic only on `self` not on `to`, which means different implementation can be called if you swap the operands. For exemple: ``` let a = Super() let b = Sub()
a.equal(to: b) // calls Super.equal(to:) b.equal(to: a) // calls Sub.equal(to:) ``` Pierre > Le 21 janv. 2017 à 08:20, Karl Wagner <razie...@gmail.com> a écrit : > > 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 >> <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