1

When using find to search the index of an object in a array:

var index = find(Store.allItems, myItem)

The compiler says that the Item class does not conform to protocol Equatable.

My Item class is an object, so in my opinion, coming from C#, the default equality comparer should be reference equality.

In Swift, It seems that you need to specify Equatable on each reference-type in order for find to work.
Also, you need to imlement the == operator so that it uses === to compare references:

func ==(a:Item, b:Item) -> Bool {
    return a === b
}

Moreover, this last declaration must be top-level code, which disgust me...

I tried writing:

func ==(a:AnyObject, b:AnyObject) -> Bool {
    return a === b
}

but it seems not to work.

Is there some faster and more elegant way to achieve standard equality comparing for objects?

Teejay
  • 7,210
  • 10
  • 45
  • 76
  • Scratch what I said. Misunderstood the question. Does your top-level `==` function not solve the problem? `AnyObject` is a protocol, so `func ==(a:Equatable, b:Equatable)` will work, but that is replicating native code. – jonogilmour Nov 12 '14 at 02:12
  • If I don't implement `func ==(a:Item, b:Item)` I cannot mark the `Item` class as `Equatable`. – Teejay Nov 12 '14 at 02:17
  • I realise that, I'm asking if it solves your problem. If it does, why do you find that solution "disgusting"? – jonogilmour Nov 12 '14 at 02:19
  • 1st: I don't love top-lvel code, and 2nd: I think AnyObject should always be Equatable, like in many languages. – Teejay Nov 12 '14 at 02:54
  • 2
    Just because a class conforms to AnyObject, doesn't mean it has to be Equatable. This is true in the case of types that are difficult to compare using == alone, where you may want to use your own set of (more specific) comparison functions to do the comparisons instead. And the top-level code just means anywhere in your module where you see an `Item`, it can be compared. I, personally, do not see an issue with this. – jonogilmour Nov 12 '14 at 02:57
  • In nearly all languages, it is so. If you don't override for a specific class `==` you got a reference equality. Moreover, it works also for obejcts that are not stritcly of the same class (e.g. when comparing a subclass object with a superclass object). – Teejay Nov 12 '14 at 03:00
  • As regards top-level code, I don't see why I can't override `==` in the class where it is meant to be overridden. Once again, a lot of languages, implements this as a class-level thing, not top-level one. – Teejay Nov 12 '14 at 03:05
  • 2
    Except `find()` is defined as `func find(domain: C, value: C.Generator.Element) -> C.Index?`. Note it requires the element conform to `Equatable`. You do realise that any definition of `==` must be done at global scope as it overloads a global operator, and it is a common practice to do this. Again I don't see why this is an issue. Swift doesn't automatically know how you want to compare objects in your `Item` class, and as Swift operates on type safety, perhaps consider using a different language if this annoys you. – jonogilmour Nov 12 '14 at 03:14
  • Consider this question: http://stackoverflow.com/questions/24467960/swift-equatable-protocol – jonogilmour Nov 12 '14 at 03:15
  • Overriding equality operator is a common practice? In years of coding, (mostly c#, java, actionscript), I probably did it 3 or 4 times. When comparing objects you want normally to use reference equality, at least this is for me in years of coding. – Teejay Nov 12 '14 at 03:56
  • 2
    You're confusing your experiences with the experiences of everyone else. Whilst I myself have also rarely overloaded an operator, it is obviously necessary in this case and for this language. Why complain that you have to do something if it actually solves your problem? That's like complaining that you have to wrap your functions in curly braces. I'm afraid that unless you are an Apple engineer, your opinions will have no effect on how Swift works, and you may just have to live with this solution. – jonogilmour Nov 12 '14 at 04:05
  • Yes, that's sure. Was just a "feedback". I know that operator overloading is needed, but it should be a possibility, not an obligation. – Teejay Nov 12 '14 at 04:13
  • @jonogilmour Just to make it clear what I'm talking about, see Swipesight's answer: in Obj-C it is actually that way, NSObject operates reference comparison if `==` not overridden! – Teejay Nov 12 '14 at 08:57
  • 2
    @Teejay: "Obj-C" is not "that way". Instances of `NSObject` are that way. There's a difference. – newacct Nov 13 '14 at 00:36
  • 2
    @Teejay: "In nearly all languages, it is so." You are only talking about languages where your classes subclass some class that implements this. This is not the case in Swift. – newacct Nov 13 '14 at 00:38
  • @newacct Yes, I'm talking about those languages, that is nearly all MODERN languages I know, including c#, vb, java, obj-c etc. – Teejay Nov 13 '14 at 09:28

4 Answers4

4

Just try to make Item class conforming to Equatable:

extension Item:Equatable { }
func ==(lhs:Item, rhs:Item) -> Bool {
    return lhs === rhs
}
rintaro
  • 51,423
  • 14
  • 131
  • 139
  • Thanks, but there is no problem for me to add Equatable directly on the class, rather than on an extension, since I wrote the class myself. – Teejay Nov 12 '14 at 08:53
  • 1
    Of course, `class Item:Equatable { ... }` is OK, as you like. But I like this pattern for code visibility. – rintaro Nov 12 '14 at 08:58
  • Yes, but I'd prefer Mike S's answer, so you have `==` overridden for all classes. – Teejay Nov 12 '14 at 09:01
2

You can get this to work by using a generic function for your == overload; you'll still have to make your class implement the Equatable protocol, of course.

The implementation would look something like this:

func ==<T: AnyObject>(a: T, b: T) -> Bool {
    return a === b
}

class AClass: Equatable {}

Testing it out:

let a = AClass()
let b = AClass()
let c = AClass()

let arr = [a, b, c]

let idxA = find(arr, a)
let idxB = find(arr, b)
let idxC = find(arr, c)

idxA, idxB, and idxC are 0, 1, and 2 respectively.

Mike S
  • 41,895
  • 11
  • 89
  • 84
  • Thanks, good solution. At least, the top-level code is a generic one. I will wait for accepting to see if others have different solution. – Teejay Nov 12 '14 at 02:58
2

If you aren't crazy about implementing a custom operator, you could implement another version of find() that does what you want:

func find<C : CollectionType where C.Generator.Element : AnyObject>(domain: C, value: C.Generator.Element) -> C.Index? {
    var index = domain.startIndex
    while index != domain.endIndex {
        if domain[index] === value {
            return index
        }
        index = index.successor()
    }
    return nil
}
Nate Cook
  • 92,417
  • 32
  • 217
  • 178
1

If you only want a reference equality, it's actually easy.

Just make your Item class inherit from NSObject.

class Item: NSObject {
        var name: String

        init(name: String) {
                self.name = name
        }
}

let item1 = Item(name: "item1")
let item2 = Item(name: "item2")
let item3 = Item(name: "item3")

let items = [item1, item2, item3]

for item in items {
        if let index = find(items, item) {
                println(items[index].name)
        }
}

// Console Output:
// item1
// item2
// item3

Update

Object Comparison

“There are two distinct types of comparison when you compare two objects in Swift. The first, equality (==), compares the contents of the objects. The second, identity (===), determines whether or not the constants or variables refer to the same object instance.

Swift and Objective-C objects are typically compared in Swift using the == and === operators. Swift provides a default implementation of the == operator for objects that derive from the NSObject class. In the implementation of this operator, Swift invokes the isEqual: method defined on the NSObject class. The NSObject class only performs an identity comparison, so you should implement your own isEqual: method in classes that derive from the NSObject class. Because you can pass Swift objects (including ones not derived from NSObject) to Objective-C APIs, you should implement the isEqual: method for these classes if you want the Objective-C APIs to compare the contents of the objects rather than their identities.

As part of implementing equality for your class, be sure to implement the hash property according to the rules in Object comparison in Cocoa Core Competencies. Further, if you want to use your class as keys in a dictionary, also conform to the Hashable protocol and implement the hashValue property.

Excerpt From: Apple Inc. “Using Swift with Cocoa and Objective-C.” iBooks. https://itun.es/tw/1u3-0.l

linimin
  • 6,239
  • 2
  • 26
  • 31
  • Any side-effect of inheriting NSObject? – Teejay Nov 12 '14 at 04:37
  • I'm trying to understand if there is any side-effect of inheriting from NSObject... if there was not, I'd do that for each class. – Teejay Nov 12 '14 at 08:52
  • 2
    @Teejay: "Obj-C" did not "work that way". `NSObject` worked that way. There's a difference. – newacct Nov 13 '14 at 00:36
  • @newacct On the pratical side there's no difference, since every object in Obj-C inherits from NSObject – Teejay Nov 13 '14 at 09:26
  • 1
    @Teejay: "since every object in Obj-C inherits from NSObject" Completely not true. – newacct Nov 13 '14 at 19:16
  • "NSObject is the root class of most Objective-C class hierarchies. Through NSObject, objects inherit a basic interface to the runtime system and ***the ability to behave as Objective-C objects.***" – Teejay Nov 13 '14 at 22:00
  • 1
    @Teejay: It is indisputably true that not all objects in Objective-C inherit from `NSObject`. See `NSProxy` for an example in Cocoa. You can make your own class that doesn't inherit from `NSObject`. You say, `NSObject` is the root of most objects. That is true and doesn't conflict with what I said and I don't see anything to reply about. – newacct Aug 06 '15 at 19:12