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


Reply via email to