Concerns about the plan for `==`
Brian Goetz
brian.goetz at oracle.com
Fri Jun 24 15:04:17 UTC 2022
I don't have an answer for you, but I can add some information to the mix.
Currently there are _nine_ "implementations" of `==`; one for
references, and one for each of the eight primitives. Regardless of
whether or not they are perfect tests of substitutibility (curse you,
floating point), the eight primitive `==` functions are highly
domain-specific. They can be so because the primitives are
monomorphic. In a sense, we've allowed primitives to "overload" `==`
because monomorphism means we can define `==` with full knowledge of the
domain, and without worry about non-well-definedness or the various
other problems of `equals` in extensible class hierarchies (as EJ
exhaustively catalogued.)
What's being proposed here is that we evolve `Object==` from "compare
identities" to a case analysis, to account for the fact that Object will
describe more things:
case (IdentityObject a, IdentityObject b) -> identity==(a, b)
case (ValueObject a, ValueObject b) -> (isNull(a) == isNull(b)) &&
(type(a) == type(b))
&& (state(a) == state(b))
default -> false
Just as `identity==` was the best we could do as a default on
polymorphic identity objects, this is the best we can do on polymorphic
mixed identity/value objects. (There's a whole digression into
overloading `==` on value types, but I'm not going to go there right now.)
While we're not making the problem of "`==` is unreliable" better, and
arguably making it incrementally worse by making it work in more cases
that look a little like the cases in which it is unreliable, we *are*
making something better here: you can now use `.equals()` everywhere.
One of the complains about `==` is that sometimes you use `==` and
sometimes you use `.equals()` and sometimes you can accidentally use one
where you should use the other. But this is because you couldn't
previous use .equals() on primitives, so an `equals()` method would
necessarily do things like:
boolean equals(Object o) {
return o instanceof Foo f
&& f.size == this.size
&& f.name.equals(this.name);
}
What stinks here is that at each point, you have to ask yourself
"equals, or =="? Now you can have a fixed rule: always say `.equals()`:
boolean equals(Object o) {
return o instanceof Foo f
&& f.size.equals(this.size) // works on int!
&& f.name.equals(this.name);
}
(The equals method on primitives is monomorphic so will JIT away, for
anyone worried about the performance.)
It is a little sad because we had to resolve the problem by using the
unfortunate spelling all the time, because `==` got the good name, but
that's not a new problem. But it means the cognitive load can disappear
if we train ourselves to uniformly use `.equals()`.
We will surely have about a million calls to make `===` or `eq` or
something else sugar for `.equals()`. We can consider that, but I don't
think its essential to do that now.
On 6/15/2022 1:51 PM, Kevin Bourrillion wrote:
> What I think I understand so far:
>
> The current plan for `==` for all bucket 2+ types (except the 8
> _primitive_ types, as I still use the word) is to have it perform a
> fieldwise `==` comparison: identity equality for bucket 1 fields, what
> it's always done for primitive fields, and of course recurse for the rest.
>
> If we consider that the broadest meaning of `a == b` has always been
> "a and b are definitely absolutely indistinguishable no matter what",
> then this plan seems to compatibly preserve that, which makes sense
> for purposes of transition.
>
> What concerns me:
>
> It's good for transition, at least on the surface, but it's a bad
> long-term outcome.
>
> Users hunger for a shorter way to write `.equals()`, and they will
> think this is it. I would not underestimate the pushback they will
> experience to writing it out the long way in cases where `==` at least
> *seems* to do the right thing. Because in some number of cases, it
> *will* do the same thing; specifically, if you can recurse through
> your fields and never hit a type that overrides equals().
>
> This is extremely fragile. A legitimate change to one type can break
> these expectations for all the types directly or indirectly depending
> on it, no matter how far away.
>
> In supporting our Java users here, there's no good stance we can take
> on it: if we forbid this practice and require them to call `.equals`,
> we're being overzealous. If we try to help them use it carefully, at
> best users will stop seeing `Object==Object` as a code smell (as we
> have spent years training them to do) and then will start misusing it
> even for reference types again.
>
> btw, why did I say it's good for transition "on the surface"? Because
> for any class a user might migrate to bucket 2+, any existing calls to
> `==` in the wild are extremely suspect and *should* be revisited
> anyway; this is no less true here than it is for existing
> synchronization etc. code.
>
> What's an alternative?:
>
> I'm sure what I propose is flawed, but I hope the core arguments are
> compelling enough to at least help me fix it.
>
> The problem is that while we /can/ retcon `==` as described above,
> it's not behavior anyone really /wants/. So instead we double down on
> the idea that non-primitive `==` has always been about identity and
> must continue to be. That means it has to be invalid for bucket 2+ (at
> compile-time for the .val type; failing later otherwise?).
>
> This would break some usages, but again, only at sites that deserve to
> be reconsidered anyway. Some bugs will get fixed in the process. And
> at least it's not the language upgrade itself that breaks them, only
> the specific decision to move some type to new bucket. Lastly, we
> don't need to break anyone abruptly; we can roll out warnings as I
> proposed in the email "We need help to migrate from bucket 1 to 2".
>
> A non-record class that forgets to override equals() from Object even
> upon migrating to bucket 2+ is also suspect. If nothing special is
> done, it would fail at runtime just like any other usage of
> `Foo.ref==Foo.ref`, and maybe that's fine.
>
> Again, I'm probably missing things, maybe even big things, but I'm
> just trying to start a discussion. And if this can't happen I am just
> searching for a solid understanding of why.
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://mail.openjdk.org/pipermail/valhalla-spec-observers/attachments/20220624/ecbb4f71/attachment.htm>
More information about the valhalla-spec-observers
mailing list