Subtract a set of a subclass?

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@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@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@apple.com> wrote:

The Liskov substitution principle
<https://en.wikipedia.org/wiki/Liskov_substitution_principle&gt; 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@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@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@apple.com> wrote:

On Sep 1, 2016, at 15:44, Zhao Xin via swift-users < >>> swift-users@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