Feedback on records (JEP 359)
Anthony Vanelverdinghe
anthonyv.be at outlook.com
Sat Apr 25 18:05:30 UTC 2020
Hi Brian
On 25/04/2020 17:42, Brian Goetz wrote:
>
>> it reminds people that the canonical constructor is:
>> a) present (unlike classes, where, if you define a constructor
>> yourself, the implicit constructor doesn't exist)
>> b) public (if there's a single private constructor, I regularly
>> forget that there's still a public constructor as well)
>
> I realize there is a precedent that "you only get a free constructor
> if you don't bring one of your own." But, do you believe that Java
> developers are unable to learn a refined version of this rule for
> records?
Unable, no, but some developers such as myself will have a hard time to
unlearn the reflex of seeing a constructor and concluding there's no
implicit constructor then. So that's why I felt it would be useful, but
I guess maybe IDEs could help here, by reminding me about the existence
of the implicit constructor whenever I declare an explicit one.
>
> We considered which additional protocols (aside from construction,
> deconstruction, equality, hashing, and string display) could and
> should be derived from the state description, and Comparable was the
> first one on that list. The problem with doing this is that, unlike
> the ones listed above, the order in which the components are compared
> is semantically significant, *and*, when you want comparators for
> records, you often don't even want to include all the components. The
> "automatic" comparison protocol you'd derive from the state
> description would almost never be what you wanted, and then you'd be
> coming back asking for "knobs" to determine which components flow into
> the comparison and in what order.
Would you mind to clarify why the order [...] being semantically
significant is a problem? I must be missing something, but I fail to see
what.
Since the order of the components already defines their order in the
canonical constructor, I feel that having it define their comparison
order as well isn't a stretch.
If you'd ask people to keep a phone book in a plain text file, I'd
assume they'd write each person on a line as `last name, first name,
phone number`, rather than any other order, and keep the entries sorted
as well, by last name, then first name.
Yes, you're right in that you often don't want to include all the
components. In the phone book example, barely anyone would bother
sorting the phone numbers of a person that has multiple numbers. But
that's exactly one of the reasons why I often find myself splitting up
records into smaller records: because the components I want to use for
comparison naturally belong together and should really be in a record of
their own. In this case: a phone book entry really has 2 components: a
Comparable record Person, and the phone number.
So I wouldn't be coming back asking for "knobs": I'd just split off
subrecords, and think about the logical sorting order when declaring the
record.
Another reason why I feel it would be good to have this as part of a
protocol, is that it would be guaranteed that compareTo is consistent
with equals (insofar that the implementations of each component are
consistent). On the other hand, if Comparable has to be implemented
manually, developers would be much less inclined to split off
subrecords, and their implementations would most likely not be
consistent with equals (which isn't the end of the world, but as you
know their usage in SortedSets, or as the key in SortedMaps, would not
correctly implement the Set/Map interface).
>
> FWIW, most of the actual boilerplate in your above Comparator is
> folding in the null handling. It might be an RFE on Comparator
> factories would be more effective in solving this problem, and also
> more useful (as it wouldn't only apply to records.) If your
> comparator was (say)
>
> Comparator.comparingNullable(YearMonth::year)
> .thenComparingNullable(YearMonth::month);
>
> or similar, would you be as concerned?
That would surely be helpful (I'll file a bug report for it), but I
believe `sorted-record` would still be useful.
>
> One more point on this: when we did streams, we started down the
> direction of having sort methods on streams, and it kind of exploded;
> we wanted to sort not only by natural-ordered comparables, but with
> comparators, and with primitives, which meant for five variants;
> reverse sort was another 2x; the four stream implementations was
> another 4x, so that meant 60 sorting methods just in streams, all
> motivated by wanting to be able to sort by providing a lambda that
> extracted a sort key. Then we realized we were being idiots, that
> adding half a dozen factories and combinators for Comparator cut
> through the explosion, but also, that they were useful far beyond
> streams. I think the same thing is true here; the problem your are
> experiencing comes via records, but it is really a problem with
> Comparator, and it is better to address the problem at the root.
As I see it, the problem is that, for me, something like `record
TrackId(Year year, String albumTitle, int trackNumber)` just screams at
me: "hey, I'm Comparable, just as you'd expect me to be". And it bothers
me that I have to write the code nonetheless :)
>
>> This is tedious to write, but seems relatively easy for a compiler to
>> generate.
>
> ... but only if the compiler knows which fields are significant for
> comparison, and in which order you want to compare them.
>
>
Kind regards, Anthony
More information about the amber-dev
mailing list