[records] translation strategy (factory methods)

Brian Goetz brian.goetz at oracle.com
Fri Jan 10 16:22:11 UTC 2020

There is a whole category if "ooh, shiny new language construct, this is 
an opportunity to slide in some new features."  We saw this with lambdas 
-- "can you please make the parameters of lambdas implicitly final."  
The motivation for such requests is obvious; we wish all parameters 
could be implicitly final (perhaps with an opt-out), but we can't turn 
back the clock, so we console ourselves with "well, at least I can have 
_some_ finality.)  But this consolation is often disappointing, because 
it creates (a) challenges for migration between existing constructs 
(anon classes) and the new construct (lambdas), often in both 
directions, and (b) requires users to keep track of complex, 
history-driven rules for when they can use a feature ("oops, can't have 
static fields in inner classes.")  These are often a siren song, and are 
best avoided.

Turning to records, an example of a feature we've rejected on the basis 
of this siren song:  Named parameter invocation (`new Point(x: 1, y: 
2)`.)  Yes, parameter names are part of the API, so this becomes easier 
for records than constructors/statics (which is easier, in turn, than 
for instance methods.)  Instead, we specified that record component 
names are always reified through reflection, so that if/when we ever get 
to a more general story for named invocation, records will be ready.

While the user burden is less in this case, compiling `new R(...)` to a 
factory invocation feels like pushing on the wrong end of the 
"constructor rehabilitation" lever, because doing so will create binary 
compatibility trouble for records that want to migrate back to classes.  
(Like with enums, this is not 100% compatible, because of the supertype 
(Enum/Record), but like with enums, it is not uncommon to hit the wall 
and want to refactor back to classes.  Let's not make this harder.)

So my vote is #0.  But, if we have a story for moving _all_ classes 
towards factory construction, then we should make sure records play 
well.  I don't think we have to do anything now for which we'd regret 
inaction, because whatever problem we have with legacy records, we'll 
already have with every class ever written.

On 1/10/2020 1:36 AM, John Rose wrote:
> Another bit of translation strategy:  What does “new R(a,b,c)” compile
> to, at the bytecode level?  At some point we will cut over to factory methods,
> for at least some types (inlines).  Should be pre-empt this trend for records,
> and mandate that records are *always* created by factory methods?
> The benefit is that records can be evolved to inline types without
> recompiling their *clients*.  The risk is that (if we don’t take this
> option) that we will have records which are constrained to be identity]
> types *until all clients are recompiled*.  (No I don’t believe that you
> can translate “new;dup;invokespecial<init>” into “invokestatic”.
> Sorry, that's a pipe dream.)  This seems sad, given that records are
> partially inspired by value types.
> (From my 2012 blog on value types: “A value type is immutable… The
> equals and hashCode operations are strictly component-wise.”  Kind
> of like a record.  Meanwhile, inlines in Valhalla give component-wise
> meaning to acmp not equals and identityHashCode not hashCode.)
> I see three options regarding factories (apart from the above pipe dream):
> 0.  Do nothing.  Records are just abbreviated classes.  Whatever works
> or doesn’t for evolving general identity classes to inline classes works
> or doesn’t for records.  No special benefit, in this vein, to declaring
> something a record.
> 1. Define a factory, on top of today’s JVM.  Records use a hidden API,
> desugaring “new R(a,b)” to a static “R.$make$(a,b)”.
> 2. Define a factory, anticipating Valhalla, using a JVM-defined entry point.
> The expression “new R(a,b)” compiles to “invokestatic R.<init>(int,int)R”,
> preceded by a push of a and b.  This requires a (relatively shallow) change
> to the JVM to double down on the name “<init>” as the factory behind a
> constructor.
> The advantage of 2 is that, if we correctly predict that “<init>” is the claimed
> factory method for value types, then records can be evolved to inlines out
> of the box.  The advantage of 1 is that we don’t need to make that prediction.
> There’s also this:
> 3. Do 1. but when 2. becomes an option create an auto-bridge from “$make$”
> to “<init>”.  New code doesn’t need the bridge because it compiles to call
> “<init>” out of the box.
> Here’s a gut check for this group:  Are we confident enough with Valhalla
> that we can settled on “invokestatic <init>” as the new dance for creating
> an instance that may or may not be an inline?
> — John

-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://mail.openjdk.java.net/pipermail/amber-spec-experts/attachments/20200110/5215bf77/attachment.htm>

More information about the amber-spec-experts mailing list