Hi Brian, 
i've maybe have twisted mind but i read your email as a rebuttal of both 
IdentityObject/ValueObject and identity/value modifiers. 

As you said, an identity object and a value object are less dis-similar now 
that they were in the past: a value class now reuse the method equals and 
hashCode of j.l.Object instead of coming with it's own definition, 
a value class is now nullable.I agree with you that synchronized is not a real 
issue so as Dan H. said, the real remaining issue is weak refs. 

Now, if there is such a small differences between an identity object and a 
value object, do we really need to introduce a mechanism to separate them in 
term of typing ? 

Rémi 

> From: "Brian Goetz" <brian.go...@oracle.com>
> To: "daniel smith" <daniel.sm...@oracle.com>, "valhalla-spec-experts"
> <valhalla-spec-experts@openjdk.java.net>
> Sent: Wednesday, March 23, 2022 1:01:20 PM
> Subject: Re: Alternative to IdentityObject & ValueObject interfaces

> Thanks Dan for putting the work in to provide a credible alternative.

> Let me add some background for how we came up with these things. At some point
> we asked ourselves, what if we had identity and value classes from day 1? How
> would that affect the object model? And we concluded at the time that we
> probably wouldn't want the identity-indeterminacy of Object, but instead would
> want something like

> abstract class Object
> class IdentityObject extends Object { }
> abstract class ValueObject extends Object { }

> So the {Identity,Value}Object interfaces seemed valuable pedagogically, in 
> that
> they make the object hierarchy reflect the language division. At the time, we
> imagined there might be methods that apply to all value objects, that could
> live in ValueObject.

> A separate factor is that we were taking operations that were previously total
> (locking, weak refs) and making them partial. This is scary! So we wanted a 
> way
> to make these expressible in the static type system.

> Unfortunately, the interfaces do not really deliver on either goal, because we
> can't turn back time. We still have to deal with `new Object()`, so we can't
> (yet) make Object abstract. Many signatures will not be changeable from
> "Object" to "IdentityObject" for reasons of compatibility, unless we make
> IdentityObject erase to Object (which has its own problems.) If people use it
> at all for type bounds, we'll see lots of uses of `Foo<? extends
> Bar&IdentityObject>`, which will put more pressure on our weak support for
> intersection types. And dynamic errors will still happen, because too much of
> the world was built using signatures that don't express identity-ness. (Kevin
> will see a parallel to introducing nullness annotations; it might be fine if
> you build the world that way from scratch, but the transition is painful when
> you have to interpret an unadorned type as "of unspecified identity-ness.")

> Several years on, we're still leaning on the same few motivating examples --
> capturing things like "I might lock this" in the type system. That we haven't
> come up with more killer examples is notable. And I grow increasingly 
> skeptical
> of the value of the locking example, both because this is not how concurrent
> code is written, and because we *still* have to deal with the risk of dynamic
> errors because most of the world's code has not been (and will not be) written
> to use IdentityObject throughout.

> As Dan points out, the main thing we give up by backing off from these
> interfaces is the static typing; we don't get to use `IdentityObject` as a
> parameter type, return type, or type bound. And the only reason we've come up
> with so far to want that is a pretty lame one -- locking.

> From a language design perspective, I find that you declare a class with 
> `value
> class`, but you express the subclassing constraint with `extends
> IdentityObject`, to be pretty leaky.

> On 3/22/2022 7:56 PM, Dan Smith 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.)

>> These interfaces have found a number of different uses (enumerated below), 
>> while
>> mostly leaning on the existing functionality of interfaces, so there's a 
>> pretty
>> good complexity vs. benefit trade-off. But their use has some rough edges, 
>> and
>> inserting them everywhere has a nontrivial compatibility impact. Can we do
>> better?

>> Language proposal:

>> - A "value class" is any class whose instances are all value objects. An
>> "identity class" is any class whose instances are all identity objects.
>> Abstract classes can be value classes or identity classes, or neither.
>> Interfaces can be "value interfaces" or "identity interfaces", or neither.

>> - A class/interface can be designated a value class with the 'value' 
>> modifier.

>> value class Foo {}
>> abstract value class Bar {}
>> value interface Baz {}
>> value record Rec(int x) {}

>> A class/interface can be designated an identity class with the 'identity'
>> modifier.

>> identity class Foo {}
>> abstract identity class Bar {}
>> identity interface Baz {}
>> identity record Rec(int x) {}

>> - Concrete classes with neither modifier are implicitly 'identity'; abstract
>> classes with neither modifier, but with certain identity-dependent features
>> (instance fields, initializers, synchronized methods, ...) are implicitly
>> 'identity' (possibly with a warning). Other abstract classes and interfaces 
>> are
>> fine being neither (thus supporting both kinds of subclasses).

>> - The properties are inherited: if you extend a value class/interface, you 
>> are a
>> value/class interface. (Same for identity classes/interfaces.) It's an error 
>> to
>> be both.

>> - The usual restrictions apply to value classes, both concrete and abstract; 
>> and
>> also to "neither" abstract classes, if they haven't been implicitly made
>> 'identity'.

>> - An API ('Object.isValueObject()'?) allows for dynamically distinguishing
>> between value objects and identity objects. The reflection API (in
>> java.lang.Class) allows for detection of value classes/interfaces, identity
>> classes/interfaces, and "neither" classes/interfaces.

>> - TBD whether/how we track these properties statically so that the type 
>> system
>> catch mismatches between non-identity class types and uses that assume
>> identity.

>> JVM proposal:

>> - Same conceptual framework.

>> - Classes can be ACC_VALUE, ACC_IDENTITY, or neither.

>> - Legacy-version classes are implicitly ACC_IDENTITY. Legacy interfaces are 
>> not.
>> Optionally, modern-version concrete classes are also implicitly ACC_IDENTITY.

>> (Trying out this alternative approach to abstract classes: there's no more
>> ACC_PERMITS_VALUE; instead, legacy-version abstract classes are automatically
>> ACC_IDENTITY, and modern-version abstract classes permit value subclasses
>> unless they opt out with ACC_IDENTITY. It's the bytecode generator's
>> responsibility to set these flags appropriately. Conceptually cleaner, maybe
>> too risky...)

>> - At class load time, we inherit value/identity-ness and check for conflicts.
>> It's okay to have neither flag set but inherit the property from one of your
>> supers. We also enforce constraints on value classes and "neither" abstract
>> classes.

>> ---

>> So how does this score as a replacement for the list of features enabled by 
>> the
>> interfaces?

>> - Dynamic detection: 'obj instanceof ValueObject' is quite straightforward; 
>> if
>> we can replace that with 'obj.isValueObject()', that feels about equally
>> useful. (I'd be more pessimistic about something like
>> 'Objects.isValueObject(obj)'.)

>> - 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. 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.

>> - 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".

>> - 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?)

>> - 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).

>> - 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. There's less risk of incompatibilities when
>> adding/removing the 'identity' and 'value' keywords (although there can still
>> be source, binary, and behavioral incompatibilities).

Reply via email to