Reviewing Java 8 Stream
API design, I was surprised by the generic invariance on the Stream.reduce()
arguments:
<U> U reduce(U identity,
BiFunction<U,? super T,U> accumulator,
BinaryOperator<U> combiner)
A seemingly more versatile version of the same API might have applied covariance / contravariance on individual references to U
, such as:
<U> U reduce(U identity,
BiFunction<? super U, ? super T, ? extends U> accumulator,
BiFunction<? super U, ? super U, ? extends U> combiner)
This would allow for the following, which isn't possible, currently:
// Assuming we want to reuse these tools all over the place:
BiFunction<Number, Number, Double> numberAdder =
(t, u) -> t.doubleValue() + u.doubleValue();
// This currently doesn't work, but would work with the suggestion
Stream<Number> stream = Stream.of(1, 2L, 3.0);
double sum = stream.reduce(0.0, numberAdder, numberAdder);
Workaround, use method references to "coerce" the types into the target type:
double sum = stream.reduce(0.0, numberAdder::apply, numberAdder::apply);
C# doesn't have this particular problem, as Func(T1, T2, TResult)
is defined as follows, using declaration-site variance, which means that any API using Func
gets this behaviour for free:
public delegate TResult Func<in T1, in T2, out TResult>(
T1 arg1,
T2 arg2
)
What are the advantages (and possibly, the reasons for EG decisions) of the existing design over the suggested design?
Or, asked differently, what are the caveats of the suggested design that I might be overlooking (e.g. type inference difficulties, parallelisation constraints, or constraints specific to the reduction operation such as e.g. associativity, anticipation of a future Java's declaration-site variance on BiFunction<in T, in U, out R>
, ...)?
TraversableOnce.reduce[A1 >: A](op: (A1, A1) ⇒ A1): A1
– PamperA
is basically invariant" It isn't, as I've shown with the examples in my question. – PamperUnaryOperator<T>
instead ofFunction<? super T, ? extends T>
orBinaryOperator<T>
instead ofFunction<? super T, ? super T, ? extends T>
as parameter creates restrictions on the input without changing the functionality. Just because they exist. Or because “it has conceptually more weight”. Whatever reason, it’s the same for usingBiFunction<U,? super T,U>
instead ofBiFunction<? super U,? super T,? extends U>
for the accumulator function. – AntonsXYZOperator
types... Oh well... – Pamper