Ambiguous lambda overload in Guava Futures

2019-09-07 00:12发布

问题:

I believe I am facing an eclipse bug here, but I'd like to confirm.
I am using java 8 (jdk 1.8.0_102), and my code compiles normally, but eclipse gives me an error.

My code looks like this:

public ListenableFuture<ProtoBufExchange> myMethod(
   //some code here
   return Futures.transform(future,(Request.Builder reqBuilder) -> {

    //some code here

    return Futures.immediateFuture(exchange);
  }

The error shown in eclipse is this:

The method transform(ListenableFuture<Request.Builder>, AsyncFunction<? super Request.Builder,? extends ProtoBufExchange>) is ambiguous for the type Futures.

If I put a cast, eclipse will not complain:

public ListenableFuture<ProtoBufExchange> myMethod(
   //some code here
   return Futures.transform(future,(AsyncFunction<Request.Builder, ProtoBufExchange>) (Request.Builder reqBuilder) -> {

    //some code here

    return Futures.immediateFuture(exchange);
  }

I know the guava 15.0 Future.transform() is overloaded, with the two forms below (on newer guava versions the async method has a different name):

transform(ListenableFuture<I> input, Function<? super I,? extends O> function)

or

transform(ListenableFuture<I> input, AsyncFunction<? super I,? extends O> function)

But somehow the jdk compiler resolves this ambiguation. Maybe because in the code above, if we were implementing a Function, rather than an AsyncFunction, the return type of Futures.transform would not match with the method return type.

Is it a bug on eclipse? Am I missing something here?

More details on my environment:

jdk: 1.8.0_102
eclipse: 4.6.2
guava: 15.0

回答1:

There is a distinction between “explicitly typed lambda expressions” and “implicitly typed lambda expressions”.

An implicitly typed lambda expression of the form name -> expressiorOrBlock or (name[,name]*) -> expressionOrBlock requires its context, i.e. a resolved method, to determine its type, hence isn’t used to disambiguate overloaded methods. It wouldn’t be impossible to do this, but due to the resulting complexity, it was explicitly ruled out by the specification.

Explicitly typed lambda expressions of the form (Type name[, Type name]*) -> expressionOrBlock have everything needed to determine their functional signature, including their return type, which allows to use them to disambiguate overloaded methods.

To provide a simple example:

interface F<T,R> {
    R apply(T t);
}
interface AF<T,R> {
    Future<R> apply(T t);
}
static <T,R> Future<R> method(F<T,R> f) {
    return null;
}
static <T,R> Future<R> method(AF<T,R> f) {
    return null;
}
public static void main(String[] args) {
    // these two don't compile
    method(x -> "bla");
    method(x -> new FutureTask<String>(null));

    // these two do compile
    method((Object x) -> "bla");
    method((Object x) -> new FutureTask<String>(null));
}

The formal rules are specified in JLS, §15.12.2.5. Choosing the Most Specific Method, but it also contains an informal hint:

The informal intuition is that one method is more specific than another if any invocation handled by the first method could be passed on to the other one without a compile-time error.

I think, it’s easy to see that every call that can be handled by method(AF) could also be handled by method(F), while the opposite doesn’t apply, i.e. the method((Object x) -> "bla") invocation can only be handled by method(F), so it’s not ambiguous, whereas method((Object x) -> new FutureTask<String>(null)) could be handled by both, but method(AF) is more specific.

The relevant formal part is:

A functional interface type S is more specific than a functional interface type T for an expression e if T is not a subtype of S and one of the following is true (where U₁ ... Uk and R₁ are the parameter types and return type of the function type of the capture of S, and V₁ ... Vk and R₂ are the parameter types and return type of the function type of T):

  • If e is an explicitly typed lambda expression (§15.27.1), then one of the following is true:
    • R₂ is void.
    • R₁ <: R₂.

So in this specific case involving an explicitly typed lambda expression, the return type of the functional type is already sufficient to disambiguate.

But note that compiling the example code also produces the warning:

warning: [overloads] <T#1,R#1>method(F<T#1,R#1>) … is potentially ambiguous with <T#2,R#2>method(AF<T#2,R#2>)

Reminding that implicitly typed lambda expressions will always be ambiguous with such overloaded methods and also the method selection with explicitly type lambda expressions might not be intuitive.