Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Inferring generic types of nested static generic functions

Is the Java compiler able to infer the type of a generic static function from its context as the argument to another generic static function?

For example, I have a simple Pair class:

public class Pair<F, S> {

    private final F mFirst;

    private final S mSecond;

    public Pair(F first, S second) {
        mFirst  = checkNotNull(first);
        mSecond = checkNotNull(second);
    }

    public static <F, S, F1 extends F, S1 extends S> Pair<F, S> of(F1 first, S1 second) {
        return new Pair<F, S>(first, second);
    }

    public F first() {
        return mFirst;
    }

    public S second() {
        return mSecond;
    }

    // ...
}

And I have the following generic static function:

public static <F, P extends Pair<F, ?>> Function<P, F> deferredFirst() {
    return (Function<P, F>)DEFERRED_FIRST;
}

private static final Function<Pair<Object, ?>, Object> DEFERRED_FIRST = 
        new Function<Pair<Object,?>, Object>() {

    @Override
    public Object apply(Pair<Object, ?> input) {
        return input.first();
    }
};

Which I wish to use as follows (Collections2.transform is from Google Guava):

List<Pair<Integer, Double>> values = ...
Collection<Integer> firsts = Collections2.transform(values, 
        Pair.deferredFirst());

To which the compiler complains:

The method transform(Collection<F>, Function<? super F,T>) in the type 
Collections2 is not applicable for the arguments 
(List<Pair<Integer,Double>>, Function<Pair<Object,?>,Object>)

So it seems that the compiler fails to propagate the types inferred for transform() to deferredFirst() as it thinks they are Objects.

Forcing the compiler to understand the types in either of these ways works:

Function<Pair<Integer, ?>, Integer> func = Pair.deferredFirst();
Collection<Integer> firsts = Collections2.transform(values, func);


Collection<Integer> firsts = Collections2.transform(values, 
        Pair.<Integer, Pair<Integer, ?>>deferredFirst());

Is it possible to change either function's signature to allow the compiler to infer/propagate the types?

Edit: For Bohemian, here's a possible method the above example could be used in:

public static int sumSomeInts(List<Pair<Integer, Double>> values) {
    Collection<Integer> ints = Collections2.transform(values, 
            Pair.deferredFirst());
    int sum = 0;
    for(int i : ints)
        sum += i;
    return sum;
}
like image 966
Hal Avatar asked Jun 07 '11 11:06

Hal


2 Answers

Type inference is nasty and complicated. They have to stop somewhere. Consider

static <T> T foo();

String s = foo();

print( foo() )

In the assignment context, the intention of the programmer is clear, T should be String

In the next line, not so much.

The print method is not a very fair example, it is heavily overloaded. Suppose print isn't overloaded, its parameter type is fixed, so T can be clearly inferred. Shouldn't the compiler be smart enough to figure it out?

That sounds reasonable, until one ventures to read the related spec text, 15.12 Method Invocation Expressions Good luck changing anything in that mess!

It is so complicated, not even the compiler authors understand it. There are tons of bugs in javac and other compilers that originated from this section of the spec.

like image 141
irreputable Avatar answered Oct 05 '22 23:10

irreputable


Try this generics kung fu:

public static int sumSomeInts(List<Pair<Integer, Double>> values) {
    Collection<Integer> ints = Collections2.transform(values, 
        Pair.<Integer, Double>deferredFirst());
    int sum = 0;
    for(int i : ints)
        sum += i;
    return sum;
}

You can type the method call and pass the generics through to the next call.

I'm not sure about the exact generic parameters to use here, because you haven't included enough code. If you paste in the whole method where the problem is I will edit this answer to make it compile. EDITED: with new info from question

Please let me know if it compiles. If it isn't that solution, it will be close. The key is to type the static method using Class.<Type>staticMethod() syntax.

like image 29
Bohemian Avatar answered Oct 05 '22 22:10

Bohemian