Simplifying 'value' and 'identity'
Dan Smith
daniel.smith at oracle.com
Fri Dec 15 20:22:05 UTC 2023
When we last checked in on it, our story for superclasses/interfaces of value classes was as follows:
In general, classes/interfaces are "unconstrained" and support subclassing by both value and identity classes. The 'value' and 'identity' keywords act as a form of "sealing", restricting subclassing to only classes of the given type. Any class/interface that extends classes/interfaces with both keywords is in error.
As a special case, concrete classes and some abstract classes with certain properties are implicitly 'identity' classes.
This approach has a three-state categorization scheme expressed with two keywords ('value', 'identity', and "neither"). This corresponds to two JVM flags (ACC_IDENTITY and ACC_VALUE).
I think it holds together fairly well, but it's also somewhat difficult to communicate, and not as intuitive as we might like. I find myself periodically reminding people (and sometimes myself): "don't forget that there are both value abstract classes and unconstrained abstract classes" or "unconstrained classes can't have mutable fields, those are only for identity classes".
As we've revised the construction mechanism, we've found ourselves searching for an explicit keyword for the third, unconstrained state: "value-capable" or "universal" or something. None of these keywords are very compelling, and I don't relish the job of trying to get people to adopt these fairly obscure, infrequently used terms.
-----
If we're willing to give up some fairly marginal fine-grained controls, this story can be simplified significantly.
Concrete classes: nothing new—a concrete class is an identity class by default, but may opt out of identity with the 'value' keyword. Concrete value classes are implicitly final and subject to a handful of extra constraints.
Abstract classes: an abstract class is also an identity class by default, but may use the 'value' modifier to indicate that it doesn't require identity. This not the same thing as saying its subclasses *must not* make use of identity, it's just an assertion about the abstract class itself (and its supers). Both value classes and identity classes may extend an abstract value class. These abstract classes are subject to the same constraints as concrete value classes.
Interfaces: all interfaces can be implemented by value classes and identity classes. Full stop. If you have a particular need to limit the kinds of implementing classes, you can use a sealed interface and provide the implementation yourself, or you can make it an informal part of the contract. But, like access control, synchronization, and other fine-grained, implementation-oriented features, identity is not something interfaces can explicitly control.
This approach has a two-state categorization scheme for classes expressed with one keyword ('value' and identity). This corresponds to one JVM flag (ACC_IDENTITY, for {reasons}). Interfaces have only one state.
What do we lose?
- You can't force an abstract class/interface to be implemented by *only* value classes
- You can't force an interface to be implemented by *only* identity classes
- You can't declare an abstract class or interface whose type will refuse to support the 'synchronized' keyword
I don't think any of these are enough to justify the extra costs of 3 states or an 'identity' keyword. (For awhile, I was considering keeping around the 'identity' keyword, just for the purpose of interfaces. But, eh, nobody is going to use it.)
More information about the valhalla-spec-experts
mailing list