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
  • [swift-evolution] Met... Francisco Javier Fernández Toro via swift-evolution
    • Re: [swift-evolu... Joe Groff via swift-evolution
      • Re: [swift-e... Francisco Javier Fernández Toro via swift-evolution
        • Re: [swi... Tony Allevato via swift-evolution
          • Re: ... Francisco Javier Fernández Toro via swift-evolution
            • ... Tony Allevato via swift-evolution
              • ... Francisco Javier Fernández Toro via swift-evolution
                • ... Pierre Monod-Broca via swift-evolution
                • ... Goffredo Marocchi via swift-evolution
                • ... Karl Wagner via swift-evolution
                • ... Pierre Monod-Broca via swift-evolution

Reply via email to