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]> a écrit :
>
>
>
>> On Wed, Jan 18, 2017 at 6:58 PM, Tony Allevato <[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),
>> 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]> 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]>
>>> 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 <[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]> wrote:
>>>
>>> > On Jan 18, 2017, at 2:59 AM, Francisco Javier Fernández Toro via
>>> > swift-evolution <[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 about that?
>>>
>>> -Joe
>>>
>>> _______________________________________________
>>> 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
_______________________________________________
swift-evolution mailing list
[email protected]
https://lists.swift.org/mailman/listinfo/swift-evolution