<div dir="auto">Thanks for explaining; I think there are a few issues with your reasoning, however:</div><div><br><div class="gmail_quote"><div dir="ltr" class="gmail_attr"><br></div><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left-width:1px;border-left-style:solid;padding-left:1ex;border-left-color:rgb(204,204,204)">
<div dir="auto">As it turns out, the mathematical construct from Domain Theory known
as "embedding-projection pairs" (which is a formalization of
approximation) offer us a useful way to talk about the right thing.
The key invariant that records must adhere to (specified in the
refined contract of `Record::equals`) is that unpacking a record
components, and repacking in a new record, should yield an "equals"
record:<br>
<br>
r.equals(new R(r.c0(), r.c1(), ..., <a href="http://r.cn" target="_blank">r.cn</a>())<br>
<br>
(This is as close as we can say in Java to "construction and
deconstruction form an embedding-projection pair between the space
of records and the cartesian product space of their components.") </div></blockquote><div dir="auto"><br></div><div dir="auto">There are actually two conditions in the definition of an embedding-projection pair (I am using “o” for function composition below);</div><div dir="auto"><br></div><div dir="auto">1) p o e = id</div><div dir="auto">2) e o p <= id </div><div dir="auto"><br></div><div dir="auto">Where <= means, roughly speaking, “less defined than”; it is equality, but allowing the left-hand side to be partial, i.e. non-terminating on some inputs</div><div dir="auto"><br></div><div dir="auto">Following your “translation” into Java (and ignoring several technicalities that make it problematic…), taking the constructor to be e and the destructors to be p,</div><div dir="auto"><br></div><div dir="auto">1) means:</div><div dir="auto"><br></div><div dir="auto">(new R(a1, …, an)).p1().equals(a1)</div><div dir="auto">… and …</div><div dir="auto">(new R(a1, …, an)).pn().equals(an)</div><div dir="auto"><br></div><div dir="auto">2) means (roughly)</div><div dir="auto"><br></div><div dir="auto">new R(r.p1(), …, <a href="http://r.pn">r.pn</a>()).equals(r) *OR* new R(r.p1(), … <a href="http://r.pn">r.pn</a>()) fails to terminate</div><div dir="auto"><br></div><div dir="auto">2) is close to the condition you claimed, but look at 1) — it *forces* the accessors to return the inputs to the constructor *according to the existing equals() defined on the inputs* — so, when equals() on the inputs is reference equality, the accessor is forced to return the exact same reference.</div><div dir="auto"><br></div><div dir="auto">So, ok, this just means that the embedding-projection pair is not the right tool to make your case, that’s ok. (Although I’m not sure where you got the impression it would — it is a very specific definition, used almost exclusively in the construction of recursive types in domain theory).</div><div dir="auto"><br></div><div dir="auto">Instead, your condition is simply e o p = id — the accessors form a right inverse of the constructor, or the constructor is a left inverse of the accessors, fine.</div><div dir="auto"><br></div><div dir="auto">Note that if your constraint is the only one applied, that permits definitions of equality that satisfy properties like R(a,b).equals(R(b,a)) — records can be made “unordered” and the accessors can roll a die to decide which component to return, as well as more complex quotients, like R(a,b).equals(R(c,d)) exactly when a*d = b*c (the rational numbers). Maybe that’s intentional? You tell me.</div><div dir="auto"><br></div><div dir="auto"><br></div><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left-width:1px;border-left-style:solid;padding-left:1ex;border-left-color:rgb(204,204,204)"><div dir="auto">The reason that the situation you describe is inevitable comes from …</div></blockquote><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left-width:1px;border-left-style:solid;padding-left:1ex;border-left-color:rgb(204,204,204)"><div dir="auto"><br>
<br>
If your component already chooses the answer for equals that you
want -- such as ArrayList::equals comparing lists by contents, or
arrays comparing by identity -- then you can do nothing. Otherwise,
you have to override the constructor, accessor, and equals in
concert to preserve the invariant that deconstruction and
reconstruction yields something equivalent to the original. <br></div></blockquote><div dir="auto"><br></div><div dir="auto">There are already at least two ways to express this in Java, the first being to wrap the components in a class that defines the equality you want, and the second is to use a plain old class instead of a record. What is missing from Java is the ability to express plain old Cartesian products where the behaviour of the accessors and equality is *known* and well-understood by all. So I would hardly call the current situation “inevitable”.</div><div dir="auto"><br></div><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left-width:1px;border-left-style:solid;padding-left:1ex;border-left-color:rgb(204,204,204)"><div dir="auto">Consistent with the choice we've made elsewhere
(functional interfaces are nominal function types, not structural
ones; sealed types are nominal union types, not structural ones),
the rational choice for Java's product types is also nominal, not
structural. </div></blockquote><div dir="auto"><br></div><div dir="auto">I think there is a confusion here between structural *types* and structural *equality*? Nominal typing means simply that types are declared equal when they have the same name, while structural typing means types are declared equal if they have the same “structure” (“definition”). Records could force structural equality but remain nominally typed. </div><div dir="auto"><br></div><div dir="auto">Note also that Java has been a hybrid of structurally typed and nominally typed ever since the introduction of generics — we no longer “name” the type of “lists of integers” IntList — instead we compare the types List<SomeComplexTypeExpression> and List<SomeOtherComplexTypeExpression> by comparing the structure of the types — that SomeComplexTypeExpression is the same as SomeOtherComplexTypeExpression, allowing type variables inside SomeComplexTypeExpression to be instantiated along the way.</div><div dir="auto"><br></div><div dir="auto">Pair<T,S> is actually a kind of structural record type!!</div><div dir="auto"><br></div><div dir="auto">So structural typing and nominal typing are not in conflict, they can and do work in harmony.</div></div></div>