Scope for JEP 468: Derived record creation
forax at univ-mlv.fr
forax at univ-mlv.fr
Fri Mar 1 15:52:43 UTC 2024
> From: "Brian Goetz" <brian.goetz at oracle.com>
> To: "Remi Forax" <forax at univ-mlv.fr>
> Cc: "Chris Bouchard" <chris at upliftinglemma.net>, "amber-dev"
> <amber-dev at openjdk.org>
> Sent: Friday, March 1, 2024 4:45:58 PM
> Subject: Re: Scope for JEP 468: Derived record creation
> I would rather revisit "record literals" when we look more carefully and
> holistically at collection literals, which will come later when some other
> foundations are in place. The current discussion is mostly one of those "While
> you've got the patient under sedation, can we do a nose job too?", and, just as
> in medicine, such questions usually bely some wrong assumptions about where the
> costs and benefits lie.
I agree that it's a step after record literals.
Does it mean that deconstructor, at least the exact syntax, should also be discussed after record literals ?
Rémi
> On 3/1/2024 10:39 AM, Remi Forax wrote:
>> ----- Original Message -----
>>> From: "Brian Goetz" [ mailto:brian.goetz at oracle.com | <brian.goetz at oracle.com> ]
>>> To: "Chris Bouchard" [ mailto:chris at upliftinglemma.net |
>>> <chris at upliftinglemma.net> ] , "amber-dev" [ mailto:amber-dev at openjdk.org |
>>> <amber-dev at openjdk.org> ] Sent: Friday, March 1, 2024 3:24:02 PM
>>> Subject: Re: Scope for JEP 468: Derived record creation
>>> I do understand that there are some people who want by-keyword
>>> invocation *so badly* that they are willing to write bad code to gain
>>> the illusion of doing so. Your example is the canonical example:
>>> MyRecord has no good default, but some people will be tempted to expose
>>> (null, null) as the default anyway (instead of rejecting those in the
>>> constructor) just so they can "stick it to the compiler." (Note that
>>> this is fine if MyRecord actually has a reasonable default, which some
>>> records do, or if you create a constructor that accepts the required
>>> components and fills in sane defaults for the rest.)
>>> Put more bluntly, some programmers overvalue superficial syntactic
>>> preferences so badly that they are willing to compromise the safety and
>>> correctness of their code -- and will pat themselves on the back for
>>> their cleverness while they do it.
>>>> People have been asking for keyword parameters in Java for years. Whether or not
>>>> this block syntax is what the language designers would choose for keyword
>>>> parameters, I think that if we introduce this feature without some way to
>>>> construct new instances it will become the de facto syntax. It's just too
>>>> tempting.
>>> Oh, I get people want this. And that bad programmers will surely do
>>> this. (And then they'll complain that they can't do it with classes, or
>>> with factories, or that it interferes with compatible migration from
>>> records to classes.) But I don't think we should let their threatened
>>> bad behavior drive language design decisions.
>>> People think they want keyword parameters, but that's not really what
>>> they want -- they just don't realize it yet. What they really want is
>>> keyword parameters *plus being able to omit parameters that have a
>>> default*. These two may sound like almost the same thing, but the
>>> difference in reality is huge. And as someone who has spent more time
>>> thinking about this problem in Java than probably anyone else, I promise
>>> you this is not the triviality it may seem. So our antipathy to named
>>> invocation is not just that it is a "meh" feature; it is that its cost
>>> and benefit are way out of line with each other.
>> Both can be unified if we introduced a weird syntax to declare and use a value
>> record as parameter or return type.
>> This is a little bit similar to an anonymous class because the value record is
>> scoped to the method but it has a name so javac error messages and binary
>> compatibility are not an issue
>> record Circle(int x, int y, int radius) {
>> Circle(Point(int x, int y)) {
>> this(x, y, 1);
>> }
>> }
>> which is translated to:
>> record Circle(int x, int y, int radius) {
>> Circle(Circle$$Point $p) {
>> int x = $p.x;
>> int y = $p.y; // or Circle$$Point(int x, int y) = $p;
>> this(x, y, 1);
>> }
>> }
>> // like an anonymous class, the EnclosingMethod is Circle(Circle$$Point)
>> value record Circle$$Point(int x, int y) {}
>> and at use site
>> new Circle({ x = 3; y = 4; })
>> we need inference so { x = 3; y = 4; } is equivalent to new Circle$$Point(3, 4).
>> The exact details are fuzy but as you know, this also solve
>> - how to write a de-constructor
>> Point(int x, int y) deconstructor() {
>> return { x = this.x; y = this.y; };
>> }
>> and it makes the syntax looks like the inverse of the constructor syntax
>> - how to returns multiple values
>> Div(int quotient, int remainder) div(int value, int divisor) {
>> return { quotient = value / divisor; remainder = value % divisor; };
>> }
>> - and even how to declare tuples, if we allow (3, 4) to be inferred as new
>> Circle$$Point(3, 4) too.
>> The idea is that a value record is so lighweight at runtime that having a syntax
>> that mix the declaration of the value record and its use make sense.
>> I also found this idea stupid at first but it keep popping in a lot of use
>> cases.
>> Rémi
>>> On 2/29/2024 7:39 PM, Chris Bouchard wrote:
>>>> Hi there. I'm new to this mailing list, but I had a similar thought to Swaranga
>>>> recently when this JEP was posted on Reddit.
>>>> Personally, my primary concern isn't having nice syntax for constructing
>>>> records. It's that if we *don't* provide nice syntax for constructing records,
>>>> people will be incentivized to hack it in by overloading the new "with" syntax.
>>>> For example, consider something like
>>>> record MyRecord(String foo, String bar) {
>>>> MyRecord {
>>>> // We'd prefer to reject nulls entirely, but we'll allow all nulls
>>>> // to have a "blank" object.
>>>> if (allNull(foo, bar))
>>>> return;
>>>> // With that out of the way, validate our *actual* constraints.
>>>> if (anyNull(foo, bar) || foo.length < 1 || bar.length < 1)
>>>> throw MyDomainException();
>>>> }
>>>> public static MyRecord blank() {
>>>> return MyRecord(null, null);
>>>> }
>>>> }
>>>> Now I can say
>>>> var value = MyRecord.blank() with {
>>>> bar = "World";
>>>> foo = greeting(bar);
>>>> };
>>>> Except that now, because my model didn't have a natural "blank" value, I've
>>>> added an *un*natural one in the interest of ergonomics. This "blank" object has
>>>> to be a valid record value. And since the with block's variables are all
>>>> initialized to null, users can accidentally run into problems with partial
>>>> initialization.
>>>> var value = MyRecord.blank() with {
>>>> // Whoops, forgot to initialize bar so it's still null.
>>>> // bar = "World";
>>>> foo = greeting(bar); // NPE
>>>> }
>>>> On the other hand, if we provided an actual way to initialize a fresh record
>>>> using this block syntax, we could say that all variables in the block are
>>>> unitialized to start and must be assigned before use. And this initial state
>>>> wouldn't have to be accepted by the constructor.
>>>> People have been asking for keyword parameters in Java for years. Whether or not
>>>> this block syntax is what the language designers would choose for keyword
>>>> parameters, I think that if we introduce this feature without some way to
>>>> construct new instances it will become the de facto syntax. It's just too
>>>> tempting.
>>>> Thanks for your time and attention,
>>>> Chris Bouchard
>>>> On Thu Feb 29 23:11:32 UTC 2024, Brian Goetz wrote:
>>>>> While such a feature is possible, I am not particularly excited about it
>>>>> for several reasons. If the goal is "I want to construct using named
>>>>> instead of position arguments" (which I think is your goal), it's a
>>>>> pretty verbose syntax; a better one would be
>>>>> new R(a: 1, b: 2)
>>>>> But that's a much smaller concern. The bigger one is that I don't think
>>>>> the language is improved by having a named parameter mechanism for
>>>>> records, but for nothing else (not for instantiating class instances,
>>>>> not for invoking methods.) While it might seem "better than nothing",
>>>>> having two different ways to do something, but one of them only works in
>>>>> narrow situations, is as likely to be frustrating than beneficial. If
>>>>> the payoff is big enough, then it might be a possibility, but
>>>>> "invocation by parameter name" is nowhere near that bar.
>>>>> Finally, if it works for records but not for classes, it makes it harder
>>>>> to refactor from records to classes, since there will be use sites that
>>>>> have to be adjusted.
>>>>> So I think for the time being, the answer is "no", though if we run out
>>>>> of things to work on, we might reconsider.
>>>>> On 2/29/2024 4:34 PM, Swaranga Sarma wrote:
>>>>>> The JEP looks really promising. However I am wondering if there will
>>>>>> be a separate JEP for creating new records from scratch with similar
>>>>>> syntax.
>>>>>> The current JEP states that its goals are to enable creating records
>>>>>> from an existing record and for that it seems sufficient. But I would
>>>>>> also love to be able to create new records from scratch using the same
>>>>>> syntax. Something like:
>>>>>> var circle = new Circle with {
>>>>>> radius = 0.5f;
>>>>>> center = new Center with {
>>>>>> x = 0;
>>>>>> y = -1;
>>>>>> z = 8;
>>>>>> };
>>>>>> };
>>>>>> Originally I had asked Brian Goetz about record literals and they
>>>>>> seemed like a possibility but withers seem like a more general feature
>>>>>> so I am hoping something like this would be supported in the future.
>>>>>> Regards
>>>>>> Swaranga Sarma
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://mail.openjdk.org/pipermail/amber-dev/attachments/20240301/4809f93e/attachment.htm>
More information about the amber-dev
mailing list