rhombus-prototype icon indicating copy to clipboard operation
rhombus-prototype copied to clipboard

Add an equality macro

Open jackfirth opened this issue 2 years ago • 8 comments

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.

jackfirth avatar Jan 09 '23 08:01 jackfirth

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]

hashim-hivery avatar Jan 09 '23 22:01 hashim-hivery

class Foo(a, b, c):
  equality:
    a
    b
    c

is an equivalent tree to

class Foo(a, b, c):
  equality: a; b; c

AlexKnauth avatar Jan 09 '23 22:01 AlexKnauth

What are the magic numbers here doing?

More specifically; why these numbers?

Lazerbeak12345 avatar Jan 12 '23 17:01 Lazerbeak12345

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.

AlexKnauth avatar Jan 12 '23 17:01 AlexKnauth

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.

jackfirth avatar Jan 13 '23 08:01 jackfirth

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.

sorawee avatar Jan 13 '23 09:01 sorawee

For hash-code-combine, I've made a PR to racket here: https://github.com/racket/racket/pull/4546

AlexKnauth avatar Jan 18 '23 02:01 AlexKnauth

Some updates for recur and hash-code-combine: https://github.com/racket/rhombus-prototype/pull/274

AlexKnauth avatar Feb 06 '23 14:02 AlexKnauth