> From: "Brian Goetz" <[email protected]>
> To: "Remi Forax" <[email protected]>
> Cc: "Viktor Klang" <[email protected]>, "amber-spec-experts"
> <[email protected]>
> Sent: Wednesday, January 21, 2026 7:01:52 PM
> Subject: Re: Data Oriented Programming, Beyond Records

>> The other solution seems to allow a carrier class to not be restricted to 
>> only
>> describe data so like a record equals/hashCode/toString are derived from the
>> fields that define a component.

> I'd like to stay away from the "macro generator" view of the world, and 
> instead
> talk about semantics and conceptual models. The more we describe things in
> terms of macro generation, the less users will be able to _see_ the underlying
> concepts.

> I believe I had already anticipated and preemptively dismissed the model you
> outline below, but I'm sure it was easy to miss. But here's the problem:
> `component` fields are supposed to be implementation details of _how_ a 
> carrier
> class achieves the class contract. Having the semantics of equality depend on
> which fields are component fields has several big problems:

> - It means that clients can't reason about what equality means by looking at 
> the
> class declaration.

> - It means that the behavior of a class will change visibly under
> harmless-looking implementation-only refactorings, such as migrating a field
> from a component field to some other representation.
yes, both are true, this is also true for any classes in general. 

> - Having flung open the door labeled "macro generator", users will instantly
> demand "I want this to be a component field to get the accessor and 
> constructor
> boilerplate, but I don't want it part of equals, can I please have a modifier
> to condition what counts in equals and hashCode, #kthxbye". This violates Rule
> Number One of the record design, which is "no generation knobs, ever". Once 
> you
> have one, you are irrevocably on the road to Lombok.
I think this is true for any proposals that generate code for implementing a 
carrier class, including the one you propose before. 

When designing records, we have established that the only reasonable 
proposition that can not be labelled as a code generator is ... a record. 

> So there are two stabled, principled alternatives:

> - Just don't ever try to derive equals and hashCode
> - Derive equals and hashCode similarly as for records

> And of course, the first means that records cannot be considered special cases
> of carriers. So the latter seems a forced move.
I still think there is value to consider that a carrier class is an abstract 
specification that just say that it can be constructed/deconstructed but 
equals/hashCode are user defined. 

When you use only pattern matching, you do not really need equals/hashCode to 
be defined, you recursively pattern-match until you have primitive values. 

> (Meta observation: it is easy, when you have your Code Generator hat on, to
> suggest reasonable-seeming things that turn out to be semantically 
> questionable
> or surprising or inconsistent; this is why we try so hard to approach this
> semantics-first.)
Rémi 

>> The carrier class feature:
>> A carrier class is a class that define "virtual" components
>> - the accessors must be implemented or declared abstract
>> - the canonical constructor must be implemented if the class is concrete
>> - the record pattern works using accessors.

>> A carrier class is not restricted to be a data class (so no
>> equals/hashCode/toString), it can represent any class that can be
>> constructed/deconstructed as several virtual components.

>> The component field feature:
>> A field can be declared as "component", i.e. implementing one of the virtual
>> component
>> - the field is automatically strict
>> - the accessor corresponding to the field can be derived (if not @Override)
>> - if at least one of the field is declared as component,
>> equals/hashCode/toString can be derived
>> - if at least one of the field is declared as component, the compact 
>> constructor
>> syntax is available and inside it, all the fields marked as component are
>> initialized automatically
>> - if all virtual components have an associated component field, the canonical
>> constructor can be derived

>> regards,
>> Rémi

>>> On 1/20/2026 1:35 PM, [ mailto:[email protected] | [email protected] ] 
>>> wrote:

>>>>> From: "Brian Goetz" [ mailto:[email protected] | 
>>>>> <[email protected]> ]
>>>>> To: "Remi Forax" [ mailto:[email protected] | <[email protected]> ]
>>>>> Cc: "Viktor Klang" [ mailto:[email protected] | 
>>>>> <[email protected]>
>>>>> ] , "amber-spec-experts" [ mailto:[email protected] |
>>>>> <[email protected]> ]
>>>>> Sent: Tuesday, January 20, 2026 4:28:14 PM
>>>>> Subject: Re: Data Oriented Programming, Beyond Records

>>>>>>>> The modifier "component" is too close to the "property" modifier I 
>>>>>>>> wanted to
>>>>>>>> include years ago, it's just to sugary for its own good.

>>>>>>> You know the rule; mention syntax and you forfeit the right to more 
>>>>>>> substantial
>>>>>>> comments....
>>>>>> I'm talking about the semantics, especially the fact that 
>>>>>> equals/hashCode and
>>>>>> toString() are derived from.

>>>>> Except that equals/hashCode have nothing to do with the "component" 
>>>>> modifier _at
>>>>> all_. They are derived from the _state description_, in terms of the
>>>>> _accessors_, whose existence is implied directly by the _state 
>>>>> description_.
>>>> I do not think you can do that, because it means that a record is not a 
>>>> carrier
>>>> class.

>>>> Do you agree that equals() and hashCode() of a record are not derived from 
>>>> the
>>>> accessors ?

>>>> regards,
>>>> Rémi

Reply via email to