IntStreams and the case of the missing reduce

Brent Walker brenthwalker at gmail.com
Tue Jan 7 07:28:01 PST 2014


Thank you Paul.  This works in this case because the integer is not used in
the computation -- in the usual case of course it is used.  I gave the
example of the function I was working at the moment -- not the best example
but that was the moment the cammel's back broke...

But you are exactly right, most uses for reduce() I found is as a
sequential fold left -- if you added a version without the combiner then
reduce() == foldLeft() as I described in the original message.  I do use
reduce() with parallel streams but not as often.  I suspect this will be
experience of most people... I could be wrong.

Pluggable operations sounds great.  When I discovered this issue I thought
I could get arround it my "inhereting" from IntStreams and implementing the
missing reduce myself but I failed -- the details as to why escape me... I
think IntStream is an interface, and not a class.  So being able to do
implement such functionality into streams would be great.

Brent

On Tue, Jan 7, 2014 at 5:08 PM, Paul Sandoz <paul.sandoz at oracle.com> wrote:

> Hi Brent,
>
> Here is an alternative way:
>
>   // assuming n -1 tails
>   // source is sequential but some parallelism can be extracted
>   return Stream.iterate(this, q -> q.tail()).skip(n).findFirst().get();
>
> You want to use reduce as a form of fold-left functionality, which is
> inherently sequential and we did not add it for reasons you state. Perhaps
> that is a little extreme? i suppose i might prefer to see a foldLeft op
> than contortions of reduce, but there are potentially other solutions, as
> shown above.
>
> A general solution is in the next revision of the API to add support for
> pluggable operations.
>
> Paul.
>
> On Jan 6, 2014, at 9:05 PM, Brent Walker <brenthwalker at gmail.com> wrote:
>
> > 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