2

It's done in java easily - the hashcode seems to be a pointer to the object or something. Why didn't swift provide us with the same comfort and requires us to define the function ourselves?

Novellizator
  • 13,633
  • 9
  • 43
  • 65

3 Answers3

3

the hashcode seems to be a pointer to the object or something.

That's true as describing the default implementation of hashCode() (Object#hashCode()) in Java.

And the fact that Java provides this default implementation of hashCode() caused hundreds of bugs I have experienced.

In Java, hashCode() and equals() needs to be consistent to work with hash-value based collections like HashMap or HashSet.

And in many, many, so many classes where equals() is defined other way than comparing pointer to the object or something. The default implementation of hashCode() is never consistent with such equals() and causes some sort of bugs, which are hard to find.

Swift is trying to tell us that hash-value needs to be consistent with the type's equality in a stronger way than other languages.

SE-0185 introduced an easy way to conform to Equatable and Hashable. In a suitable condition and the equality is trivial, you do not need to define the function.

The default implementation of hashCode() in Java is useless and you must override it when you have overridden equals(), and compiler gives us no warnings even if we forgot to override hashCode(). Is that really a comfort?

OOPer
  • 47,149
  • 6
  • 107
  • 142
1

Java's only value types are the fixed set of "primitives" (bool, char, byte, short, int, long, float, double). Swift has a generalized mechanism for creating value types that Java doesn't. Using Identity (instance address) as the basis of a default value for a hashValue would make no sense, given that value types don't have identity.

Reference types (instances of classes, called objects) are passed by copying the pointer that points to the object. If you had some object a, took its default hashCode, then passed it to a function f as parameter p, then p.hashCode() would be the same as a.hashCode(), because the pointer value is preserved.

Value types (instances of structs, tuples and enums) are passed by copying their content, or their value. If you had some instance a, and tried to derive a hashCode based off the base address where a's members starts, you'll get some value. But when you passed it to a function f as a parameter p, you would cause a copy of a's members into the memory set aside on the stack for p within f. This is at a different location than a. If you tried to tried to derive a hashCode based off the base address where p's members start, you would get a different value!

Thus, identity doesn't exist for value types. An Int8 with pattern 0b00000001 here has the exact same value as an Int8 pattern somewhere else that's also 0b00000001. This is different from two heap objects, which can each have the same content, but be distinguished apart by their different (but psuedo-permanent) locations, which forms the basis of their identity.

There's an open JDK enhancement proposal (#169) for introducing generalized value types, for its very obvious performance benefits in reducing the number of small short-lived objects like Optional<T>, Point, etc. I suspect they'll be running into these same hurdles that Swift experienced. Here's an excerpt from the JEP (emphasis mine):

Summary

Provide JVM infrastructure for working with immutable and reference-free objects, in support of efficient by-value computation with non-primitive types.

...

Description

A new operator lockPermanently will be defined which takes an Object and marks it as immutable and unaliasable.

In general, a permanently locked object cannot be subjected meaningfully to any operation that depends on the reference identity of the object. An operation depends on reference identity if the operation produces different results depending on whether it applies to the original object or one of its clones. Thus:

  • Fields and array elements cannot be changed.
  • Synchronization cannot be performed.
  • Methods for waiting or notifying cannot be invoked.
  • An identity hash code cannot be queried.
  • Pointer equality checks should not be performed.
Alexander
  • 59,041
  • 12
  • 98
  • 151
  • The OP's question was around Swift, not Java, and he also specifically said classes, which are reference types. I too wonder why reference types in Swift don't automatically conform to Equatable. I usually add global equality operators to add this. Plus, if you do need to customize it, you can still do that on a per-type basis, even with the default behavior in place, so again, like the OP, I can't find any rationale for not including it by default. – Mark A. Donohoe Mar 09 '20 at 02:04
  • @MarkA.Donohoe I'm not really sure what you're getting at with your first sentence, because I very clearly talk about what differentiates Swift (its user-definable value type), why that makes its behavior different (value types don't have a stable identity to use for a hashcode), and why that's a more general problem than just in Swift (citing how when Java undergoes an enhancement to gain support for value types, they won't have a hash code, either.) – Alexander Mar 09 '20 at 13:17
  • As for reference types in particular: Reference based hash codes seldom make sense. They're trivially correct when comparing an object to itself, without any false matches. But they're wrong when comparing an object A to a distinct object B (even if A and B have the same value, and should be equivalent in the business domain), which is a very common source of bugs in java (forgetting to override equals and hash code). – Alexander Mar 09 '20 at 13:20
  • I'd be curious to hear your use-case, because I've never encountered a situation where implementing `==` as `===` made any sense. I certainly can't imagine it being a sensible default behavior. – Alexander Mar 09 '20 at 13:21
  • Per your second comment, you would *not* use the same hash value for a reference type that is otherwise equivalent with another instance of that reference type. But that means you have explicitly defined something else to mean 'equal' so you should use those same things to calculate the hash value. See my post here for a detailed explanation of that exact scenario (near the bottom.). https://stackoverflow.com/a/60580514/168179. But... – Mark A. Donohoe Mar 09 '20 at 15:44
  • But in general, I would argue unless there is a special case to override equality, reference-equality should be the default because you *are* referring to the same object instance. Again, you can override it (like I did) to instead use ID, but also as I did, you then need to additionally override the hash value. But that should be the exception, not the norm. That was my only point. – Mark A. Donohoe Mar 09 '20 at 15:46
  • My first comment was only to not start by talking about another language as that may not clarify anything if they don't have any experience with it. Stay on Swift. Sure, use it as supporting details, but the way it reads, it seems like the focus was on Java, then you mentioned Swift as an aside, in only one sentence. Not wrong per se. Just not as clear as perhaps you intended. – Mark A. Donohoe Mar 09 '20 at 15:48
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/209329/discussion-between-alexander-reinstate-monica-and-mark-a-donohoe). – Alexander Mar 09 '20 at 17:44
0

I've posted another answer here on Stack Overflow that goes over how to implicitly add Equatable for all class types without having to do anything manually by simply defining global operators for == and !=.

Additionally, I show how you can also make all classes implement Hashable by simply specifying the protocol on the type you want to make Hashable but without having to implement the hashing function. It uses an extension to implement it for you.

Both have become staples of all new projects I create.

halfer
  • 19,824
  • 17
  • 99
  • 186
Mark A. Donohoe
  • 28,442
  • 25
  • 137
  • 286