Stream.flatMap reference ambiguity

Dan Smith daniel.smith at
Tue Feb 26 13:05:36 PST 2013

On Feb 26, 2013, at 1:03 PM, Zhong Yu <zhong.j.yu at> wrote:

> On Tue, Feb 26, 2013 at 12:59 PM, Dan Smith <daniel.smith at> wrote:
>> On Feb 26, 2013, at 11:09 AM, Maurizio Cimadamore <maurizio.cimadamore at> wrote:
>>> Now - in this example there are three potentially applicable methods
>>> (after provisional applicability):
>>> 1) <R> Stream<R> flatMap(FlatMapper<? super T, R> mapper) // (T, Consumer<R>)void
>>> 2)IntStream flatMap(FlatMapper.ToInt<? super T> mapper); // (int, IntConsumer)void
>>> 3)LongStream flatMap(FlatMapper.ToLong<? super T> mapper); // (long, LongConsumer)void
>>> 4)DoubleStream flatMap(FlatMapper.ToDouble<? super T> mapper); // (double, DoubleConsumer)void
>>> the first parameter type inferred from the descriptor is interesting: it
>>> can be either T (the type of the Stream, String in this case), int, long
>>> or double. But if you look at the code, it contains this snippet:
>>> x.split(",")
>>> This will only compile when x has type String - which means only method
>>> (1) is applicable, no ambiguity should be issued.
>> Correct.  The invocation of "split" should disambiguate (as well as the invocation of 'accept' with a String).
>> I think the core problem is the API (in general, it's best to avoid overloading with functional interfaces that have different parameter types), but this is an interesting example in any case.
>> David, this is actually something we've had a hard time predicting user expectations on.  You get the more powerful inference, but at the cost of more fragile code.  As a programmer, are you okay with that?  (Other interested parties are free to chime in, too.)
>> Here's the original example, which should compile:
>> List<String> ss = new ArrayList<>();
>>, sink) -> {
>>  for (String s : x.split(",")) {
>>    sink.accept(s);
>>  }
>> });
>> If I refactor (maybe for debugging) to something like this, it won't compile anymore, due to ambiguity:
>> List<String> ss = new ArrayList<>();
>>, sink) -> sink.accept(x));
> wait... since x must be String (or supertype of), sink cannot be
> IntConsumer, therefore flatMap(FlatMapper.ToInt) cannot apply,
> correct?

You're right.  I was going off of Maurizio's description of the method signatures, but looking at the sources, 'x' is always a String.

The descriptors in question are:
(String, Consumer<R>)->void (R is inferred)
(String, IntConsumer)->void
(String, LongConsumer)->void
(String, DoubleConsumer)->void

(Which makes a lot more sense.  I was having a hard time picturing what these methods are supposed to do...)

So, "x.split" does _not_ disambiguate, but "sink.accept(s)" does.  And the way to lose that disambiguation would be to avoid calling 'accept', again maybe as part of a debugging exercise:

List<String> ss = new ArrayList<>();, sink) -> {
 for (String s : x.split(",")) {

Repeating: this sort of brittleness is something we think is probably acceptable in the language, but I'm happy to hear "real-world" feedback about whether the trade-offs are worth it.  (And, in this specific instance, this is probably a part of the API that should be tweaked.)


More information about the lambda-dev mailing list