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

Jordan Rose jordan_rose at apple.com
Fri Sep 2 11:23:58 CDT 2016


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 <mailto: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 <mailto: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 <mailto: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 <tel:%2B44%20%280%297986%20048%20141>
>> W: http://nickbrook.me <http://nickbrook.me/>
>> 
>>> On 1 Sep 2016, at 23:55, Jordan Rose <jordan_rose at apple.com <mailto:jordan_rose at apple.com>> wrote:
>>> 
>>>> 
>>>> On Sep 1, 2016, at 15:44, Zhao Xin via swift-users <swift-users at swift.org <mailto: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/20160902/68cc0ea8/attachment.html>


More information about the swift-users mailing list