rhombus-prototype
rhombus-prototype copied to clipboard
Add an equality macro
This is a draft PR I'm leaving up as a reference. It implements a hypothetical equality:
macro for specifying what components of a class are relevant when comparing its instances for equality. This code:
class Foo(a, b, c):
equality:
a
b
c
Expands into this, roughly:
class Foo(a, b, c):
implements Equatable
override method equals(other):
(other is_a Foo)
&& (a == other.a)
&& (b == other.b)
&& (c == other.c)
override method hashCode():
let code = 1000003
let code = 31 * code + a.hashCode()
let code = 31 * code + b.hashCode()
let code = 31 * code + c.hashCode()
code
You can pass arbitrary expressions into equality
, not just fields, so this works:
class TwoElementIntSet(a :: Integer, b :: Integer):
equality:
min(a, b)
max(a, b)
I'm drafting this PR for reference in future discussions. I'm not sure this is the way to go, but I did want to make this code available. Consider it a proof of concept.
Are the a b c on separate lines, a list? If not, would they be nicer in a list? Then they can be in one line
class Foo(a, b, c):
equality: [a, b, c]
class Foo(a, b, c):
equality:
a
b
c
is an equivalent tree to
class Foo(a, b, c):
equality: a; b; c
What are the magic numbers here doing?
More specifically; why these numbers?
It would be good to replace the magic numbers in the hash-code method with a call to a hash-code-combine function similar to hash-code-combine
in racket/src/cs/rumble/hash-code.ss
.
For the 31 constant, see this stackoverflow post. For the 1000003 constant, see this one. I just copied what @AutoValue
does since it seemed fine in practice. I'm guessing the reason for picking a large starting constant is to avoid hash codes for small record types being biased to small-ish positive integers.
Effective Java uses 1000003 in the AutoValue
section without using 31 and 1000003 is used as a multiplier. On the other hand, the hashCode
recipe section uses 31 without using 1000003. Using both 1000003 and 31 with 1000003 as the initial value is probably fine, but seems unnecessary.
While it might be the case that 1000003 and 31 work, I really dislike how Effective Java says "trust me, just do it and it will be fine" without giving any justification. The inconsistency (using 1000003 in a place and 31 in another place) is also baffling.
I agree with @AlexKnauth that exposing and calling hash-code-combine
would be better.
For hash-code-combine
, I've made a PR to racket here: https://github.com/racket/racket/pull/4546
Some updates for recur and hash-code-combine: https://github.com/racket/rhombus-prototype/pull/274