<!DOCTYPE html><html><head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
</head>
<body>
<font size="4" face="monospace">This has been well covered in
several previous mails. <br>
<br>
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. <br>
<br>
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.<br>
<br>
Compared to nominal invocation, the `with` version is:<br>
<br>
- More confusing, because there is no withing going on <br>
- More verbose<br>
- Offers no path to nominal invocation for instance members
(e.g., `x.foo(a: 1)`) <br>
<br>
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.<br>
<br>
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.<br>
<br>
So yes, we deeply get that there is a pain point here. But this
is not the solution.<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
</font><br>
<br>
<div class="moz-cite-prefix">On 11/26/2024 5:13 AM, Øyvind Kvien
wrote:<br>
</div>
<blockquote type="cite" cite="mid:CAKipqtLvnHZa5bQhHWYXso-w9XCDQxYzMttXFGnHzwik5vC_SQ@mail.gmail.com">
<div dir="ltr">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.
<div><br>
<div>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.</div>
<div><br>
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.<br>
<br>
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).
<div><br>
</div>
<div> record RequestBody(<br>
String audience,<br>
boolean withoutDefaultClientClaims,<br>
boolean withoutDefaultUserClaims,<br>
boolean createDPoPTokenWithDPoPProof,<br>
ExpirationParameters expirationParameters,<br>
ClientClaimsParameters clientClaimsParameters,<br>
@Nullable UserClaimsParameters
userClaimsParameters,<br>
@Nullable DPoPProofParameters
dPoPProofParameters) {<br>
}<br>
<br>
record ExpirationParameters(<br>
int expirationTimeInSeconds) {<br>
}<br>
<br>
record ClientClaimsParameters(<br>
List<String> scope,<br>
String orgnrParent,<br>
String clientId,<br>
String clientName,<br>
String jti) {<br>
}<br>
<br>
record UserClaimsParameters(<br>
String pid,<br>
String hprNumber,<br>
String name,<br>
String givenName,<br>
String middleName,<br>
String familyName,<br>
String securityLevel,<br>
String assuranceLevel,<br>
String amr) {<br>
}<br>
<br>
record DPoPProofParameters(<br>
String htuClaimValue,<br>
String htmClaimValue) {<br>
}</div>
</div>
</div>
<div><br>
</div>
<div>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.</div>
<div><br>
</div>
<div>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.</div>
<div><br>
</div>
<div>Note that no parenthesis are used after 'new RequestBody'
for the default constructor as a syntax suggestion.<br>
<br>
public static RequestBody createRequestBodyUserToken(<br>
@Nullable String clientId,<br>
@Nullable String userNin<br>
) {<br>
return new RequestBody with { // No parenthesis after
'new RequestBody' for the default constructor.<br>
audience = "audience";<br>
withoutDefaultClientClaims = true;<br>
withoutDefaultUserClaims = true;<br>
createDPoPTokenWithDPoPProof = true;<br>
expirationParameters = new ExpirationParameters
with {<br>
expirationTimeInSeconds = 300;<br>
};<br>
clientClaimsParameters = new
ClientClaimsParameters with {<br>
scope = List.of("openid", "scope");<br>
orgnrParent = "12345";<br>
clientId = clientId != null ? clientId :
"13edc8d1-3fa2-425a-9b53-c346df79e589";<br>
clientName = "SmokeTest";<br>
jti = UUID.randomUUID().toString();<br>
};<br>
userClaimsParameters = new UserClaimsParameters
with {</div>
<div> pid = userNin != null ? userNin :
"12345678912";<br>
hprNumber = "987654";<br>
name = "Half Badger";<br>
givenName = "Half";<br>
middleName = "";<br>
familyName = "Badger";<br>
securityLevel = "4";<br>
assuranceLevel = "high";<br>
amr = "pwd";<br>
};<br>
dPoPProofParameters = new DPoPProofParameters
with {<br>
htuClaimValue = "GET";<br>
htmClaimValue = "<a href="https://myservice/test" moz-do-not-send="true" class="moz-txt-link-freetext">https://myservice/test</a>";<br>
}<br>
};<br>
}</div>
<div><br>
</div>
<div>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.<br>
<br>
private static RequestBody createRequestBodyUserToken(<br>
@Nullable String clientId,<br>
@Nullable String userNin<br>
) {<br>
var expirationParameters = new ExpirationParameters(<br>
300<br>
);<br>
<br>
var clientClaimsParameters = new
ClientClaimsParameters(<br>
List.of("openid","scope"),<br>
"12345",<br>
clientId != null ? clientId :
"13edc8d1-3fa2-425a-9b53-c346df79e589",<br>
"SmokeTest",<br>
UUID.randomUUID().toString()<br>
);<br>
<br>
var userClaimsParameters = new UserClaimsParameters(<br>
userNin != null ? userNin : "12345678912",<br>
"987654",<br>
"Half Badger",<br>
"Half",<br>
"",<br>
"Badger",<br>
"4",<br>
"high",<br>
"pwd"<br>
);<br>
<br>
var dPoPProofParameters = new DPoPProofParameters(<br>
"GET",<br>
"<a href="https://myservice/test" moz-do-not-send="true" class="moz-txt-link-freetext">https://myservice/test</a>"<br>
);<br>
<br>
return new RequestBody(<br>
"audience",<br>
true,<br>
true,<br>
true,<br>
expirationParameters,<br>
clientClaimsParameters,<br>
userClaimsParameters,<br>
dPoPProofParameters<br>
);<br>
}</div>
<div><br>
</div>
<div>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.</div>
<div><br>
</div>
<div>Regards</div>
<div>Øyvind Kvien</div>
<div><br>
</div>
</div>
</blockquote>
<br>
</body>
</html>