Draft JEP: Derived Record Creation (Preview)
Dan Heidinga
dan.heidinga at oracle.com
Thu Jan 25 01:56:54 UTC 2024
Remi, is the issue that this design doesn't address 100% of the use cases you think should be addressed?
With expressions provide a way to express clone-but-update-these-fields without requiring the user to manually code the update methods. It's a great tradeoff to get more expressiveness. The fact that it's not named parameters isn't a draw back of the design.
Additionally, I think there's some confusion about what a with expression does. You say "the main way it is used teach people to avoid precondtions in record constructor" but it doesn't avoid preconditions... The canonical constructor is still called.
--Dan
________________________________
From: amber-spec-experts <amber-spec-experts-retn at openjdk.org> on behalf of forax at univ-mlv.fr <forax at univ-mlv.fr>
Sent: January 24, 2024 4:43 PM
To: Brian Goetz <brian.goetz at oracle.com>
Cc: Gavin Bierman <gavin.bierman at oracle.com>; amber-spec-experts <amber-spec-experts at openjdk.org>
Subject: Re: Draft JEP: Derived Record Creation (Preview)
________________________________
From: "Brian Goetz" <brian.goetz at oracle.com>
To: "Remi Forax" <forax at univ-mlv.fr>, "Gavin Bierman" <gavin.bierman at oracle.com>
Cc: "amber-spec-experts" <amber-spec-experts at openjdk.org>
Sent: Wednesday, January 24, 2024 9:33:34 PM
Subject: Re: Draft JEP: Derived Record Creation (Preview)
And as a general remarks, I hope there will be a following JEP about record instance creation that allows to use the syntax of a transformation block to initialize a record.
Because as this have been already discussed on several mailing list, if we only give the derived instance creation syntax, people will twist it to be able to initialize a record by component names, by adding an empty constructor that does nothing on the record. Defeating the idea that constructors should ensure that an invalid instance is not possible to create.
Two things about this.
1. In a sense, there _already is_ a way to create a record instance using the syntax of a transformation block: it is called a compact constructor. If you look carefully, the body of a compact constructor, and RHS of a with-expression, are the same thing -- they are blocks for which N mutable locals magically appear, the block gets run, the final values of those locals are observed, and fed to the canonical constructor of a record.
But I know this is not what you mean.
2. You are hoping that this can be turned into something like invoking constructor parameters by name rather than positionally. But it seems that your argument here is not "because that would be a really good thing", but more "people want it so badly that they will distort their code to do it". But that's never a good reason to add a language feature.
I agree, but it may be a reasonable reason to *not* introduce a feature if the main way it is used teach people to avoid precondtions in record constructor.
I do not hope anything, i'm not ones that write a record with a dozen fields for a living. But seeing how far people (and my students) are willing to go to have classes initialized by names, i.e. write a full builder class per record, add a dependency on an annotation processor like record-builder or lombok, etc, it's easy too see how this feature will be abused.
Data classes usually:
- can have a lot of components,
- are updated because business requirement changes modify the data,
- are application specific, so unlike methods of the JDK/libraries, it's hard to remember them.
so having a way to create them by spelling each component by name is actually a good way to make the code readable.
That's why people goes to a great length to use named parameters.
And for the anecdote, a recurrent question of my students with a C background is to ask how to initialize a class with the field names like C 99 (*).
I think many of the "turn records into builders" proposals (of which there are many) leave out an important consideration: that the real value of by-name initialization is when you have an aggregate with a large number of components, most of which are optional. Initializing with
new R(a: 1, b: 2, c: 3)
is not materially better than
new R(1, 2, 3)
when R only has three components. It is when R has 26 components, 24 of which are optional, that makes things like:
new R(a:1, z :26)
more tempting. But the suggestion above doesn't move us towards having an answer for that, and having to write out
new R(a: 1, b : <default-for-b>, c: <default-for-c>, ... z: 26)
isn't much of an improvement.
For records for which most parameters _do_ have reasonable defaults, then a slight modification of the trick you suggest actually works, and also captures useful semantics in the programming model:
record R(int a /* required */,
int b /* optional, default = 0 */,
...
int z / * required */) {
public R(int a, int z) { this(a, 0, 0, ..., z); }
}
and you can construct an R with
new R(1, 26) with { h = 8; };
where the alternate constructor takes the required parameters and fills in defaults for the rest, and then you can use withers from there. (People will complain "but then you are creating a record twice, think of the cost", to which the rejoinder is "then use a value record.")
It is nice but in a way it does not solve the problem fully because people may still want to initialize the required parameters of R with named parameters.
Rémi
(*) The same way my students with a Python background (all my student nowadays, because in France, Python is now mandatory in highschool) ask how to create a tuple in Java.
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://mail.openjdk.org/pipermail/amber-spec-experts/attachments/20240125/140fb2e4/attachment.htm>
More information about the amber-spec-experts
mailing list