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