daniel.smith at oracle.com
Tue Feb 26 13:47:10 PST 2013
A thread on lambda-dev highlighted a problem with the overloading of flatMap:
<R> Stream<R> flatMap(FlatMapper<? super T, R> mapper)
IntStream flatMap(FlatMapper.ToInt<? super T> mapper)
LongStream flatMap(FlatMapper.ToLong<? super T> mapper)
DoubleStream flatMap(FlatMapper.ToDouble<? super T> mapper)
These functional interfaces have corresponding descriptors:
(T, Consumer<R>)->void (R is inferred)
This violates the general rule that overloading with functional interfaces of the same shape shouldn't use different parameter types. Various ambiguities result:
- "(x, sink) -> sink.accept(10)" is compatible with all the primitive consumers, and we have no way to disambiguate (the new most-specific rules handle this sort of thing with return types, but are not designed to decide which type is "better" for an arbitrary block of code).
- "(x, sink) -> sink.accept(22.0)" is compatible with "(T, DoubleConsumer)->void", and leads "(T, Consumer<R>)->void" to be provisionally applicable -- we don't check the body at all in that case, until we've had a chance to look at the target type of the 'flatMap' invocation and figure out what R is supposed to be. So, again, an ambiguity will occur.
It would probably be best to give the primitive versions distinct names.
Note also that an invocation like the following will always produce a Stream<Object>:
stream.flatMap((x, sink) -> sink.accept("x")).filter(...)....
Inference is forced to resolve R without knowing anything about it, and so it must go with the default "Object".
The only way to get useful information about R is to derive bounds from the body of the lambda, and that's simply not something we can do in general.
I don't know what to recommend in this case, except perhaps that this method inherently depends on some explicit typing (e.g., "(String x, Consumer<String> sink) -> ...").
More information about the lambda-libs-spec-observers