Scope for JEP 468: Derived record creation

Remi Forax forax at univ-mlv.fr
Fri Mar 1 15:39:55 UTC 2024


----- Original Message -----
> From: "Brian Goetz" <brian.goetz at oracle.com>
> To: "Chris Bouchard" <chris at upliftinglemma.net>, "amber-dev" <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


More information about the amber-dev mailing list