[swift-evolution] Method dispatching issue with subclasses implementing Equatable protocol.

Francisco Javier Fernández Toro fran at gokarumi.com
Wed Jan 18 11:36:23 CST 2017


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.allevato at 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 at 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 <jgroff at apple.com> wrote:
>>
>>
>> > On Jan 18, 2017, at 2:59 AM, Francisco Javier Fernández Toro via
>> swift-evolution <swift-evolution at 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 at swift.org
>> https://lists.swift.org/mailman/listinfo/swift-evolution
>>
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.swift.org/pipermail/swift-evolution/attachments/20170118/b49dc21a/attachment.html>


More information about the swift-evolution mailing list