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