"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