<T> Stream.fromForEach(Consumer<Consumer<T>>)

Steven Schlansker stevenschlansker at gmail.com
Mon May 23 17:36:23 UTC 2022



> On May 23, 2022, at 5:53 AM, Brian Goetz <brian.goetz at oracle.com> wrote:
> 
> This is a nice use of `mapMulti` to create a stream whose contents are dynamically generated.  It is one step short of generators (which we hope Loom will eventually give us), in that it is restricted to a generator method that generates all of the elements in one invocation.  This is still quite expressive.  (In hindsight, the name `generate` was wasted on the current `Stream::generate`, which is not all that useful and taking up a good name.)
> 
> I agree that the naming needs work, and people may well need help navigating the type `Consumer<Consumer<T>>`. Push is evocative, but there's more than just push; it's more like you have to kick something into pushing.  (Perhaps it is like the mythical Pushmi-Pullyu beast.)
> 
> Ideally, what is captured in the naming is that the outer `consumer` method is called exactly once, and that it should call the inner consumer to push elements.  (Of course that's a lot to capture.)  Something that evokes "build by push", perhaps.  Stream::fromPush is about the best I've got right now.
> 
> Are there any useful Consumer<Consumer<T>> in the wild, that are actually typed like that?  I doubt it (there are many things convertible to it, though.)  Which suggests it might be fruitful to define a FI:

We have such a use case "in the wild" but for a slightly different purpose - instead of adapting to a Stream, it adapts to a JAX-RS StreamingOutput.

StreamingOutput ofItems(Consumer<? super Consumer<? super Object>> itemGenerator) { ... }

Wrapping the logic in this PushSource-like abstraction allows us to wrap the generated JSON stream with a header, footer, and to handle each item.
And, it seems that we might even have gotten the wildcard super vs extends wrong! If the standard library provided such a PushSource FI,
we would have used that, and avoided doing it possibly incorrectly ourselves.


>     interface PushSource<T> {
>         void accept(Consumer<? extends Consumer<T>> pusher);
>     }
> 
>     static<T> Stream<T> fromPush(PushSource<T> source) { ... }
> 
> and Iterable::forEachRemaining and Optional::ifPresent will convert to it.
> 
> 
> On 5/21/2022 6:54 PM, Remi Forax wrote:
>> Hi all,
>> a stream is kind of push iterator so it can be created from any object that has a method like forEach(Consumer),
>> but sadly there is no static method to create a Stream from a Consumer of Consumer so people usually miss that creating a Stream from events pushed to a consumer is easy.
>> 
>> By example, let say i've a very simple parser like this, it calls the listener/callback for each tokens
>> 
>>   record Parser(String text) {
>>     void parse(Consumer<? super String> listener) {
>>       for(var token: text.split(" ")) {
>>          listener.accept(token);
>>       }
>>     }
>>   }
>> 
>> Using the method Stream.fromForEach, we can create a Stream from the sequence of tokens that are pushed through the listener
>>   var parser = new Parser("1 2");
>>   var stream = Stream.fromForEach(parser::parse);
>> 
>> It can also works with an iterable, an optional or even a collection
>>   Iterable<String> iterable = ...
>>   var stream = Stream.fromForEach(iterable::forEachRemaning);
>> 
>>   Optional<String> optional = ...
>>   var stream = Stream.fromForEach(optional::ifPresent);
>> 
>>   List<String> list = ...
>>   var stream = Stream.fromForEach(list::forEach);
>> 
>> I known the last two examples already have their own method stream(), it's just to explain how Stream.fromForEach is supposed to work.
>> 
>> In term of implementation, Stream.fromForEach() is equivalent to creating a stream using a mapMulti(), so it can be implemented like this
>> 
>>   static <T> Stream<T> fromForEach(Consumer<? super Consumer<T>> forEach) {
>>     return Stream.of((T) null).mapMulti((__, consumer) -> forEach.accept(consumer));
>>   }
>> 
>> but i think that Stream.fromForEach(iterable::forEachRemaning) is more readable than Stream.of(iterable).mapMult(Iterable::forEachRemaining).
>> 
>> The name fromForEach is not great and i'm sure someone will come with a better one.
>> 
>> regards,
>> Rémi



More information about the core-libs-dev mailing list