Concerns about the plan for `==`
Brian Goetz
brian.goetz at oracle.com
Wed Jun 15 18:14:36 UTC 2022
The way I would interpret this is "Great, you just made == slightly more
reliable, but *still unreliable*. Now people will be even more likely
to make mistakes with it." Right?
The basic problem stems from that fact that users want to think in terms
of an `eq` operation, and the language needs a "primitive" equals which
gets used at the base of the tower of overloaded equals() methods, but
the language has given the good name to the thing that users usually
don't want. (Javascript bit the bullet and introduced `===` for this
reason, though they had managed to bork up equality much more
dramatically before they pulled this ripcord.)
Let's also observe that there is currently not just one `==` operation,
but nine; there is one for object references, and one for each primitive
type, each with its own ad-hoc meaning. If Valhalla is to deliver on
the promise of "programmable primitives", not being able to compare "new
primitives" with `==` doesn't feel like "works like an int."
The obvious first counter-argument here is "then let me overload `==`
for primitives, because otherwise, I can't even write classes like
`float` which treat 0.00 as == to -0.00. To which I might say "I agree,
we need to address operator overloading, at least for user-definable
numerics, anyway", at which point the current `==` is just the default
for classes that don't override `==`.
At which point you counter again with "Great, so I can do that for
identity classes too?" At which point we have to face the real problem.
The root problem of `==` and `.equals()` is that *Object::equals was
fundamentally the wrong design.* Josh spends half a dozen Items in EJ
about "how not to shoot your foot with equals". And this comes from two
roots: equals never should have been an instance method in the first
place, and we want to be able to say that instances of C can sometimes
be equals to instances of subclasses of C, under the right conditions.
This is not a stable design. (An example of a more stable design is
where equality is a witness to `Eq t`, where both sides have to agree on
the definition before they can be compared.) But that runs into the
desire for equality across subclasses. The design for equality, in the
context of extensible classes, is on a collision course with reality.
What this says is that `==` for identity classes will remain a permanent
toxic waste zone, but it can be made to behave well -- in fact, the way
people want -- for values. Which is truly a glass half everything; the
fact that we can get what we want on one side, makes it more galling
that we can never get what we want on the other side.
We can choose to drain the glass preemptively to avoid regret, or fill
the glass halfway with something good now (and better later), with the
understanding that this glass will not be fully filled. Both choices
suck. (As does papering over both with `===`.)
How's your day going?
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/20220615/9585428f/attachment-0001.htm>
More information about the valhalla-spec-observers
mailing list