[swift-users] Subtract a set of a subclass?

Zhao Xin owenzx at gmail.com
Fri Sep 2 20:47:08 CDT 2016


Hi Jordan,

Both you and I were wrong.

My wrongness: Your so called principle should be applied here.

Your wrongness: If you really want a different hash value, the parent
equality function has to be conservative and say that the different types
are different.

Here is the conclusion.

The key is how to write the `==` function. It should compare the`
dynamicType`(or `type(of:)` in Swift 3.0) if the class is not a final class.

func ==(lhs: Fruit, rhs: Fruit) -> Bool {



    print(lhs.hashValue)

    print(rhs.hashValue)



    return type(of:lhs) == type(of:rhs) && lhs.name == rhs.name

}


func ==(lhs: Apple, rhs: Apple) -> Bool {

    return type(of:lhs) == type(of:rhs) && lhs.name == rhs.name && lhs.shape ==
rhs.shape

}


func ==(lhs: Banana, rhs: Banana) -> Bool {

    return type(of:lhs) == type(of:rhs) && lhs.name == rhs.name && lhs.shape ==
rhs.shape

}



class Fruit:Hashable {

    let name:String



    var hashValue: Int {

        return 0

    }



    init(_ name:String = "common fruit") {

        self.name = name

    }

}


enum FruitShape:Int {

    case small = 1000

    case medium = 2000

    case big = 3000

}


class Apple:Fruit {

    let shape:FruitShape



    override var hashValue: Int {

        return 5

    }



    required init(_ name:String = "common fruit", shape:FruitShape = .medium)
{

        self.shape = shape

        super.init(name)

    }

}


class Banana:Fruit {

    let shape:FruitShape



    override var hashValue: Int {

        return 10

    }



    required init(_ name:String = "common fruit", shape:FruitShape = .medium)
{

        self.shape = shape

        super.init(name)

    }

}


let apple = Apple()

let banana = Banana()


print(apple == banana)

/*

 5

 10

 false

*/


I got the idea from book "Core Java", mine is version 8, the latest is the
version 10. I learnt how to writing Object oriented code from it. I am glad
it is still useful.


Zhaoxin

On Sat, Sep 3, 2016 at 12:23 AM, Jordan Rose <jordan_rose at apple.com> wrote:

> This is incorrect. If I have a Set<Fruit>, I should expect that the set
> may contain Apples and Bananas. If you really want a different hash value,
> the parent equality function has to be conservative and say that the
> different types are different. But that’s *your* choice, because *you* wrote
> the implementation of Equatable for the base class. (And if you *didn’t,* then
> you should be concerned, because any functions that came with the base
> class will assume the Apple and the Banana are interchangeable.)
>
> Remember, hashValue can return 0 for all instances and still be correct.
> The implications only work one way.
>
> if a == b, then a.hashValue == b.hashValue
> if a != b, then we know nothing about the hash values
> if a.hashValue == b.hashValue, then we know nothing about a == b
> if a.hashValue != b.hashValue, a != b
>
> I’ll finish by repeating what I said earlier: if you plan to have a base
> class be Equatable, you need to design your == in such a way that it makes
> sense for subclasses. If you can’t do that, you either can’t be Equatable
> or can’t allow subclasses, or common uses of the standard library will
> break. How you *want* it work doesn’t matter if you don’t control ==.
>
> Jordan
>
>
> On Sep 1, 2016, at 22:50, Zhao Xin <owenzx at gmail.com> wrote:
>
> No. I don't think what you so called principle should be applied here. For
> example, I have a `class Fruit`, then I have a `class Apple:Fruit`. If they
> are using different `hashValue` generating method, you suggest me to use
> composition instead of inheritance?
>
> Also, it is very common for subclass to override super class `hashValue`.
> Supposing opposite, if we also have another class called `class
> Banana:Fruit`, we may get the result that an `Apple` is equals to a
> `Banana`, using `Fruit`,  just because they have the same `hashValue`.
>
> If we stick to the super class `hashValue`, we may also not get the
> differences between instances of a certain subclass. For example, we may
> get the result that a `redApple` equals to a `greenApple`.
>
> So in my option, if one instance equals to another instance, the
> foundation should be that the `type(of:instance)` equals. If you want to
> enlarge the type to their super class, you need to be careful, as they are
> not guaranteed automatically.
>
> Zhaoxin
>
> On Fri, Sep 2, 2016 at 7:32 AM, Jordan Rose <jordan_rose at apple.com> wrote:
>
>> The Liskov substitution principle
>> <https://en.wikipedia.org/wiki/Liskov_substitution_principle> says that
>> a B should always be able to be treated like an A. Your Set<A> may
>> *already* contain Bs, even without them ever being statically typed as
>> B. If you think A and B are unrelated types, you should be using
>> composition rather than inheritance.
>>
>> If a subclass overrides hashValue, they must be in a position to affect
>> == as well, and it must work no matter which object is on the left-hand
>> side. NSObject does this by having == call the isEqual(_:) method, but you
>> still need to design your class hierarchy and isEqual(_:) methods carefully.
>>
>> Jordan
>>
>>
>> On Sep 1, 2016, at 16:28, Zhao Xin <owenzx at gmail.com> wrote:
>>
>> I believe if B inherits A, they are not the same type. So the rule
>> doesn't apply here.
>>
>> Zhaoxin
>>
>> On Fri, Sep 2, 2016 at 7:02 AM, Nick Brook <nrbrook at gmail.com> wrote:
>>
>>> Hi Jordan,
>>>
>>> Thanks for the advice.
>>>
>>> What if a subclass does implement hashValue differently? It seems you
>>> are saying a subclass should never override hashValue? Should Set not
>>> compare elements with == instead of hashValue?
>>>
>>> Thanks
>>> Nick
>>>
>>> M: +44 (0)7986 048 141
>>> W: http://nickbrook.me
>>>
>>> On 1 Sep 2016, at 23:55, Jordan Rose <jordan_rose at apple.com> wrote:
>>>
>>>
>>> On Sep 1, 2016, at 15:44, Zhao Xin via swift-users <
>>> swift-users at swift.org> wrote:
>>>
>>> Hi Nick,
>>>
>>> Glad to help.
>>>
>>> but when using third party classes I don’t know if the hash values are
>>>> comparable
>>>>
>>>
>>> You can create an extension with a convenient init(:), which creates a
>>> new instance of  the super class basing on the instance of the sub class.
>>> That will guarantee the subtraction. Below code works in Xcode 7.3.1 with
>>> Swift 2.2.
>>>
>>> import Foundation
>>>
>>> func ==(lhs: Foo, rhs: Foo) -> Bool {
>>>     return lhs.id == rhs.id
>>> }
>>>
>>> class Foo:Hashable {
>>>     let id:Int
>>>     var hashValue: Int {
>>>         return id
>>>     }
>>>
>>>     required init(_ id:Int) {
>>>         self.id = id
>>>     }
>>> }
>>>
>>> class Bar:Foo {
>>>     override var hashValue: Int {
>>>         return id * 5
>>>     }
>>> }
>>>
>>> var fooSet:Set<Foo> = [Foo(10), Foo(9), Foo(8), Foo(7)]
>>> var barSet:Set<Bar> = [Bar(8), Bar(7), Bar(6), Bar(5)]
>>>
>>> //fooSet.subtract(barSet) // error: cannot invoke 'subtract' with an
>>> argument list of type '(Set<Bar>)'
>>> fooSet = fooSet.subtract(barSet as Set<Foo>) // works, but not what we
>>> want
>>> fooSet.forEach { print("\($0.dynamicType), id:\($0.id)") }
>>> /*
>>>  Foo, id:7
>>>  Foo, id:10
>>>  Foo, id:9
>>> */
>>>
>>>
>>> This isn't really a sensible thing to do. The rules for Hashable require
>>> that `a == b` implies `a.hashValue == b.hashValue`, and `a.hashValue !=
>>> b.hashValue` implies `a != b`. If you break these rules you're going to
>>> have problems no matter what static types you're using.
>>>
>>> Upcasting from Set<Bar> to Set<Foo> is the most concise way to solve
>>> this problem.
>>>
>>> Jordan
>>>
>>>
>>>
>>
>>
>
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.swift.org/pipermail/swift-users/attachments/20160903/df3ed6c8/attachment.html>


More information about the swift-users mailing list