Alternative to IdentityObject & ValueObject interfaces

Kevin Bourrillion kevinb at google.com
Wed Mar 23 01:44:40 UTC 2022


On Tue, Mar 22, 2022 at 4:56 PM Dan Smith <daniel.smith at oracle.com> wrote:

In response to some encouragement from Remi, John, and others, I've decided
> to take a closer look at how we might approach the categorization of value
> and identity classes without relying on the IdentityObject and ValueObject
> interfaces.
>
> (For background, see the thread "The interfaces IdentityObject and
> ValueObject must die" in January.)
>

Could anyone summarize the strongest version of the argument against them?
The thread is not too easy to follow.


- A "value class" is any class whose instances are all value objects. An
> "identity class" is any class whose instances are all identity objects.


I assume you are contrasting "bucket 1" vs. "buckets 2+3" here. (My own
chosen nomenclature would only alter it slightly to say that value classes
*also* have instances that are pure values, no object in sight.)


- Subclass restriction: 'implements IdentityObject' has been replaced with
> the 'identity' modifier. Complexity cost of special modifiers seems on par
> with the complexity of special rules for inferring and checking the
> superinterfaces.


The rules for the modifiers are okay. But here's my observation. The
simplest way to explain those rules would be if the `value` keyword is
literally shorthand for `extends/implements ValueObject`. I think the rules
fall out from that, plus:

   - IO and VO are disjoint. (As interfaces can *already* be, like
   `interface Foo { int x(); }` and `interface Bar { boolean x(); }`, and if
   it really came down to it, you could literally put an incompatible method
   into each type and blame their noncohabitation on that :-))
   - A class that breaks the value class rules has committed to being an
   identity class.
   - We wouldn't know how to make an *instance* that is "neither", so
   *instantiating* a "neither" class has to have default behavior, and that
   has to be to give you what it always has.

In each case I've explained why the rule seems very easy to understand to
me. So from my POV, this *still* pulls me back to the types anyway. I would
say that your rules for the modifiers are largely *simulating* those types.


I think it's a win that we use the 'value' modifier and "value" terminology
> for all kinds of classes/interfaces, not just concrete classes.
>

I think I've probably come around to that terminology over the long course
of reediting this email.


- Variable types: I don't see a good way to get the equivalent of an
> 'IdentityObject' type. It would involve tracking the 'identity' property
> through the whole type system, which seems like a huge burden for the
> occasional "I'm not sure you can lock on that" error message. So we'd
> probably need to be okay letting that go. Fortunately, I'm not sure it's a
> great loss—lots of code today seems happy using 'Object' when it means,
> informally, "object that I've created for the sole purpose of locking".
>

I'm confused, because it seems like we'd be throwing out an awful lot here.
If I pass a value object to `identityHashCode` we'd rather that didn't
compile. Seems like this list goes on a long way.


- Type variable bounds: this one seems more achievable, by using the
> 'value' and 'identity' keywords to indicate a new kind of bounds check
> ('<identity T extends Runnable>'). Again, it's added complexity, but it's
> more localized. We should think more about the use cases, and decide if it
> passes the cost/benefit analysis. If not, nothing else depends on this, so
> it could be dropped. (Or left to a future, more general feature?)
>

Don't we already need `Foo<ref T>` though? Adding this too seems
*super* confusing
to me. Let types do what types already do.



> - Documentation: we've lost the handy javadoc location to put some
> explanations about identity & value objects in a place that curious
> programmers can easily stumble on. Anything we want to say needs to go in
> JLS/JVMS (or perhaps the java.lang.Object javadoc).
>

Going beyond "mere" documentation: capturing capabilities and constraints
is precisely what types are *for*. Isn't being able to determine those
behaviors from types the reason people choose a strongly typed language in
the first place?


- Compatibility: pretty clear win here. No interface injection means tools
> that depend on reflection results won't be broken. (We've found a
> significant number of these problems in our own code/tests, FWIW.) No new
> static types means inference results won't change.
>

Seems less "breaking compatibility" than just "Hyrum's Law".  But I lack
understanding of how widespread or hard to fix these problems are. We could
maybe do an experiment over here if necessary.
-- 
Kevin Bourrillion | Java Librarian | Google, Inc. | kevinb at google.com


More information about the valhalla-spec-observers mailing list