4 ms·
Yeah, I asked why there're no Hashable & Equatable interfaces instead of putting things into Object which would make more sense to me. People responded that's j
by vbezhenar 4y ago
Yeah, I asked why there're no Hashable & Equatable interfaces instead of putting things into Object which would make more sense to me. People responded that's just how things should be. Apparently not. IMO object identity is almost always not needed and a sign of bad design. You either should write proper hash code, or you should not use data structures which use hash code.
- cesarb 4y ago> You either should write proper hash code, or you should not use data structures which use hash code. There's also the question of which hash code. Do you use a fast but low quality hash function, or a slower but higher quality one? Does your hash function need to be secure against hash collision attacks? Does it have to be deterministic? The correct choice of hash function can depend on the data structure, and the same object might have to be hashed with different hash functions (or different hash function seeds) in the same program.
- mike_hearn 4y agoThe reason is that it'd make HashMap and HashSet a lot less useful. If every type had to opt in, you'd be unable to use many types as keys or set entries for no better reason than the authors didn't bother to implement hash codes or equality. By providing reasonable identity-based versions of these, it increases the utility of the language at a tradeoff in memory usage.
- jzoch 4y agoyeah this is where traits instead of hierarchies become useful - I should be able to implement the Hash interface for an object I do not own and then use that object + trait going forward for HashMap and HashSet. Java doesnt make this very composable
- nusaru 4y agoShould be noted that Rust (one of the most prominent languages with traits) doesn't allow you to implement a trait for an object you do not own. A common workaround is to wrap that object in your own tuple struct and then implement the trait for that struct.
- Sharlin 4y ago(If you don’t own the trait either, that is. Your own traits can be implemented for foreign types.) Rust’s approach to the Hash and Eq problem is to make them opt-in but provide a derive attribute that autoimplements them with minimal boilerplate for most types. Also, Rust’s Hash::hash implementations don’t actually hash anything themselves, they just pass the relevant parts of the object to a Hasher passed as a parameter. This way types aren’t stuck with just a single hash implementation, and normal programmers don’t need to worry about primes and modular arithmetic.
- Phelinofist 4y agoThat sounds like a use case for the decorator pattern?
- mike_hearn 4y agoJava uses this pattern in some places, for example you can usually pass a custom Comparator to anything that needs comparisons (like sorts). Fully separating implementation of an interface from the data can create quite a lot of additional complexities. See my comment elsewhere about encapsulation and version stability.
- hesk 4y agoYou could implement the hashing code in a helper class and construct the HashMap with it.
- mike_hearn 4y agoWithout a way to give objects identity you'd get stuck pretty fast as it's not guaranteed you have access to any data to hash. You'd have to break encapsulation. That would then hit problems with evolution, where a new version of a class changes its fields and then everything that uses it as a hashmap key breaks. My experience with platform design has consistently been that handling version evolution in the presence of distant teams increases complexity by 10x, and it's not just about some mechanical notion of backwards compatibility. It's a particular constraint for Java because it supports separate compilation. This enables extremely fast edit/run cycles because you only have to recompile a minimal set of files, and means that downloading+installing a new library into your project can be done in a few seconds, but means you have to handle the case of a program in which different files were compiled at different times against different versions of each other.
- titzer 4y agoJavaScript handles the "no identity hash" with WeakMap and WeakSet, which are language built-ins. For Virgil, I chose to leave out identity hashes and don't really regret it. It keeps the language simple and the separation clear. HashMap (entirely library code, not a language wormhole) takes the hash function and equality function as arguments to the constructor. [1] https://github.com/titzer/virgil/blob/master/lib/util/Map.v3 https://github.com/titzer/virgil/blob/master/lib/util/Map.v3 This is partly my style too; I try to avoid using maps for things unless they are really far flung, and the things that end up serving as keys in one place usually end up serving as keys in lots of other places too.
- josefx 4y agoDoes that assume that you have only one key type and not an infinite sized hierarchy of child classes to deal with? If you had a map that took a Number as key, how many child classes do you think your helper class would cover and what extension framework would it use to be compatible with user defined classes?
- josefx 4y ago> IMO object identity is almost always not needed and a sign of bad design. Sometimes you have no choice. I had multiple times where I needed to store additional data for instances of class X but had not control over it, so I had to store it in a separate structure and keep track of things by object identity. > You either should write proper hash code And object identity fulfills all the requirements of a "proper" hash code.
- lazulicurio 4y agoYeah, having equals and hashCode on the root Object class is Java's biggest mistake, IMO. Although for a slightly different reason: equality is usually context-dependent, but having equals as an instance method ties you to one implementation.
- devman0 4y agoThat is somewhat fixed by Comparable, but the fact that HashMap doesn't have a pluggable Hash override always bothered me.
- specialist 4y agoYes and: toString() and clone(). Further: default implementations should be synthesized by the runtime. Prevents subtle bugs when the equals(...) and hashCode() implementations are incorrect or missing. eg contract for HashMap. To backfill, perhaps Object could extend new base class NakedObject, and implement your new interfaces Equalable, Hashable. Then classes which don't need identity can extend NakedObject. Maybe the "canonical Object" was motivated by prior experience with Self or some such. It definitely calmed down people new to Java & OOP. I think it was the right call at the time.
- kaba0 4y agoRecords are very useful for data-only objects though, as they do implement proper equals and hash.