Finding max or min of exactly two objects
Remi Forax
forax at univ-mlv.fr
Tue May 13 14:58:29 UTC 2025
> From: "Brian Goetz" <brian.goetz at oracle.com>
> To: "Tagir Valeev" <amaembo at gmail.com>, "core-libs-dev"
> <core-libs-dev at openjdk.org>
> Sent: Tuesday, May 13, 2025 4:30:33 PM
> Subject: Re: Finding max or min of exactly two objects
> When we did Lambda, we made a few mistakes in the category of adding default
> methods to some "highly abstract" types, such as Function::andThen. You were
> there; these were well-intentioned, but we neglected to think sufficiently
> about the consequences for subclasses. For example:
> interface Function<T,U> {
> U apply(T t);
> <V> default Function<T, V> andThen(Function<U,V> g) { ... }
> }
> seems fine, but then when you extend it:
> interface UnaryOperator<T> extends Function<T,T> {
> }
> you kind of lose, because you can't compose _unary operators_ using
> Function::compose. And if you try to refine it:
> interface UnaryOperator<T> extends Function<T,T> {
> default UnaryOperator<T> andThen(UnaryOperator<T> g) { ... }
> }
> you end up overloading, not overriding the method, in a way that clients cannot
> distinguish: `f.andThen(lambda)` will likely not be able to distinguish between
> the overloads, because they have the same shape. Ooops.
It's not clear to me that the mistake was adding a default method but not using inheritance in between UnaryOperator and Function.
A functional interface represents a structural type but by adding inheritance, we made it a weird mix.
> So with that as background, I am very cautious to consider adding methods to
> Comparable, because it is a highly abstract type that was designed for
> extension, and the risk of the above kind of clash seems "not low".
> Comparator seems less risky, because it is not designed to be extended by domain
> objects, but instead functions more like a type class in Haskell -- it is
> behavior _about_ a type, defined from the outside. And Haskell would agree with
> you that this move is sensible; here's Haskell's `Ord` (like Comparator), which
> extends `Eq` (providing equality.)
> class (Eq a) => Ord a where
> compare :: a -> a -> Ordering
> (<), (<=), (>), (>=) :: a -> a -> Bool
> max, min :: a -> a -> a
> compare x y = if x == y then EQ
> -- NB: must be '<=' not '<' to validate the
> -- above claim about the minimal things that
> -- can be defined for an instance of Ord:
> else if x <= y then LT
> else GT
> x <= y = case compare x y of { GT -> False; _ -> True }
> x >= y = y <= x
> x > y = not (x <= y)
> x < y = not (y <= x)
> -- These two default methods use '<=' rather than 'compare'
> -- because the latter is often more expensive
> max x y = if x <= y then y else x
> min x y = if x <= y then x else y
> {-# MINIMAL compare | (<=) #-}
> The `compare` method is like our Comparator method, just returning a
> three-valued enum rather than an int. (Haskell has a cool feature here, where
> you can define all the methods in terms of others, and then you can override
> whichever ones make sense to break the cycles. The MINIMAL annotation says "if
> you provide either compare or <=, you're good." I wish we had this for
> interfaces with default methods.) It then proceeds to derive `min` and `max`
> from `<=` (which might itself be derived from `compare`.)
> OK, "comparative languages" lesson over, back to your point. There are two ways
> to get where you want: a static method that takes a comparator and the operands
> (`Comparator.max(c, a, b)`), or a default method on Comparator (`c.max(a, b)`).
> (You say "add simple static methods ... to Comparator" but I think you mean to
> put the word `static` elsewhere in that sentence.)
> I am receptive to the idea of extending Comparator here, but would want to think
> about it more to feel out potential mistakes like the `andThen` one above. But
> your point is solid: a "comparator" is also a "maxxer" and a "minner" (neither
> of those are words, and if they were, are probably spelled wrong), and that is
> a natural place to locate such behavior.
In terms of performance, unlike Kotlin where you can create an inline method, static methods that wrap an interface call sometimes behave funnily in Java because the profiling done by the VM is attached to a bytecode instruction and not to the call graph (or at least to the caller site).
Sadly that means that Comparator.max(c a, b) can be significantly slower than c.compare(a, b).
Rémi
> On 5/13/2025 10:12 AM, Tagir Valeev wrote:
>> Hello!
>> Several times already when writing Java programs, I stumbled with a simple task:
>> given two objects with natural order, find the maximal of them. The algorithm I
>> need could be formulated like this:
>> <T extends Comparable<T>> T max(T a, T b) {
>> return a.compareTo(b) > 0 ? a : b;
>> }
>> Writing manually compareTo >= 0 looks too verbose, not very readable and
>> error-prone: one has to mention both objects twice, and it's possible to mix >
>> with <. I can surely add a method mentioned above to a utility class in my
>> project and use it everywhere. However, I feel that it deserves a place in the
>> standard library.
>> The alternatives we have now:
>> BinaryOperator.maxBy(Comparator.<T>naturalOrder()).apply(a, b);
>> This speaks clearly about the intent (we'd like to get the maximum and we write
>> 'maxBy') but very wordy.
>> Stream.of(a, b).max(Comparator.naturalOrder()).get();
>> Also clear and a little bit shorter, but has an unnecessary Optional in-between
>> (we know that we have at least one element, so the result is always present)
>> and we have to mention the comparator. Finally, it might be much less efficient
>> than expected.
>> Probably we can add simple static methods `max` and `min` either to the
>> `Comparator` interface, or to `java.util.Objects`? Such methods would
>> complement methods from the `Math` class for numbers. In addition, having
>> default methods `max` and `min` in the `Comparator` interface would also be
>> nice:
>> String bigger = String.CASE_INSENSITIVE_ORDER.max("Hello", "world");
>> What do you think? Can we proceed with such an enhancement?
>> With best regards,
>> Tagir Valeev
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://mail.openjdk.org/pipermail/core-libs-dev/attachments/20250513/481b0a5f/attachment-0001.htm>
More information about the core-libs-dev
mailing list