Subtract a set of a subclass?


(Nick Brook) #1

I have a set, Set<A> and a subset of that, Set<B>, where B: A. I want to subtract Set<B> from Set<A>, but the subtract function signature specifies that the set elements must be the same type (S.Generator.Element == Element). I guess this is because Element is not required to be a class, simply hashable, therefore inheritance is not guaranteed? Is there any way this could be implemented in Set, in an extension, or what would be the most efficient way to perform that operation?

Thanks

Nick


(Zhao Xin) #2

I don't see the point. For example if an element in Set<B> and another
element in Set<A> are with a same hash value. Neither of the elements
should be subtracted. As they are in different types. And hash values
between different types are not guaranteed to be comparable.

import Foundation

class Foo:Hashable {

    var value: Int

    public var hashValue: Int {

        return value

    }

    public static func ==(lhs: Foo, rhs: Foo) -> Bool {

        return lhs.value == rhs.value

    }

    required init(_ value:Int) {

        self.value = value

    }

}

class Bar:Foo {

    override public var hashValue: Int {

        return value * 10

    }

}

let foo = Foo(10)

let bar = Bar(10)

print(foo.hashValue) // 10

print(bar.hashValue) // 100

print((bar as Foo).hashValue) // 100 instead of 10

print(foo == bar) // true

print(foo.hashValue == bar.hashValue) // false

As you can see in the above code, although `foo == bar` is true,
`foo.hashValue == bar.hashValue` is not guaranteed to be true. As far as I
know, Set<T> uses hash values instead of equations to compare the elements.
So the results of a super class and its sub class are not guaranteed. Also,
as `(bar as Foo).hashValue` is always the result of its own class, you
can't get the results you want through casting.

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)

fooSet.forEach { print("\(type(of:$0)), value:\($0.value)") }

/*

Foo, value:10

Foo, value:9

Foo, value:8 // Here is a mystery, Foo(7) is unreasonably missing.

*/

However, if you can guarantee the hash values are comparable, you still can
get the results you want.

class Foo:Hashable {

    var value: Int

    public var hashValue: Int {

        return value

    }

    public static func ==(lhs: Foo, rhs: Foo) -> Bool {

        return lhs.value == rhs.value

    }

    required init(_ value:Int) {

        self.value = value

    }

}

class Bar:Foo {

    var name = "bar"

}

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)

fooSet.forEach { print("\(type(of:$0)), value:\($0.value)") }

/*

Foo, value:10

Foo, value:9

*/

Zhaoxin

···

On Thu, Sep 1, 2016 at 8:31 AM, Nick Brook via swift-users < swift-users@swift.org> wrote:

I have a set, Set<A> and a subset of that, Set<B>, where B: A. I want to
subtract Set<B> from Set<A>, but the subtract function signature specifies
that the set elements must be the same type (S.Generator.Element ==
Element). I guess this is because Element is not required to be a class,
simply hashable, therefore inheritance is not guaranteed? Is there any way
this could be implemented in Set, in an extension, or what would be the
most efficient way to perform that operation?

Thanks

Nick

_______________________________________________
swift-users mailing list
swift-users@swift.org
https://lists.swift.org/mailman/listinfo/swift-users


(Nick Brook) #3

Hi Zhao

Thanks for your response.

I understand your point, but when using third party classes I don’t know if the hash values are comparable, but for example I may want to have a set of ‘data' (NSData) and a subset of ‘mutable data' (NSMutableData), which point to the same objects. As a user of swift I would expect to be able to subtract Set<NSMutableData> from Set<NSData>.

Your last example perhaps works in Swift 3, so this may be fixed now, but in Swift 2 you get the error

Cannot invoke 'subtract' with an argument list of type '(Set<Bar>)’

Perhaps Swift 3 supports it with some additional safety around hashValue overriding or something.

Thanks

Nick

···

On 1 Sep 2016, at 04:00, Zhao Xin <owenzx@gmail.com> wrote:

I don't see the point. For example if an element in Set<B> and another element in Set<A> are with a same hash value. Neither of the elements should be subtracted. As they are in different types. And hash values between different types are not guaranteed to be comparable.

import Foundation

class Foo:Hashable {
    var value: Int
    
    public var hashValue: Int {
        return value
    }
    
    public static func ==(lhs: Foo, rhs: Foo) -> Bool {
        return lhs.value == rhs.value
    }
    
    required init(_ value:Int) {
        self.value = value
    }
}

class Bar:Foo {
    override public var hashValue: Int {
        return value * 10
    }
}

let foo = Foo(10)
let bar = Bar(10)

print(foo.hashValue) // 10
print(bar.hashValue) // 100
print((bar as Foo).hashValue) // 100 instead of 10

print(foo == bar) // true
print(foo.hashValue == bar.hashValue) // false

As you can see in the above code, although `foo == bar` is true, `foo.hashValue == bar.hashValue` is not guaranteed to be true. As far as I know, Set<T> uses hash values instead of equations to compare the elements. So the results of a super class and its sub class are not guaranteed. Also, as `(bar as Foo).hashValue` is always the result of its own class, you can't get the results you want through casting.

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)
fooSet.forEach { print("\(type(of:$0)), value:\($0.value)") }
/*
Foo, value:10
Foo, value:9
Foo, value:8 // Here is a mystery, Foo(7) is unreasonably missing.
*/

However, if you can guarantee the hash values are comparable, you still can get the results you want.

class Foo:Hashable {
    var value: Int
    
    public var hashValue: Int {
        return value
    }
    
    public static func ==(lhs: Foo, rhs: Foo) -> Bool {
        return lhs.value == rhs.value
    }
    
    required init(_ value:Int) {
        self.value = value
    }
}

class Bar:Foo {
    var name = "bar"
}

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)
fooSet.forEach { print("\(type(of:$0)), value:\($0.value)") }
/*
Foo, value:10
Foo, value:9
*/

Zhaoxin

On Thu, Sep 1, 2016 at 8:31 AM, Nick Brook via swift-users <swift-users@swift.org <mailto:swift-users@swift.org>> wrote:
I have a set, Set<A> and a subset of that, Set<B>, where B: A. I want to subtract Set<B> from Set<A>, but the subtract function signature specifies that the set elements must be the same type (S.Generator.Element == Element). I guess this is because Element is not required to be a class, simply hashable, therefore inheritance is not guaranteed? Is there any way this could be implemented in Set, in an extension, or what would be the most efficient way to perform that operation?

Thanks

Nick

_______________________________________________
swift-users mailing list
swift-users@swift.org <mailto:swift-users@swift.org>
https://lists.swift.org/mailman/listinfo/swift-users


(Zhao Xin) #4

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

*/

Extension part. You should comment above subtracting code before you run
below code, as you want to make sure the result is not polluted.

extension Foo {

    convenience init(_ instance:Foo) {

        self.init(instance.id)

    }

}

let anotherFooSet = { () -> Set<Foo> in

    var set = Set<Foo>()

    for element in barSet {

        let foo = Foo(element)

        set.insert(foo)

    }

    return set

}()

fooSet = fooSet.subtract(anotherFooSet)

fooSet.forEach { print("\($0.dynamicType), id:\($0.id)") }

/*

Foo, id:10

Foo, id:9

*/

Zhaoxin

···

On Thu, Sep 1, 2016 at 9:41 PM, Nick Brook <nrbrook@gmail.com> wrote:

Hi Zhao

Thanks for your response.

I understand your point, but when using third party classes I don’t know
if the hash values are comparable, but for example I may want to have a set
of ‘data' (NSData) and a subset of ‘mutable data' (NSMutableData), which
point to the same objects. As a user of swift I would expect to be able to
subtract Set<NSMutableData> from Set<NSData>.

Your last example perhaps works in Swift 3, so this may be fixed now, but
in Swift 2 you get the error

Cannot invoke 'subtract' with an argument list of type '(Set<Bar>)’

Perhaps Swift 3 supports it with some additional safety around hashValue
overriding or something.

Thanks

Nick

On 1 Sep 2016, at 04:00, Zhao Xin <owenzx@gmail.com> wrote:

I don't see the point. For example if an element in Set<B> and another
element in Set<A> are with a same hash value. Neither of the elements
should be subtracted. As they are in different types. And hash values
between different types are not guaranteed to be comparable.

import Foundation

class Foo:Hashable {
    var value: Int

    public var hashValue: Int {
        return value
    }

    public static func ==(lhs: Foo, rhs: Foo) -> Bool {
        return lhs.value == rhs.value
    }

    required init(_ value:Int) {
        self.value = value
    }
}

class Bar:Foo {
    override public var hashValue: Int {
        return value * 10
    }
}

let foo = Foo(10)
let bar = Bar(10)

print(foo.hashValue) // 10
print(bar.hashValue) // 100
print((bar as Foo).hashValue) // 100 instead of 10

print(foo == bar) // true
print(foo.hashValue == bar.hashValue) // false

As you can see in the above code, although `foo == bar` is true,
`foo.hashValue == bar.hashValue` is not guaranteed to be true. As far as I
know, Set<T> uses hash values instead of equations to compare the elements.
So the results of a super class and its sub class are not guaranteed. Also,
as `(bar as Foo).hashValue` is always the result of its own class, you
can't get the results you want through casting.

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)
fooSet.forEach { print("\(type(of:$0)), value:\($0.value)") }
/*
Foo, value:10
Foo, value:9
Foo, value:8 // Here is a mystery, Foo(7) is unreasonably missing.

*/

However, if you can guarantee the hash values are comparable, you still
can get the results you want.

class Foo:Hashable {
    var value: Int

    public var hashValue: Int {
        return value
    }

    public static func ==(lhs: Foo, rhs: Foo) -> Bool {
        return lhs.value == rhs.value
    }

    required init(_ value:Int) {
        self.value = value
    }
}

class Bar:Foo {
    var name = "bar"
}

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)
fooSet.forEach { print("\(type(of:$0)), value:\($0.value)") }
/*
Foo, value:10
Foo, value:9

*/

Zhaoxin

On Thu, Sep 1, 2016 at 8:31 AM, Nick Brook via swift-users < > swift-users@swift.org> wrote:

I have a set, Set<A> and a subset of that, Set<B>, where B: A. I want to
subtract Set<B> from Set<A>, but the subtract function signature specifies
that the set elements must be the same type (S.Generator.Element ==
Element). I guess this is because Element is not required to be a class,
simply hashable, therefore inheritance is not guaranteed? Is there any way
this could be implemented in Set, in an extension, or what would be the
most efficient way to perform that operation?

Thanks

Nick

_______________________________________________
swift-users mailing list
swift-users@swift.org
https://lists.swift.org/mailman/listinfo/swift-users


(Jordan Rose) #5

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

···

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
*/


(Nick Brook) #6

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 <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 <mailto: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


(Zhao Xin) #7

`A hash value, provided by a type’s hashValue property, is an integer
that is the same for any two instances that compare equally. That is, for
two instances a
​​
and b of the same type`

So do you believe A and B are the same type if B inherits A. That's the
differences between you and me.

Zhaoxin

···

On Fri, Sep 2, 2016 at 6:55 AM, 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


(Zhao Xin) #8

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


(Jordan Rose) #9

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(_:slight_smile: method, but you still need to design your class hierarchy and isEqual(_:slight_smile: 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 <mailto: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 <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@apple.com <mailto:jordan_rose@apple.com>> wrote:

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


(Zhao Xin) #10

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> 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(_:slight_smile: method, but you still
need to design your class hierarchy and isEqual(_:slight_smile: 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


(Jordan Rose) #11

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 <mailto:jordan_rose@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(_:slight_smile: method, but you still need to design your class hierarchy and isEqual(_:slight_smile: methods carefully.

Jordan

On Sep 1, 2016, at 16:28, Zhao Xin <owenzx@gmail.com <mailto: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 <mailto: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 <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@apple.com <mailto:jordan_rose@apple.com>> wrote:

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


(Zhao Xin) #12

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> 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(_:slight_smile: method, but you
still need to design your class hierarchy and isEqual(_:slight_smile: 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


(Dmitri Gribenko) #13

That's one way you can satisfy the rules, but not the only one.

Think about class clusters. NSString has many subclasses that store
strings in different ways (for example, as Latin1 and UTF-16), but any
subclass instance compares equal to any other subclass instance that
carries the same character data, and also produces the same hash
value.

Dmitri

···

On Sat, Sep 3, 2016 at 4:47 AM, Zhao Xin via swift-users <swift-users@swift.org> wrote:

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.

--
main(i,j){for(i=2;;i++){for(j=2;j<i;j++){if(!(i%j)){j=0;break;}}if
(j){printf("%d\n",i);}}} /*Dmitri Gribenko <gribozavr@gmail.com>*/


(Zhao Xin) #14

You are correct. Whether override `hashValue` is basing on the
implementation of `==`. You can't do what you want. You should follow the
required rules. However, if you don't want to get the unexpected result,
you can always use a `convenience init(_ instance:Self)` in `extension` to
convert subclass to superclass as I did in previous replies.

Zhaoxin

···

On Sat, Sep 3, 2016 at 10:12 AM, Dmitri Gribenko <gribozavr@gmail.com> wrote:

On Sat, Sep 3, 2016 at 4:47 AM, Zhao Xin via swift-users > <swift-users@swift.org> wrote:
> 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.

That's one way you can satisfy the rules, but not the only one.

Think about class clusters. NSString has many subclasses that store
strings in different ways (for example, as Latin1 and UTF-16), but any
subclass instance compares equal to any other subclass instance that
carries the same character data, and also produces the same hash
value.

Dmitri

--
main(i,j){for(i=2;;i++){for(j=2;j<i;j++){if(!(i%j)){j=0;break;}}if
(j){printf("%d\n",i);}}} /*Dmitri Gribenko <gribozavr@gmail.com>*/