Java 14 records canonical constructor
Christian Beikov
christian.beikov at gmail.com
Thu Jun 4 20:18:41 UTC 2020
Am 04.06.2020 um 21:39 schrieb Brian Goetz:
> OK, this seems a totally different question to the one you initially
> asked :)
>
> You're basically saying: why is a record constructor allowed to do
> _anything_ other than check invariants?
>
> I understand where this desire comes from. But, let's pull on that
> string.
>
> Suppose you want to write a record like:
>
> record Rational(int num, int denom) { }
>
> It seems pretty reasonable to want to normalize the numerator and
> denominator to lowest terms; among other things, this makes it easy to
> obtain the likely-desired invariant of `new Rational(1, 2)` equals
> `new Rational(2, 4)`. Sure, you could export that responsibility on
> the client, or provide a separate factory `Rational::inLowestTerms`,
> but surely you see how that is more error-prone.
Let's say that normalization is allowed in the canonical constructor,
how would that interact with e.g. pattern matching which I assume is the
main consumer of the "re-construction" property of records.
Would the following match the first or the second case?
switch (new Rational(1, 2)) {
case Rational(2, 4):
break;
case Rational(1, 2):
break;
}
I guess what I am asking is, will the pattern cause a Rational object to
be instantiated such that the normalization can happen for that pattern
object as well? If not, then it seems to me that a canonical constructor
that alters/mutates the arguments is broken and can't be used for
pattern matching.
> Suppose you have a record whose components are mutable (say, an
> array.) You might well want to perform a defensive copy on the way in
> (and, possibly on the way out, in the accessor.)
> You might be willing to say "ban all these uses, they should all be
> illegal", and that would be a principled and defensible position. But
> surely you see as well that this would lead to less useful records.
> Now, we struggled a lot with whether we can forbid the "bad" cases and
> keep the "good" cases. This went about as well as you might expect.
> So instead, we settled on allowing users to override accessors, to
> normalize arguments in the constructor, and to override equals, with
> the caveat that `java.lang.Record` imposes some stronger invariants on
> `equals()`, and you'd better maintain those.
I'm not saying that these use cases should be banned, but maybe the
canonical constructor should be made package-private by default? A user
can then use the "compact named constructor" or a custom constructor for
doing defensive copying. That way the state can be kept deeply immutable
and at the same time the canonical constructor is ensured to be safe.
More information about the amber-dev
mailing list