Hi Tyler,
Thank you for the kind words -- they are much appreciated. And you have a very
good question indeed!
My thinking behind making fold a Gatherer is that I think that it is strictly
more powerful than "only" having it as a collector (It wouldn't be able to be a
Collector since you'd need a combiner for it, but also being able to compose it
with other operations, choosing the output type at a later stage in the
process, and so forth).
Also, if you think about it -- single-element Streams are just as valid as
empty Streams, N-sized Streams, or even unbounded Streams, and conceptually,
there's no difference between the following two Streams:
var a = Stream.of("1234")
var b = Stream.of(1,2,3,4).gather(fold(() -> "", (str, next) -> str + next))
So that was my thinking—allow developers to stay within Stream processing for
as long as they want, and choose the terminal operation when they need to break
out from the Stream.
Cheers,
√
Viktor Klang
Software Architect, Java Platform Group
Oracle
________________________________
From: Tyler Kindy <[email protected]>
Sent: Monday, 30 October 2023 12:14
To: Viktor Klang <[email protected]>
Cc: [email protected] <[email protected]>
Subject: [External] : Re: Update on JEP-461: Stream Gatherers (Preview)
Thanks for the JEP and your talk, Viktor! I think `Stream::gather` will be
super useful in my day-to-day as a Java developer.
I’m curious why `fold` is being implemented with gatherers. I recognize
`Gatherer` is designed to support intermediate operations, but `fold` feels
inherently like a terminal operation to me since it, like `reduce` or
`collect`, consumes all the elements in the stream and produces a single result.
Is there a technical limitation to making `fold` a terminal operation? For
example, does `Collector` inherently presume parallelization in a way that
`Gatherer` does not?
Or is the idea mainly to demonstrate the power of gatherers, and we could also
make `fold` a terminal operation with the current `Stream` API?
Thank you!
Tyler Kindy
On Oct 27, 2023, at 9:50 AM, Viktor Klang <[email protected]> wrote:
Greetings,
As you may have already seen, Stream Gatherers is now a Preview JEP with
Candidate status<https://openjdk.org/jeps/461>
Work-in-progress (interfaces, implementation, tests, benches, and
documentation) for JEP-461 is currently available
here<https://urldefense.com/v3/__https://github.com/viktorklang-ora/jdk/tree/gatherer__;!!ACWV5N9M2RV99hQ!Lz2sC02xW35XpuwoaqBvD_iR80Xrzkbj-60oOKuQklUOm8e69-O3WC9N93leBFbMkFmULJmsat9k1dmt$>.
While the design<https://cr.openjdk.org/~vklang/Gatherers.html> has held up
well, there are some important improvements made since the original design
document was written.
Notable changes (without any particular order):
* Stream::gather() now has a default implementation.
* Gatherer::supplier() was renamed to Gatherer::initializer() to better
reflect intent.
* Gatherer.Sink<R> was renamed to Gatherer.Downstream<R> to better signal
what it represents.
* Gatherer::collect(Collector) and its companion type
Gatherer.ThenCollector was dropped due to compatibility concerns with existing
code which operates on Collector.
*
Gatherer.Characteristics have been eliminated and superseded by having default
values that are used as sentinels.
(discussed further down the list)
This is important because with the Characteristics-model keeping alignment
between Characteristics and actual implementation proved brittle, and under
composition of Gatherers computing the union was inefficient and mostly lead to
an empty set in the end.
* Gatherer.defaultInitializer(), Gatherer.defaultCombiner(), and
Gatherer.defaultFinisher() were added as static methods—these are the sentinels
used to elide calling the initializer, to elide calling the combiner (avoid
parallelization), and to elide calling the finisher, respectively.
* Gatherer::initializer(), Gatherer::combiner(), and Gatherer::finisher()
default implementations return the respective sentinels.
* A subtype of Integrator named Greedy was added, together with a factory
method to guide lambda-to-nominal-type conversion. This allows creators of
Gatherers to signal that an Integrator will never initiate a short-circuit (but
may relay one from downstream), and that is available during evaluation to
determine if the operation can short-circuit or not.
* Factories for creating anonymous Gatherers were expanded upon to include
Gatherer.of() and Gatherer.ofSequential() with different sets of parameters,
primarily to make it more ergonomical and easier to read and write the code
using those factories.
* A curated set of initial built-in Gatherers is located in
java.util.stream.Gatherers
I recently presented Gatherers at
Devoxx<https://urldefense.com/v3/__https://www.youtube.com/watch?v=8fMFa6OqlY8__;!!ACWV5N9M2RV99hQ!Lz2sC02xW35XpuwoaqBvD_iR80Xrzkbj-60oOKuQklUOm8e69-O3WC9N93leBFbMkFmULJmsam2MP-_S$>,
which I'd recommend watching for an introduction to the feature.
<Outlook-cztoycvq.jpg><https://urldefense.com/v3/__https://www.youtube.com/watch?v=8fMFa6OqlY8__;!!ACWV5N9M2RV99hQ!Lz2sC02xW35XpuwoaqBvD_iR80Xrzkbj-60oOKuQklUOm8e69-O3WC9N93leBFbMkFmULJmsam2MP-_S$>
Teaching old Streams new tricks By Viktor
Klang<https://urldefense.com/v3/__https://www.youtube.com/watch?v=8fMFa6OqlY8__;!!ACWV5N9M2RV99hQ!Lz2sC02xW35XpuwoaqBvD_iR80Xrzkbj-60oOKuQklUOm8e69-O3WC9N93leBFbMkFmULJmsam2MP-_S$>
Have you ever wanted to perform an operation on a java.util.stream.Stream only
to find that the existing set of operations didn't provide what you
needed—forcing you to break out early from the Stream and perform the logic
outside of it? As a matter of fact, java.util.stream was the first JDK API
designed with lambdas in mind and was ...
www.youtube.com<https://urldefense.com/v3/__http://www.youtube.com/__;!!ACWV5N9M2RV99hQ!Lz2sC02xW35XpuwoaqBvD_iR80Xrzkbj-60oOKuQklUOm8e69-O3WC9N93leBFbMkFmULJmsalMQI96j$>
Cheers,
√
Viktor Klang
Software Architect, Java Platform Group
Oracle