How can Java stream reduce accumulator parameter b

2020-07-26 07:38发布

问题:

The question is about

java.util.stream.Stream.reduce(U identity,
             BiFunction<U, ? super T, U> accumulator,
             BinaryOperator<U> combiner) 

method.

From the API docs:

accumulator – an associative, non-interfering, stateless function for incorporating an additional element into a result

Now, the definition of associative is:

(a op b) op c = a op (b op c) for all a, b, c

As we can see, the above definition requires a BinaryOperator, not only a BiFunction, because a op b requires b of type T while b op c requires b of type U. If U and T are distinct, this is impossible.


My question

How can the definition of associative be formulated for a BiFunction that is not a BinaryOperator?


UPDATE

I have submitted a bug to Oracle about this issue, with the internal review ID : 9063337

I will update the question with Oracle's feedback.

回答1:

Mind the documentation:

Additionally, the combiner function must be compatible with the accumulator function; for all u and t, the following must hold:

combiner.apply(u, accumulator.apply(identity, t)) == accumulator.apply(u, t)

whereas == is not to be taken literally but rather means “equivalent result”

So in fact, we have a much stronger constraint on accumulator than associativity, as it must be compatible with the combiner, which is an associative function.

There’s also the API Note:

Many reductions using this form can be represented more simply by an explicit combination of map and reduce operations. The accumulator function acts as a fused mapper and accumulator, which can sometimes be more efficient than separate mapping and reduction, …

When the accumulator function is a fused mapping and reduction function, only the reduction part must be associative. When you want to test that part, just test whether the combiner function fulfills the associativity constraint and whether combiner and accumulator are compatible in the specified way. If both are proven, the associativity of the accumulator’s reduction part has been proven too.

While the API Note says “many reductions using this form”, actually all of them can be expressed as an explicit combination of map and reduce operations, when we ignore the efficiency, due to the specified constraints:

Function<T,U> mappingFunc = t -> accumulator.apply(identitiy, t);
U result = streamOfT.map(mappingFunc).reduce(identitiy, combiner);

This result must be equivalent to the result of, e.g. a typical sequential evaluation of streamOfT.reduce(identitiy, accumulator, combiner), which doesn’t use the combiner at all.

We can formulate narrow test cases for the associativity as

U u1 = accumulator.apply(accumulator.apply(identitiy, t1), t2);
U u2 = combiner.apply(accumulator.apply(identitiy, t1), accumulator.apply(identitiy, t2));

where u1 and u2 must be equivalent results, as well as

U u1 = accumulator.apply(accumulator.apply(accumulator.apply(identitiy, t1), t2), t3);
U u2 = combiner.apply(accumulator.apply(identitiy, t1),
           accumulator.apply(accumulator.apply(identitiy, t2), t3));