Swift dictionary with custom class as key - comparison function?

In my Silver/Toffee project I’m using a Swift dictionary declared as [A : B]

A is a custom class. I thought implementing the equality operator would be sufficient for value lookups to work but that doesn’t seem to be the case.

public func ==(lhs: A, rhs: A) -> Bool {
	return lhs.stringValue == rhs.stringValue
}

public class A {

        // ...

	var stringValue: String {
		// ...
		return "someStringRepresentingThisValue"
	}

}

Am I approaching this the wrong way? Usually in Swift I would mark class A as Equatable but it seems the IEquatable protocol isn’t quite the same.

-Benoît

i believe NSDictionary, which this is mapped to, uses .equals

I also suspected as much but isEqual doesn’t seem to get called during value lookups. This is puzzling.

there’s two different ones, because Cocoa. equal and isEqual.

that said, I just checked and in Elements RTL’s Url class I implement these three

    method isEqual(obj: id): Boolean; override;
    method copyWithZone(aZone: ^NSZone): instancetype;
    method hash: NSUInteger; override;

and it works fine with NSDIctionary. Note that hash might be required too, as the dictionary will check the hash before calling isEqual, iirc.

1 Like

That did it.

I added:

	override var hash: NSUInteger {
		return stringValue.hash
	}
	
	override func isEqual(_ object: Any?) -> Bool {
		return stringValue.isEqual(to: (object as? A)?.stringValue)
	}

…and it works. I was hoping to do this for isEqual (which would rely on my custom == operator) but it didn’t call the operator func:

	override func isEqual(_ object: Any?) -> Bool {
		return self == (object as? A)
	}

Since class A is used in two projects (Island Windows DLL and Toffee shared library) using __partial classes, I was hoping to rely on the custom == operator. I guess I’m missing something for that to happen.

– EDIT –

The custom operator will be called if I use the following code, which makes sense:

override func isEqual(_ object: Any?) -> Bool {
	return self == object as! A
}

-Benoît

Yeah, for some platform-intrinsic stuff like that, you can’t get around some per-platform IFDefs (for now)…

hmm., I’d expect both of the versions you posted to call the custom operator. How is art defined? Could you create a small testcase that shows it only working with the as! cast (which seams wrong, of course — because it’ll throw, if that fails), so that we can have a look and maybe improve/fix this?

This calls my custom == operator:

override func isEqual(_ object: Any?) -> Bool {
	return self == object as! A
}

This does not:

override func isEqual(_ object: Any?) -> Bool {
	return self == object as? A
}

I’ll see about building a quick test case but that behaviour is what I observed earlier this afternoon.

Yeah, that’s how I understood it. A complete testcase including the actual operator and a test call would help me get this logged.

This is a Silver/Toffee source file that illustrates the issue:

import Foundation

public func ==(lhs: A, rhs: A) -> Bool {
	return lhs.stringValue == rhs.stringValue
}

public class A: INSCopying {

	private var string: String
	
	init(string: String) {
		self.string = string
	}

	init(copyFrom: A) {
		string = copyFrom.string
	}

	override var hash: NSUInteger {
		return string.hash
	}

	override func isEqual(_ object: Any?) -> Bool {
		return self == object    // same result with object as? A
	}
	
	public var stringValue: String {
		return string
	}

	// MARK: INSCopying

	func copy(withZone zone: UnsafePointer<NSZone>) -> Any {
		return A(copyFrom: self)
	}

}

let s1 = A(string: "text1")
let s2 = A(string: "text1")
var dict: [A : Int] = [:]
dict[s1] = 1
print(dict[s2])

It prints (null) but will print 1 (the correct value) if you change isEqual as follows:

	override func isEqual(_ object: Any?) -> Bool {
		return self == object as! A
	}

Thanks, logged as bugs://79831

bugs://79831 got closed with status fixed.

I’ve fixed this so that as? works as it should. however:

override func isEqual(_ object: Any?) -> Bool {
	return self == object 
}

Won’t give the result you want, “any” wouldn’t validate for the equals (because if we did that every any == any would fail because ALL overloads would match).

That makes sense.

Thank you Carlo.

I confirm this is fixed in 2267.

1 Like

How does one ensure the custom objects are compared properly when using Silver with Island on Windows?

Right now there’s no clean cross-platform way to do this. You will need to implement the proper pattern for each platform (eg GetHash/Equals on .NET, hash/isEqual for Cocoa, etc. See the implementation of Url in RTL2 for an example.

I just looked at the Url implementation in RTL2. I’m running this on Island, however. I was thinking of implementing Equatable but the compiler keeps asking me to implement “static op_Equality(...) -> Bool”. I tried adding this as a class func, as a static func and as a Swift operator (class func ==(...)), to no avail. I also tried just marking the class as Hashable and adding the hashValue property, no go again.

sounds like a bad error message (can you give ma a concrete example so I can log?). the syntax would be

operator Equals(a, b: ByObject): Boolean

(where you can also provide additional overloads to compare your object to others. Url does that, for Cocoa'sNSURL`). We should check with @ck or @EvgenyK to make sure there = operator is actually what Island RTL dictionary uses under the hood — as I myself am not that deeply familiar with the Island RTL API there…

That said, a cleaner way to do tis cross-platform would be nice, Ill log that for investigation.

1 Like

Thanks, logged as bugs://80241

This is the error I’m getting (this is in Fire right now but Water had the same message).