Should JEP 468 derived withers be opt in?

Adam Gent me at adamgent.com
Thu Nov 7 16:50:14 UTC 2024


I generally agree and I'm hoping to eliminate this concern (I was hesitant to even bring it up) but the current manual `withers` do differ then proposed withers in that they have access to the previous instance (that is withName with the time could check that the time is newer than "this").

While yes records do provide public construction with all fields (and thus what you can do with that is the same as withers) but the idea is if you are creating the initial record you have more information to create it correctly than perhaps downstream where one just wants to update/transform a few fields.

The thing is records I assume (and probably falsely) are akin to ML / Haskell style records. In those languages there is no behavior in the construction. They are pure data so validation needs to happen elsewhere. Thus pure records are rarely API and are encapsulated. Java records do have the validation in the constructor and thus are more likely to be public API.

I agree though this all pretty weak arguments. And obviously would prefer Java with withers then this minor problem of "trying" to force users to use certain methods of construction. I just wanted to give feedback on record usage currently and how I'm surprised at the sheer number of "wither" like methods we have that take multiple arguments but that could be largely the pain of adding withers. I shall look more into our  newer code base later this week.

Thanks for the reply!

-Adam

On Thu, Nov 7, 2024, at 10:57 AM, Brian Goetz wrote:
> Record constructors are public and can (theoretically) set all the 
> fields.  So if you have a record with invariants (such as "name != null" 
> or "x < y") these should be checked in the constructor.
>
> If they are checked in the constructor, they will also be checked 
> implicitly in a `with` expression, since `with` expressions go back 
> through the constructor to produce the result.
>
> It seems that in your example, the requirement you want to impose is 
> "when I call withName on a Something, I must update the time too." But 
> even without withers, your program doesn't enforce that:
>
>      Something s = new Something("Bob", randomTime());
>      Something ss = s.withName("Mary", s.updateTime());
>
> So while ss is derived from s, they share the same update time.  The 
> requirement you are trying to enforce is a "handshake" one: "when you 
> update something, set the update time."  And if handshake enforcement is 
> enough without withers, it is enough with withers too:
>
>      ss = s with { name = "Mary"; updateTime = now(); }
>
>
>
>
> On 11/7/2024 9:28 AM, Adam Gent wrote:
>> One concern I have with "Withers" is that they don't codify the need or requirement of multiple fields to be set at the exact time.
>>
>> Currently folks are probably making static methods on records for creation. After (ab)using records in our code base that is less academic and more business we frequently have a time field.
>>
>> record Something(String name, Instant updateTime, ... other fields) {
>>     static Something withName(String name, Instant updateTime) {
>>      ...
>>      }
>>     // Other with methods always including updateTime.
>> }
>>
>> I admit my example is poor but the idea is that we want to encodify that the time needs to be set on each call.
>>
>> If JEP 468 comes through one could create the record without specifying the time field.  Basically withers allow each field to be updated independently. That is JEP 468 sort of increases the API of existing code.
>>
>> So I wonder if a safer backward compatibility is to provide some sort of marker annotation? e.g. `@Withers` placed on the record or some other way to mark that the record is safe to have withers.
>>
>> Thoughts?
>>
>> Cheers
>> -Adam


More information about the amber-dev mailing list