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.
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));