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.

 - 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.

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.

(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.)

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, [email protected] wrote:



        ------------------------------------------------------------------------

            *From: *"Brian Goetz" <[email protected]>
            *To: *"Remi Forax" <[email protected]>
            *Cc: *"Viktor Klang" <[email protected]>,
            "amber-spec-experts" <[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