This is related to my answer to "stream reduction incompatible types". I don't know why what I suggested works, and Holger rightly pressed me on this. But even he doesn't seem to have a clear explanation for why it works. So, let's ask it as its own question:
The following code does not compile in javac
(for the links to ideone below, this is sun-jdk-1.8.0_51
, per http://ideone.com/faq):
public <T> Object with(Stream<Predicate<? super T>> predicates) {
return predicates.reduce(Predicate::or);
}
And rightly so: or-ing together two predicates from this stream is like writing:
Predicate<? super T> a = null;
Predicate<? super T> b = null;
a.or(b); // Compiler error!
However, it does compile in intellij, although with a raw type warning on the Predicate::or
method reference. Apparently, it would also compile in eclipse (according to the original question).
But this code does:
public <T> Object with(Stream<Predicate<? super T>> predicates) {
return predicates.map(a -> a).reduce(Predicate::or);
// ^----------^ Added
}
Despite the fact I thought to try this, it's not exactly clear to me why this would work. My hand-wavy explanation is that .map(a -> a)
acts like a "cast", and gives the type inference algorithm a bit more flexibility to pick a type which allows the reduce
to be applied. But I'm not sure exactly what that type is.
Note that this isn't equivalent to using .map(Function.identity())
, because that is constrained to return the input type. ideone demo
Can anybody explain why this works with reference to the language spec, or if, as suggested by Holger, it is a compiler bug?
A bit more detail:
The return type of the method can be made a bit more specific; I omitted it above so that the nasty generics on the return type wouldn't get in the way:
public <T> Optional<? extends Predicate<? super T>> with(
Stream<Predicate<? super T>> predicates) {
return predicates.map(a -> a).reduce(Predicate::or);
}
This is the output of compiling with -XDverboseResolution=all
. Not entirely sure if this is the most relevant output I can post to debug the type inference; please advise if there is something better:
Interesting.java:5: Note: resolving method <init> in type Object to candidate 0
class Interesting {
^
phase: BASIC
with actuals: no arguments
with type-args: no arguments
candidates:
#0 applicable method found: Object()
Interesting.java:7: Note: resolving method map in type Stream to candidate 0
return predicates.map(a -> a).reduce(Predicate::or);
^
phase: BASIC
with actuals: <none>
with type-args: no arguments
candidates:
#0 applicable method found: <R>map(Function<? super T#1,? extends R>)
(partially instantiated to: (Function<? super Predicate<? super T#2>,? extends Object>)Stream<Object>)
where R,T#1,T#2 are type-variables:
R extends Object declared in method <R>map(Function<? super T#1,? extends R>)
T#1 extends Object declared in interface Stream
T#2 extends Object declared in method <T#2>with(Stream<Predicate<? super T#2>>)
Interesting.java:7: Note: Deferred instantiation of method <R>map(Function<? super T#1,? extends R>)
return predicates.map(a -> a).reduce(Predicate::or);
^
instantiated signature: (Function<? super Predicate<? super T#2>,? extends Predicate<CAP#1>>)Stream<Predicate<CAP#1>>
target-type: <none>
where R,T#1,T#2 are type-variables:
R extends Object declared in method <R>map(Function<? super T#1,? extends R>)
T#1 extends Object declared in interface Stream
T#2 extends Object declared in method <T#2>with(Stream<Predicate<? super T#2>>)
where CAP#1 is a fresh type-variable:
CAP#1 extends Object super: T#2 from capture of ? super T#2
Interesting.java:7: Note: resolving method reduce in type Stream to candidate 1
return predicates.map(a -> a).reduce(Predicate::or);
^
phase: BASIC
with actuals: <none>
with type-args: no arguments
candidates:
#0 not applicable method found: <U>reduce(U,BiFunction<U,? super T,U>,BinaryOperator<U>)
(cannot infer type-variable(s) U
(actual and formal argument lists differ in length))
#1 applicable method found: reduce(BinaryOperator<T>)
#2 not applicable method found: reduce(T,BinaryOperator<T>)
(actual and formal argument lists differ in length)
where U,T are type-variables:
U extends Object declared in method <U>reduce(U,BiFunction<U,? super T,U>,BinaryOperator<U>)
T extends Object declared in interface Stream
Interesting.java:7: Note: resolving method metafactory in type LambdaMetafactory to candidate 0
return predicates.map(a -> a).reduce(Predicate::or);
^
phase: BASIC
with actuals: Lookup,String,MethodType,MethodType,MethodHandle,MethodType
with type-args: no arguments
candidates:
#0 applicable method found: metafactory(Lookup,String,MethodType,MethodType,MethodHandle,MethodType)
Interesting.java:7: Note: resolving method metafactory in type LambdaMetafactory to candidate 0
return predicates.map(a -> a).reduce(Predicate::or);
^
phase: BASIC
with actuals: Lookup,String,MethodType,MethodType,MethodHandle,MethodType
with type-args: no arguments
candidates:
#0 applicable method found: metafactory(Lookup,String,MethodType,MethodType,MethodHandle,MethodType)
map(Function.identity())
? Have tried that one? Did it work? – Circumfluentmap
is not the same as the plaina.or(b)
, since those type parameters are actually different captures, but the entries in the stream do have the same type (the same capture), you just can't really express that in plain code. – Gudgeonincompatible types: Predicate<CAP#1> cannot be converted to Predicate<? super CAP#2>
. Method reference version;a.or(b)
version – YukiFunction<Predicate, Predicate<T>> predicatePredicateFunction = a -> a;
. Perhaps this is what's being inferred? – Squeegeemap(t -> t)
causes the introduction of a new type variableCAP#1
whilemap(Function.identity())
does not, it will use? super T#2
instead, which looks inconsistent to me, as both are poly expressions. But both should end up in identical constraints anyway. The most interesting aspect is that the verbose output doesn’t show any resolution forPredicate::or
, it looks like the compiler just didn’t care. – Innmap
isStream<Predicate<CAP#1>>
- am I right in thinking that this is logically the same asStream<Predicate<Q>>
, for some type variableQ
? If it is, that's why it's possible then to applyreduce(Predicate::or)
. But the dropping of the lower bound feels... odd. – Yuki? super T#2
has been captured inCAP#1
. I could consider that a reasonable behavior ifjavac
wasn’t denying the same for every other generic construct. It’s just inconsistent not to allow? super Type
to be captured by an explicitly declared type variable, e.g. when invoking a method, but allow it for the compiler generated type variable. – Innjavac
, the assignmentPredicate<? super T> a = null; Predicate<? super T> b = a;
(similarly withextends
) compiles, but the invocationa.or(b);
does not. Intuitively, If the first compiles the second should as well. And in similar cases, it does compile: ideone.com/wVlROT. The longer I look at this, the more I think this is an type system limitation. – GudgeonForkJoinTask
(it's not a parallel stream), but even if there were, that's just an implementation detail which has nothing to do with the type inference. – Yukireduce()
accepts aBinaryOperator
(which extendsBiFunction<T,T,T>
), i.e. input and output types must be equal. HoweverPredicate.or()
has different input and output types. It may hint to a bug in cases where this acutally compiles. – Laurasia