somu
(somu)
1
Hi,
I have an enum with an associated value
I am using it in a dictionary (need it to be Hashable)
I am using the default implementation of Hashable which is great
enum Status : Hashable {
case start
case completed(String)
}
let s1 = Status.completed("A")
let s2 = Status.completed("B")
s1.hashValue == s2.hashValue //false
var statuses = [Status : String]()
statuses[s1] = "aaa"
statuses[s2] = "bbb"
print(statuses) //Ideally I would like to have just 1 value
Aim
For my specific scenario I would like to the hashValue to ignore the associated value.
Note: I suppose I could implement my own Hashable, but I don't know how to do it well (other than XOR)
Question:
- What would be a good approach to handle this ?
- I have mentioned a workaround (is that a good approach) or is there a better approach ?
Workaround (not sure if it is a good approach):
Implement rawValue a computed variable and let hashValue use the rawValue
enum Status : Hashable {
case start
case completed(String)
//MARK: Hashable
var hashValue: Int {
return rawValue.hashValue
}
//MARK: Equatable
//Need to this to make it equatable based on my logic (to ignore associated value)
static func == (lhs: Mode, rhs: Mode) -> Bool {
return lhs.rawValue == rhs.rawValue
}
private var rawValue : String {
let value : String
switch self {
case .start:
value = "start"
case .completed(_):
value = "completed"
}
return value
}
}
let s1 = Status.completed("A")
let s2 = Status.completed("B")
s1.hashValue == s2.hashValue //true
var statuses = [Status : String]()
statuses[s1] = "aaa"
statuses[s2] = "bbb"
print(statuses) //1 value
Thanks.
2 Likes
Letan
(Letanyan Arumugam)
2
Well, I'm certainly no expert on hashing, but your case seems like it could simply be solved by implementing your own hashValue
var hashValue: Int {
switch self {
case .start: return 0
case .completed: return 1
}
}
You also need to explicitly implement ==
static func ==(lhs: Status, rhs: Status) -> Bool {
return lhs.hashValue == rhs.hashValue
}
2 Likes
somu
(somu)
3
Thanks @Letan
Oops my bad, I didn't read your post carefully.
I think that is a good idea to use Int instead of String, thanks a lot !!
ahti
(Lukas Stabe 🙃)
4
Don't implement == by comparing hashValue though. That's just setting yourself up for hard-to-find bugs in the future.
1 Like
Letan
(Letanyan Arumugam)
5
But I don't see the problem with this specific case though? Am I missing something or will this not always be the same. Because the hashValue's will never overlap. And any Equatable implementation that doesn't treat instances as equal if their hashValue is not equal will not do what the OP wanted.
ahti
(Lukas Stabe 🙃)
6
It works right now, but if the implementation of hashValue changes so that collisions are possible, maybe due to a new case or so, it will fail in unpredictable, hard-to-diagnose ways. Relying on hashValue providing stronger guarantees than Hashable requires is not good practice, even if it works right now™.
3 Likes
somu
(somu)
7
Thanks a lot @ahti and @Letan
Really interesting thoughts, I think I would have a private computed property rawValue just for clarity and use an Int instead of String rawValue
- Use the
rawValue for comparison of equality
- Use
rawValue.hashValue for getting the hashValue
bzamayo
(Benjamin Mayo)
8
You don’t need an underlying property to implement equality. In ==, switch on the pair of values and ignore the associated values in the cases.
1 Like
somu
(somu)
9
Thanks @bzamayo, I agree with you, there is no need to have a property.
I was just thinking that I needed a switch statement for hashValue implementation and the same switch for ==, so was thinking to have one computed property to use in both implementations.