RFR: 8319123: Implement JEP 461: Stream Gatherers (Preview) [v7]

Viktor Klang vklang at openjdk.org
Wed Nov 15 07:46:48 UTC 2023


On Tue, 14 Nov 2023 09:48:13 GMT, Rémi Forax <forax at openjdk.org> wrote:

>> Viktor Klang has updated the pull request incrementally with one additional commit since the last revision:
>> 
>>   Addressing last review feedback
>
> Hello,
> the relation between a stateless gatherer (default initializer) and the default combiner are not obvious to me.
> 
> A default initializer means stateless and a default combiner means sequential, so if i want the integrator of my stateless gatherer to be called in parallel, i need to not use the default combiner but at the same time the combiner I will pass as parameter will not be called because the gatherer is stateless ?
> 
> Having to create a combiner that will be not called seems weird. I'm sure i'm missing something ?

@forax 

> But I found the API surprising, the way to opt-in is to provide a combiner which does not work well with a stateless intermediary operation which by definition does not need a combiner.

"Does not need a combiner" is not strictly true, it's just that the (only) combiner which makes sense is `(null,null) -> null` (which is a completely valid combiner).

>Why not either using capabilities (like Spliterator and Collector) or subclasses like Greedy, or any other mechanisms instead of the concept of a default initializer/combiner ?

I tried multiple variations of that during the design process, including attaching the combiner to the state via `Initializer<T>` (a sealed type whose type members were `SequentialInitializer<T>` and `ParallelizableInitializer<T>` where the latter had the `combine`-method). This had other problems, such as (but not limited to): the latter not being a SAM-type anymore, which led to more boilerplate, interactions with composition which were more challenging to encode (Sequential andThen Parallelizable, Parallelizable andThen Sequential). And it also introduced a bit of slipperly slope to move the goalposts such that all the behaviors ended up on a single type, where we'd be back at the original formulation, so in the end the current encoding was selected.

>The idea of default initializer/combiner is that those are orthogonal, you can choose the default initializer and the default combiner separatly, but an initializer and a combiner are related, they work together so the idea of default initializer/combiner is leaky. A contrario, there is no such isseu with the default finisher because this one is independant of the others methods of a Gatherer.

I'd say that all of them are related. In the case of forEach and forEachOrdered it is the Integrator which wants a certain execution order, so you could argue that it is influencing the choice of the combiner. But that is sort of the point—they (initializer, integrator, combiner, finisher) are all related in important ways, that's why they have a shared home—Gatherer. The reason for them being lifted representations (methods that return functions) is to be able to more precisely be able to choose, and compose, them—since they should only depend on the mutable state which they are passed.

While I think it would have been interesting to a select number of people, writing up the pros-cons for each failed experiment would have taken much more time to analyze and put into words, the resulting solution proposed in this PR was the best of many.

My ambition with this Preview of JEP-461 is to:
1) Verify that the documentation is helpful
2) Verify that the chosen Gatherer encoding is correct
3) Verify that the API surface doesn't have any major foot-guns
4) Verify that the performance of the reference implementation is in the right neighbourhood
5) Verify that the chosen, initial, built-in Gatherers are helpful to developers

-------------

PR Comment: https://git.openjdk.org/jdk/pull/16420#issuecomment-1811953981


More information about the core-libs-dev mailing list