"With" for records

Brian Goetz brian.goetz at oracle.com
Fri Jun 10 12:44:51 UTC 2022


In

https://github.com/openjdk/amber-docs/blob/master/eg-drafts/reconstruction-records-and-classes.md

we explore a generalized mechanism for `with` expressions, such as:

     Point shadowPos = shape.position() with { x = 0 }

The document evaluates a general mechanism involving matched pairs of 
constructors (or factories) and deconstruction patterns, which is still 
several steps out, but we can take this step now with records, because 
records have all the characteristics (significant names, canonical ctor 
and dtor) that are needed.  The main reason we might wait is if there 
are uncertainties in the broader target.

Our C# friends have already gone here, in a way that fits into C#, using 
properties (which makes sense, as their language is built on that):

     object with { property-assignments }

The C# interpretation is that the RHS of this expression is a sort of 
"DSL", which permits property assignment but nothing else.  This is 
cute, but I think we can do better.

In our version, the RHS is an arbitrary block of Java code; you can use 
loops, assignments, exceptions, etc.  The only thing that makes it 
"special" is that that the components of the operand are lifted into 
mutable locals on the RHS.  So inside the RHS when the operand is a 
Point, there are fresh mutable locals `x` and `y` which are initialized 
with the X and Y values of the operand.  Their values are committed at 
the end of the block using the canonical constructor.

This should remind people of the *compact constructor* in a record; the 
body is allowed to freely mutate the special variables (who also don't 
have obvious declarations), and their terminal values determine the 
state of the record.

Just as we were able to do record patterns without having full-blown 
deconstructors, we can do with expressions on records as well, because 
(a) we still have a canonical ctor, (b) we have accessors, and (c) we 
know the names of the components.

Obviously when we get value types, we'll want classes to be able to 
expose (or not) such a mechanism (both for internal or external use).

#### Digression: builders

As a bonus, I think `with` offers us a better path to getting rid of 
builders than the (problematic) one everyone asks for (default values on 
constructor parameters.)  Consider the case of a record with many 
components, all of which are optional:

     record Config(int a,
                   int b,
                   int c,
                   ...
                   int z) {
     }

Obviously, no one wants to call the canonical constructor with 26 
values.  The standard workaround is a builder, but that's a lot of 
ceremony.  The `with` mechanism gives us a way out:

     record Config(int a,
                   int b,
                   int c,
                   ...
                   int z) {

         private Config() {
             this(0, 0, 0, ... 0);
         }

         public static Config BUILDER = new Config();
     }

Now we can just say

     Config c = Config.BUILDER with { c = 3; q = 45; }

The constant isn't even necessary; we can just open up the constructor.  
And if there are some required args, the constructor can expose them 
too.  Suppose a and b are required, but c..z are optional.  Then:

     record Config(int a,
                   int b,
                   int c,
                   ...
                   int z) {

         public Config(int a, int b) {
             this(a, b, 0, ... 0);
         }
     }

     Config c = new Config(1, 2) with { c = 3; q = 45; }

In this way, the record acts as its own builder.

(As an added bonus, the default values do not suffer from the "brittle 
constant" problem that a default value would likely suffer from, because 
they are an implementation detail of the constructor, not an exposed 
part of the API.)


I think it is reasonable at this point to take this idea off the shelf 
and work towards delivering this for records, while we're building out 
the machinery needed to deliver this for general classes.  It has no 
remaining dependencies and is immediately useful for records.

(As usual, please hold comments on small details until everyone has had 
a chance to comment on the general direction.)


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


More information about the amber-spec-experts mailing list