Data Oriented Programming, Beyond Records

Brian Goetz brian.goetz at oracle.com
Wed Jan 21 19:34:17 UTC 2026


> In other words, I like Remi's conceptual idea of carrier classes being 
> "(possibly abstract) things with virtual components". Really that's 
> almost like saying "modernized java beans". Then a record could still 
> be a specialized, locked down, concrete form of a carrier class.

It is fair to say "I want to consider a different semantic meaning for ' 
a class that has a state description '."  And it is also fair to 
question "did records get it wrong?" as part of that discussion. Indeed, 
there were half a dozen different potential semantics for "class with 
header" that were raised during the design of records, and this variety 
was one of the reasons its taken us this long to figure out which we 
like the best.

One of the implicit, but deeply held, design choices made here is that 
the class header is a semantic statement about what this class *is*.  
Records had the cutesy slogan "the state, the whole state, and nothing 
but the state", which may or may not be the best slogan, but it does 
allow you to describe record-ness pretty concisely.  We are looking to 
get as close to such a crisp semantic statement as possible, ideally 
leaving words like "generate" out of the process.

The proposal we put forth is that a carrier class C for state 
description S *is* a carrier for the data tuple described by S, and 
anything more is "incidental".  This turns out to be a pretty strong 
statement:

  - If C is just a carrier for an S-shaped slug of data, then (for 
concrete C) I must be able to take an S-shaped slug of data and produce 
a C (canonical constructor.)
  - If C is just a carrier for an S-shaped slug of data, then I must be 
able to extract that slug from a C (deconstruction pattern.)
  - If C is just a carrier for an S-shaped slug of data, then taking a C 
apart (into an S), and creating a new C from that same S (via the 
constructor) _must yield an equivalent C_.  Otherwise is is not "just" a 
carrier for S.

If you read the spec for Record::equals, you'll see that this invariant 
-- take it apart with accessors, put it back together unchanged with the 
constructor, the result must be equals() -- is written into the Record 
spec.  Not because we chose to "generate" equals and hashCode, but 
because of what the "just" in "just a carrier for S" means.

If this were not the case for carriers, then it must be because carriers 
are making some much weaker claim about what carrier-ness means.  But 
you can't claim much less without giving up a lot of benefits, too.  If 
we backpedal on the _completeness_ claim (the state description is a 
complete, canonical, nominal description of the classes state), you 
could argue that we then have no basis for default equality semantics 
other than identity-based, but we also undermine the semantic basis for 
reconstruction -- without the completeness claim, then `x with {}` can't 
be seen as "give me an equivalent x".

This model of "a carrier class is one that has at these properties" is a 
credible one (in a vacuum), but you get much less for it; you don't get 
reconstruction, the constructor and deconstructor can't really be 
considered canonical, and it is a semantic departure from recordness.




More information about the amber-spec-experts mailing list