"With" for records and the default constructor

Brian Goetz brian.goetz at oracle.com
Tue Nov 26 18:24:35 UTC 2024


No, in part because we don't yet have a good enough solution, and in 
part because there are other higher priorities at the moment.


On 11/26/2024 1:09 PM, Øyvind Kvien wrote:
> Thank you for the answer. Yes nominal invocation and default parameter 
> values would be a better approach. Is there a JEP drafted for this 
> feature (I can't find one)?
>
> On Tue, Nov 26, 2024 at 4:00 PM Brian Goetz <brian.goetz at oracle.com> 
> wrote:
>
>     This has been well covered in several previous mails.
>
>     The reason this request has not seen favorable reception is
>     because it is actually a request for a _different_ feature -- just
>     with a worse syntax.  What you really want is nominal (rather than
>     positional) invocation of constructors (like, for example, `new
>     Foo(a: 1, b: 2)`). And we totally get that people really, really
>     want this. We're just not going to cram a bad version of it into
>     the language because people want it so badly.
>
>     Why this would be a bad version of nominal invocation is evident
>     in your mail: you talk about using withers with "default"
>     constructors, but records don't have default constructors.  And
>     you point out why: that often there is no good default value for
>     record components.  So in the solution you are suggestion, there
>     is no "withing" at all; it is not deriving one record from
>     another.  It is just trying to attach an unrelated feature to the
>     wither proposal, because it kinda sorta looks like it (and it
>     feels like the train is leaving the station and if we don't throw
>     this extra feature on it, we might never get it!)  This is not the
>     way to evolve the language.
>
>     Compared to nominal invocation, the `with` version is:
>
>      - More confusing, because there is no withing going on
>      - More verbose
>      - Offers no path to nominal invocation for instance members
>     (e.g., `x.foo(a: 1)`)
>
>     So it is a much worse version of "nominal invocation." Its the
>     sort of thing people would be happy about for about five minutes,
>     but would likely be unhappy about thereafter.
>
>     And, when we say what people really want is nominal invocation,
>     what we mean is that they really want nominal invocation _with
>     defaultable parameters_.  Because this is what gets rid of the
>     builders.
>
>     So yes, we deeply get that there is a pain point here. But this is
>     not the solution.
>
>
>
>
>
>
>
>
>
>
>     On 11/26/2024 5:13 AM, Øyvind Kvien wrote:
>>     I see there're no plans of including the default constructor with
>>     withers for record creation, and a manually created static
>>     builder has previously been suggested instead with default values
>>     in the constructor. In my opinion ommiting the default
>>     constructor to be used with withers would be a big mistake.
>>
>>     A very common scenario in smaller services is to use simple data
>>     structures to pass data to the api of other services and message
>>     brokers. In these cases the data contains mostly primitive types
>>     and (these are not domain models). Setting default values
>>     manually in the default constructor, as suggested would be very
>>     tedious and lots of typing. Also many times a default value is
>>     not even possible since it could mean setting the object in a
>>     wrong initial state. Forcing the use of default values I think is
>>     a bad approach.
>>
>>     It would therefore be immensely useful if the default record
>>     constructor could be used with withers. The compiler needs of
>>     course to enforce that all parameters in the default constructor
>>     are set.
>>
>>     Below is a real world example of how building a request body to
>>     an internal service in my organization, used for getting access
>>     tokens for test purposes, would look like using withers and the
>>     default constructor. The following records describes the simple
>>     data structure for the request body to the api of the service
>>     (which gets serialized to json).
>>
>>         record RequestBody(
>>                 String audience,
>>                 boolean withoutDefaultClientClaims,
>>                 boolean withoutDefaultUserClaims,
>>                 boolean createDPoPTokenWithDPoPProof,
>>                 ExpirationParameters expirationParameters,
>>                 ClientClaimsParameters clientClaimsParameters,
>>                 @Nullable UserClaimsParameters userClaimsParameters,
>>                 @Nullable DPoPProofParameters dPoPProofParameters) {
>>         }
>>
>>         record ExpirationParameters(
>>                 int expirationTimeInSeconds) {
>>         }
>>
>>         record ClientClaimsParameters(
>>                 List<String> scope,
>>                 String orgnrParent,
>>                 String clientId,
>>                 String clientName,
>>                 String jti) {
>>         }
>>
>>         record UserClaimsParameters(
>>                 String pid,
>>                 String hprNumber,
>>                 String name,
>>                 String givenName,
>>                 String middleName,
>>                 String familyName,
>>                 String securityLevel,
>>                 String assuranceLevel,
>>                 String amr) {
>>         }
>>
>>         record DPoPProofParameters(
>>                 String htuClaimValue,
>>                 String htmClaimValue) {
>>         }
>>
>>     Using withers with the default constructor would look as
>>     suggested in the method below. It's very easy to visualise the
>>     json structure that the RequestBody record get serialized into
>>     when the parameter names are present.
>>
>>     Also there's no need to use default values which is a big win,
>>     and the compiler enforces that all parameters are set. This way
>>     the record is never instantiated in a wrong state.
>>
>>     Note that no parenthesis are used after 'new RequestBody' for the
>>     default constructor as a syntax suggestion.
>>
>>         public static RequestBody createRequestBodyUserToken(
>>                 @Nullable String clientId,
>>                 @Nullable String userNin
>>         ) {
>>             return new RequestBody with { // No parenthesis after
>>     'new RequestBody' for the default constructor.
>>                 audience = "audience";
>>                 withoutDefaultClientClaims = true;
>>                 withoutDefaultUserClaims = true;
>>                 createDPoPTokenWithDPoPProof = true;
>>                 expirationParameters = new ExpirationParameters with {
>>                     expirationTimeInSeconds = 300;
>>                 };
>>                 clientClaimsParameters = new ClientClaimsParameters
>>     with {
>>                     scope = List.of("openid", "scope");
>>                     orgnrParent = "12345";
>>                     clientId = clientId != null ? clientId :
>>     "13edc8d1-3fa2-425a-9b53-c346df79e589";
>>                     clientName = "SmokeTest";
>>                     jti = UUID.randomUUID().toString();
>>                 };
>>                 userClaimsParameters = new UserClaimsParameters with {
>>                     pid = userNin != null ? userNin : "12345678912";
>>                     hprNumber = "987654";
>>                     name = "Half Badger";
>>                     givenName = "Half";
>>                     middleName = "";
>>                     familyName = "Badger";
>>                     securityLevel = "4";
>>                     assuranceLevel = "high";
>>                     amr = "pwd";
>>                 };
>>                  dPoPProofParameters = new DPoPProofParameters with {
>>                     htuClaimValue = "GET";
>>                     htmClaimValue = "https://myservice/test
>>     <https://urldefense.com/v3/__https://myservice/test__;!!ACWV5N9M2RV99hQ!ImZVGoRgncmogAFG0n7i4KRdOhw1kyEzqsIO-IXBkta5m2B1ycwHwb1WWaPmHcZtDQIdeFH1JKGiMmeEwA$>";
>>                 }
>>             };
>>         }
>>
>>     The alternative, as of today, is to first instantiate each record
>>     separately and then lastly instantiate the RequestBody and return
>>     it. It's harder to read as the parameter names get lost.
>>
>>         private static RequestBody createRequestBodyUserToken(
>>                 @Nullable String clientId,
>>                 @Nullable String userNin
>>         ) {
>>             var expirationParameters = new ExpirationParameters(
>>                  300
>>             );
>>
>>             var clientClaimsParameters = new ClientClaimsParameters(
>>                     List.of("openid","scope"),
>>                     "12345",
>>                     clientId != null ? clientId :
>>     "13edc8d1-3fa2-425a-9b53-c346df79e589",
>>                     "SmokeTest",
>>                     UUID.randomUUID().toString()
>>             );
>>
>>             var userClaimsParameters = new UserClaimsParameters(
>>                     userNin != null ? userNin : "12345678912",
>>                     "987654",
>>                     "Half Badger",
>>                     "Half",
>>                     "",
>>                     "Badger",
>>                     "4",
>>                     "high",
>>                     "pwd"
>>             );
>>
>>             var dPoPProofParameters = new DPoPProofParameters(
>>                     "GET",
>>                     "https://myservice/test
>>     <https://urldefense.com/v3/__https://myservice/test__;!!ACWV5N9M2RV99hQ!ImZVGoRgncmogAFG0n7i4KRdOhw1kyEzqsIO-IXBkta5m2B1ycwHwb1WWaPmHcZtDQIdeFH1JKGiMmeEwA$>"
>>             );
>>
>>             return new RequestBody(
>>                    "audience",
>>                     true,
>>                     true,
>>                     true,
>>                     expirationParameters,
>>                     clientClaimsParameters,
>>                     userClaimsParameters,
>>                     dPoPProofParameters
>>             );
>>         }
>>
>>     I therefore hope that the default record constructor can be
>>     included with withers! It would be very useful when working with
>>     any type of api.
>>
>>     Regards
>>     Øyvind Kvien
>>
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://mail.openjdk.org/pipermail/amber-dev/attachments/20241126/b1075509/attachment-0001.htm>


More information about the amber-dev mailing list