alternative implementations of Iterable and MapStream

Peter Levart peter.levart at gmail.com
Wed May 16 14:46:38 PDT 2012


The first naive implementation, that I scraped, was based on a doubly-linked 
list of pipe nodes that was built as a result of composing lazy operations 
(.map().filter().sorted()...). There was the main driving loop at the head of 
the this same list that iterated over source elements and pushed them down the 
pipe. The drawbacks of this approach were:

1. the state that was maintained in the buffering nodes (.sorted()) prevented 
multiple concurrent (not necessarily multi threaded) iterations (the pipe was 
"occupied" by a single iteration).
2. half-built pipe could not be used as a "prefix" for building multiple 
different continuations (each parent node could only have a single child)
3. the approach to obtain "first" result was to issue a "long-break" out of 
driving loop using a sub-type of RuntimeException.

Now my current approach is on one hand very similar to the approach with 
Iterables. The building of a chain of "Producables" is similar to building a 
chain of Iterables - at each step the child Producable is created that has a 
reference to the parent - single parent can have multiple children - drawback 
#2 eliminated.

The Producable is a factory for Producers (like Iterable is for Iterators).

The main Producable API is:

public abstract class Producable<T> implements Iterable<T>
{
   public Producer producer(Transformer<? super T> downstream);
   public Producer producer(Consumer<? super T> consumer);
}

If you have a chain of:

producable1 <- producable2 <- ... <- producableN

then a call to producableN.producer(consumer) creates a reverse chain of:

producer1 -> transformer2 -> ... -> transformerN -> consumer

and returns head producer1. So each call to producer() starts an independent 
producing session (eliminated drawback #1).

A Transformer is a Producer and a Consumer:

public interface Producer
{
   /**
    * Try to produce one piece of output. One call to this method produces at most one piece of output,
    * but need not produce any output.
    *
    * @return true if this producer has more output to produce or false if it has exhausted all output
    */
   boolean produce();
}

public interface Consumer<T>
{
   /**
    * Consumes one object.
    *
    * @param t the object to consume
    */
   void consume(T t);
}

public interface Transformer<T> extends Consumer<T>, Producer
{
   /**
    * @return true if this transformer is in a state that allows consuming input so that the
    *         next call to {@link #consume} will not throw {@link IllegalStateException}.
    */
   boolean canConsume();


   /**
    * @param t the object to consume
    * @throws IllegalStateException if this transformer is in a state that doesn't allow consuming
    */
   @Override
   void consume(T t) throws IllegalStateException;
}


This way it's easy to set-up a context where you have a head producer and a 
tail consumer accessible inside a particular custom producing loop so you can 
control how much you produce and when to stop producing (until first result or 
until second or until first that matches, etc.) - eliminated drawback #3

The Producer/Consumer/Transformer API allows piecewise producing in spite of 
Transformers that compress (.filter()), expand (.flatMap()) or buffer the entire 
stream (.sorted()).

The only remaining drawback is that this approach is different than the known 
approach using Iterable API, so it is maybe harder to understand. But this 
could be internal API that is not exposed to the client and it has this single 
advantage or ability to "push" down tuples of values without boxing (a method 
can have multiple parameters but only a single result).

Regards, Peter

On Tuesday, May 15, 2012 02:59:43 PM Brian Goetz wrote:
> We toyed with this as well and had some concerns about the approach.
> Since you claim this approach "has no remaining drawbacks", can you
> outline the list of drawbacks you started with?
> 
> On 5/11/2012 1:00 PM, Peter Levart wrote:
> > Hello lambdanians,
> > 
> > I toyed a little more with the idea of using "push" processing instead of
> > traditional "pull" that is used in current implementations of stream
> > transformation methods of Iterable and MapStream.
> > 
> > It turned out that it can be done and that this approach has a benefit of
> > avoiding box/unbox-ing of pairs of values into/out of BiValue while
> > actualy
> > having no drawbacks (I managed to elliminate all drawbacks of my 1st
> > implementation from a week ago).
> > 
> > If you are interested, have a look at:
> > 
> > https://github.com/plevart/PushPipes
> > 
> > Basic classes are:
> > 
> > Producable - which implements Iterable (only as a facade)
> > MapProducable - which implements MapStream (only as a facade)
> > 
> > both classes override all defenders from Iterable and MapStream with their
> > own "push" implementations.
> > 
> > Currently Producable/MapProducable are abstract classes, but they could
> > easily be transformed into interfaces with defender methods.
> > 
> > 
> > Regards,
> > Peter


More information about the lambda-dev mailing list