5 ms·
As much as null is considered the "billion dollar mistake", personally, I think that Java giving Object a virtual equals() and hashCode() was an even bigger mis
by lazulicurio 7y ago
As much as null is considered the "billion dollar mistake", personally, I think that Java giving Object a virtual equals() and hashCode() was an even bigger mistake. NPEs blow up loudly in your face and are fairly easy to lint for. By comparison, bad equals() and hashCode() implementations can cause difficult-to-track-down errors. Unfortunately, this design was inherited by C# and other OO languages. C# does have the IEqualityComparer interface, which helps in some cases, but many containers and other framework methods rely on an overridden instance equality method.
- mjevans 7y agoI agree. It might make sense to use something like memcmp() on structs to assert that they are exactly the same, or to have a dedicated comparison function that makes sure they are functionally equivalent for some defined need. However Objects should have only the second and it might also need to be (I think replacing a function pointer might be) an over-loaded function if something has extended a base object type. I'm not even actually positive that could work if there are two extensions to a base type.
- usrnm 7y ago> It might make sense to use something like memcmp() on structs to assert that they are exactly the same No, actually, it doesn't make sense in general. Due to padding, structs may have arbitrary meaningless bytes between the actual data fields, and memcmp will break on those. It works because structs are often filled with zeroes on initialization, but it isn't required or enforced in any way. I was burnt by this problem more than once, not really fun to debug
- Gibbon1 7y agoFun, create a struct on the stack. Set the elements individually. Padding is now filled with garbage. Any elements you forget to set will also be garbage. When you copy the struct the garbage goes with it. I've been burnt too.
- mjevans 7y agoYou've missed the 'functionally equal' check option; which covers cases like you're focusing on. To check if two differently created things happen to be equal, yes, you should prefer the function that checks exactly what matters exactly how it matters. As you rightly point out, the memcmp check would only work on either pre-zeroed memory (depends on environment and what you're doing) or actual literal copies of a specific struct.
- barrkel 7y agomemcmp() on your structs means any fields of a reference type are back to using reference equality. You need to recurse if you're going to be consistent.
- deleted 7y ago[deleted]
- deleted 7y ago[deleted]
- WalterBright 7y agoC's actual billion dollar mistake was losing array bounds with every function call: https://www.digitalmars.com/articles/b44.html https://www.digitalmars.com/articles/b44.html
- hinkley 7y agoThere's something interesting about 'sortBy' semantics which you see in some languages. As the cost of your comparator goes up (especially on real hardware with pointer chasing) the difference between running the comparator m * nlogn times and running the transform m*n times could start to stack up.
- kstenerud 7y agoThe mistake wasn't in giving Object those functions. You get "=" behavior for all types in a given language, and it's nice to be able to control this somehow (i.e. they were actually thinking a little bit about the equality problem here). The problem came when they decided that the default behavior should be a pointer comparison rather than a member-by-member sub-equals. As for hashCode(), it should have thrown an exception as the default behavior, because there's no way a language designer can anticipate what makes a proper hash for class XYZ.
- barrkel 7y agoRecursive by default (which is what member-by-member implies) leads trivially to infinite recursion in reference loops, which your default equals would need to guard against, greatly increasing its complexity. Reference equality is generally fine for mutable types, you probably only want value equality for immutable types. If you use value equality for mutable types, you start getting into trouble when you use them as e.g. hash keys.
- kstenerud 7y agoYou are correct, but it's far better to have expensive-by-default but safe operations vs the counterintuitive mess you end up with when comparing references by default. The point is that you need to counter human nature, which is to not do work you can get away with not doing. If the worst case result of not doing the work (i.e. not creating an equals method) is slow performance, that's better than getting incorrect results. You'll have a much harder time designing a useable language if your understanding of human psychology is poor. Besides, in 90% of cases, you won't have potentially recursive types, and so a smart compiler can optimize for this when generating a default equals method. In fact, many cases could just degenerate into a memcmp.
- renox 7y agoI'm not so sure, when Java was created computers weren't that fast.. Of course, now with computers fast enough that Python and JavaScript are king, sure!