<html><body><div style="font-family: arial, helvetica, sans-serif; font-size: 12pt; color: #000000"><div><br></div><div><br></div><hr id="zwchr" data-marker="__DIVIDER__"><div data-marker="__HEADERS__"><blockquote style="border-left:2px solid #1010FF;margin-left:5px;padding-left:5px;color:#000;font-weight:normal;font-style:normal;text-decoration:none;font-family:Helvetica,Arial,sans-serif;font-size:12pt;"><b>From: </b>"Brian Goetz" <brian.goetz@oracle.com><br><b>To: </b>"Remi Forax" <forax@univ-mlv.fr><br><b>Cc: </b>"Viktor Klang" <viktor.klang@oracle.com>, "amber-spec-experts" <amber-spec-experts@openjdk.java.net><br><b>Sent: </b>Wednesday, January 21, 2026 8:54:40 PM<br><b>Subject: </b>Re: Data Oriented Programming, Beyond Records<br></blockquote></div><div data-marker="__QUOTED_TEXT__"><blockquote style="border-left:2px solid #1010FF;margin-left:5px;padding-left:5px;color:#000;font-weight:normal;font-style:normal;text-decoration:none;font-family:Helvetica,Arial,sans-serif;font-size:12pt;"><blockquote cite="mid:626263129.22463743.1769024919495.JavaMail.zimbra@univ-eiffel.fr">
<div style="font-family: arial, helvetica, sans-serif; font-size: 12pt; color: #000000">
<div>
<blockquote style="border-left:2px solid #1010FF;margin-left:5px;padding-left:5px;color:#000;font-weight:normal;font-style:normal;text-decoration:none;font-family:Helvetica,Arial,sans-serif;font-size:12pt;">But
also, you pay a big complexity tax when the new concept is
almost like but can't quite fully meet up with the old
concept; it again means that refactoring from record
<--> carrier comes with a significant sharp edge, and
this is a big warning signal. So we would need a much
stronger reason than "hmm, kind of like it better this way"
to choose this divergent path. So far I'm not seeing it?</blockquote>
<div><br>
</div>
<div>There is a big sharp edge between a record and a class
which is due to mutability, this is true inside the class
but also outside, the user code when something is mutable or
not is quite different .So refactoring from a mutable class
to to an unmodifiable record is a not battle we should be
interested in.</div>
</div>
</div>
</blockquote>
<br>
Just because carrier class state _can_ be mutable, doesn't mean it
_must_ be. So you're skipping over the interesting case, which is:<br>
<br>
record R(int x, ...) { }<br>
<br>
and<br>
<br>
final class R(int x, ...) { private final component int x; ...
} // not equivalent to above! <br>
<br>
In your model, the class version of R is painful to write, because
you have to write equals, hashCode, and toString that delegate to
each of the components. </blockquote><div><br data-mce-bogus="1"></div><div>If you want to model something mutable you have to maintain the invariants, if you want to have some fields to be component, you have to write equals/hashCode/toString.</div><div><br data-mce-bogus="1"></div><div>We can provide a more declarative syntax:</div><div>- for the former, the syntax can declare the preconditions and for each field how to do the defensive copy</div><div>- for the latter, the syntax can declare each field that are part of the equality dance so equals/hashCode/toString can be derived.</div><div><br data-mce-bogus="1"></div><div>A record is easier to write because it's a sum-types, so it's unmodfiable *and* you can derived equals/hashCode/toString, that's the sweet spot.</div><div><br data-mce-bogus="1"></div><blockquote style="border-left:2px solid #1010FF;margin-left:5px;padding-left:5px;color:#000;font-weight:normal;font-style:normal;text-decoration:none;font-family:Helvetica,Arial,sans-serif;font-size:12pt;">But that's not even the main point; it is
that while there is no theoretical distinction between a record and
a final class all of whose components are backed by final component
fields, there is a big and hard-to-explain discontinuity when you
start from either of those and try to refactor to the other.</blockquote><div><br></div><div>You are focusing on the gap between an unmodifiable class and a record, even if we fill that gap by adding the "component" feature,</div><div>the gap between modifiable and unmodifiable will still exist. And for me, i do not see why one gap is more important than the other.</div><div><br data-mce-bogus="1"></div><blockquote style="border-left:2px solid #1010FF;margin-left:5px;padding-left:5px;color:#000;font-weight:normal;font-style:normal;text-decoration:none;font-family:Helvetica,Arial,sans-serif;font-size:12pt;"> <br>
<br>
But you are still not justifying your preference; WHY is
identity-based equality the *obviously right* choice for carriers?
Be semantic please! Tell me what you think a carrier *means*. </blockquote><div><br data-mce-bogus="1"></div><div>For an enum, the semantics of equals() has to be ==, so a carrier enum should use the identity-based semantics.</div><div>For a data class, the semantics of equals is likely to not be == so a carrier data class has to override equals/hashCode.</div><div><br data-mce-bogus="1"></div><div><div>Basically, a carrier class does not take a side on what the semantics of equals should be, both are fine depending on the use case.</div></div><div><br></div><div>regards,</div><div>Rémi</div><div><br data-mce-bogus="1"></div></div></div></body></html>