IntStreams and the case of the missing reduce
Brent Walker
brenthwalker at gmail.com
Mon Jan 6 12:05:39 PST 2014
I have been programming for a few months now in Java 8 and with streams in
particular. I am implementing a persistent data structures library for
Java. I have a couple of small criticisms to share with you guys. I know
it's too late for Java 8 -- perhaps you can do something about them in a
future release.
I found (to my surprise) that one of the most useful classes that came with
the lambda project is IntStream. Maybe it's my programming style, but I
find myself using that class all the time. I rarely write explicit loops
anymore. Unfortunately the most useful function (IMHO) in the stream
class, the general form of the reduce function somehow did not make it to
IntStream. I am talking about this function:
<U> U reduce(U identity, BiFunction<U,? super T,U> accumulator,
BinaryOperator<U> combiner);
which in the IntStream case (had it existed) would be taking a
BiIntFunction<U, U> accumulator.
So for a function like the following:
@Override
public HMRealTimeQueue<V> drop(final int n) {
HMRealTimeQueue<V> q = this;
for (int i = 0; i != n; ++i) {
q = q.tail();
}
return q;
}
I cannot replace it with the one liner:
@Override
public HMRealTimeQueue<V> drop(final int n) {
return IntStream.range(0, cnt).reduce(this, (q, i) -> q.tail(),
Functionals::functionShouldNotBeCalled);
}
because this version of the reduce is not there even though it is there on
streams. I have to do:
return IntStream.range(0, cnt).*boxed()*.reduce(this, (q, i) -> q.tail(),
Functionals::functionShouldNotBeCalled);
but the extra boxing is just silly. I honestly can't think of a good
reason why you chose to omit it. Perhaps method/type creep? But we are
talking about one method here and no extra types -- the reduce version for
primitive to primitive I could live without.
Another comment is the following: For the stream case the third argument
to the reduce function is the combiner which is only used for parallel
streams when pieces of the computation are combined. I applaud your
efforts in designing an api that is agnostic to the kind of stream coming
in and where parallelism can be turned on so easily with such minimal code
changes. But it is maybe the case that in most uses of the reduce function
(certainly that has been the case with me so far), the stream is sequential
and could not be parallel (consider for instance the example above -- we
cannot take the tail of a queue in parallel). In such cases I found I had
to keep specifying something like:
(z1, z2) -> { throw new AssertionFailure("This function should never be
called. The stream was sequential"; }
as the combiner which make the code, well ugly. Shortly after I started to
use streams, I wrote this function in my utilities library.
public static <A> A functionShouldNotBeCalled(final A a, final A b) {
throw new AssertionError("Should never get here. The stream was
sequential.");
}
and it is now in use in the majority of calls to reduce. You could perhaps
have a version of reduce without the combiner so that we can avoid this
code-smell in most uses of reduce -- if the stream happened to be parallel,
then make do without the combiner -- do things sequentially for example.
Apologies for the long email -- and thank you for your efforts in getting
Java 8 finished -- Java is now a much more pleasant language to program in
after the addition of lambdas and streams.
Brent
More information about the lambda-dev
mailing list