> 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
