daniel.smith at oracle.com
Fri Jul 12 15:23:34 PDT 2013
On Jul 12, 2013, at 4:03 PM, Zhong Yu <zhong.j.yu at gmail.com> wrote:
> Consider these three methods
> public interface CompletionStage<T>
> CompletionStage<Void> thenRun(Runnable action)
> CompletionStage<Void> thenAccept(Consumer<? super T> action)
> <U> CompletionStage<U> thenApply(Function<? super T,? extends U> fn)
> Would it be safe to overload them, e.g. name all three as "then()"?
> (assuming that's a good idea)
> <U> then(T->U)
> Could this overloaded design cause any ambiguity anywhere, or cause
> other kinds of problems?
Something I've been working on is a guide to when it's safe to overload. I will eventually post the full write-up for people to study. To summarize:
A set of methods is "amenable to disambiguation" if, for every pair of "overlapping" declarations, a "most specific" declaration exists. Thus, to facilitate disambiguation, when a method is declared, it should either i) not overlap with other declarations, or ii) where there is an overlap, have a most specific declaration.
Two functional interface types can be considered to "overlap" if they have the same number of parameter types, and any of:
- The descriptors have the same parameter types and overlapping return types ('void' overlaps with everything)
- The descriptors have different parameter types
- At least one descriptor parameter type relies on context-dependent inference (not applicable in this case)
So, the Runnable method is okay because its descriptor has a different arity. Consumer and Function overlap, so the next thing to check is whether one is "more specific" than the other. This is the case: Function is more specific than Consumer because they have the same parameter types, and Function has a more specific return type (everything is more specific than "void").
(Note that these rules don't guarantee a lack of ambiguity errors. There are still corner cases, like a method reference where one method overload matches the shape of Runnable and another matches the shape of Function. But they ensure a "reasonable" expectation of no ambiguities.)
> Another question. Consider these two methods
> interface Foo<T>
> #1 <R> Foo<R> map( F1<T, R> );
> #2 <R> Foo<R> map( F2<T, Foo<R>> ); // AKA "flat map"
> where F1 and F2 are exactly like Function, and there's no subtyping
> relationship between the two.
> The two map() methods may create ambiguity when both are applicable to
> a lambda expression (that returns a Foo<?>). However, is there a
> notion that #2 is more specific than #1, therefore #2 is chosen over
> (Analogy - according to JLS7, #4 is more specific than #3
> #3 <R> void foo(R)
> #4 <R> void foo(List<R>)
> intuitively whenever #4 is applicable, #3 is applicable. So is there a
> similar rule for cases like #1 and #2?)
Right. This is what we do. There exists an R1 such that Foo<R2> <: R1 (for a fixed R2), so the second can be considered more specific than the first. And there does _not_ exist an R2 such that R1 <: Foo<R2> (for a fixed R1), so the second is the most specific. (This isn't fully specified yet; it belongs in 18.5.4 of Part G of the spec.)
> If the answer is no, I guess we can always make F2<:F1, which makes #2
> more specific than #1, correct?
Yes, that will also work, as long as the descriptor parameter types are the same (otherwise, you've potentially got two completely different interpretations of the lambda body, and we won't try to claim that one is better than the other).
More information about the lambda-dev