CFR - updated 8001667: Comparator combinators and extension methods

Henry Jen henry.jen at oracle.com
Thu Mar 7 22:57:42 PST 2013


I think it all boiled down to re-use an existing Comparator to compare
for another type.

What about we added this to Comparator<T> as a default method,

>     default <S> Comparator<S> apply(Function<S, ? extends T> keyExtractor) {
>         Objects.requireNonNull(keyExtractor);
>         return (Comparator<S> & Serializable)
>             (c1, c2) -> compare(keyExtractor.apply(c1), keyExtractor.apply(c2));
>     }

Then the code you illustrated would be,

people.sort(CASE_INSENSITIVE_ORDER.apply(Person::getLastName)
	.thenComparing(nullsLast.thenComparing(CASE_INSENSITIVE_ORDER)
		.apply(Person::getEmailAddress)));

byKey and byValue is actually added based on the same thought that when
something is not a Comparable. With above, it can be replaced with

cmp.apply(Map.Entry<K,V>::getKey);

This is just inverse thinking of comparing, where the thoughts is mainly
for Comparable and primitive type. For those, comparing/thenComparing is
a more convenient and comprehensive expression.

Thoughts?

Cheers,
Henry


On 03/06/2013 03:06 PM, Michael Hixson wrote:
> On Wed, Mar 6, 2013 at 1:01 PM, Henry Jen <henry.jen at oracle.com> wrote:
>> On 03/06/2013 02:31 AM, Michael Hixson wrote:
>>
>>> 1. Why disable the following code even though it is (theoretically) safe?
>>>
>>>   Comparator<CharSequence> a = comparing(CharSequence::length);
>>>   Comparator<String> b = a.thenComparing(CASE_INSENSITIVE_ORDER);
>>>
>>> That code would compile if the signatures of the thenComparing methods
>>> had generics like <S extends T> instead of <T>.  The example above is
>>> conjured up and ridiculous, but I think real code will have use for
>>> it.  Is there any downside to narrowing the return type this way?
>>>
>>
>> I think that make sense, will need to do some experiment to make sure it
>> won't confuse type system when chaining all together, and I am not sure
>> how much burden this has on inference engine. I prefer simplicity.
>>
>> Theoretically, if all function take Comparator carefully allows super
>> type, which we have, isn't that enough? Your above example can be,
>>
>> Comparator<String> a = comparing<CharSequence::length);
>> a.thenComparing(CASE_INSENSITIVE_ORDER);
>>
> 
> The idea is that I wanted to use both comparators.  It's important
> that "a" remains Comparator<CharSequence> because I want to sort
> List<CharSequence> objects with it and use it to generate other
> CharSequence-subclass comparators in addition to "b".
> 
> Also, min/max will need <S extends T> or else it will break Guava's
> Ordering class.  The same thing happened a while back with
> comparator.reverse() (which was then renamed to reverseOrder).  If the
> only reason for the rename was to unbreak Guava, then if you use <S
> extends T> you could change it back because the signatures will match.
> 
> (Maybe the Guava devs have more insight about this signature?  They
> went that route for most of Ordering's methods.  Some of the same
> reasoning might apply here.)
> 
>>
>>> 2. There's a thenComparing(Function), but no thenComparing(Function,
>>> Comparator).  This seems like a big omission.  Surely people will want
>>> secondary orderings on fields by something other than natural
>>> (null-unfriendly) ordering.  Also, a Comparators.comparing(Function,
>>> Comparator) method would remove the need for all the Map-centric
>>> methods that are currently in the Comparators class.  For instance:
>>> comparing(Map.Entry::getValue, naturalOrder()).
>>>
>>
>> Note that Function form must return a Comparable, which implies an order
>> already. Combine with Comparator.reverseOrder() method, that would cover
>> the ground.
>>
>> If the Comparable is not a fit, probably write a Comparator in lambda is
>> needed anyway?
>>
>>> 3. There is no support for sorting of nullable values or fields.
>>> Sorting of nullable values directly perhaps should not be encouraged,
>>> but sorting values by nullable fields within a chained sort is
>>> completely reasonable.  Please add some support for this, either as
>>> chain methods on Comparator, or as factory methods on Comparators.
>>>
>>
>> Not sure what do you propose to be added. NULL is a controversial topic,
>> only application know what it means. Therefore, avoid try to interpret
>> null is probably a better approach. Write a Comparator if needed.
>>
> 
> Regarding comparing(Function,comparator) and nulls - I'm possibly just
> repeating old arguments but I'll give it another shot.
> 
> There are use cases for these all over the code base I ported to Java
> 8.  I'll repost the example from my first email since I think that may
> answer your question about nulls:
> 
>   // Sort a list of people by:
>   //  1) Last name, ignoring case
>   //  2) Email address, with no email (null) last, ignoring case
>   people.sort(
>       comparing(Person::getLastName, CASE_INSENSITIVE_ORDER)
>           .thenComparing(
>               Person::getEmailAddress,
>               nullsLast().thenComparing(CASE_INSENSITIVE_ORDER)));
> 
> The Comparators class itself presents four use cases for
> comparing(Function,Comparator).  I don't think they're especially good
> cases, but: naturalOrderKeys, naturalOrderValues, byKey, byValue could
> all be done instead as comparing(Map.Entry::get{Key,Value},c).  It is
> odd to me that four specialized versions of this are being offered (I
> can't recall the last time I wanted to sort map entries) but the more
> primitive operation behind them is not.
> 
> -Michael
> 



More information about the lambda-dev mailing list