> From: "Viktor Klang" <viktor.kl...@oracle.com> > To: "Tyler Kindy" <m...@tylerkindy.com> > Cc: "core-libs-dev" <core-libs-dev@openjdk.org> > Sent: Monday, October 30, 2023 10:59:48 PM > Subject: Re: [External] : Re: Update on JEP-461: Stream Gatherers (Preview)
> That's also a good point, and I've heard from multiple sources that sometimes > you want to make sure that you only have a single element left at the end, and > otherwise throw an exception. Hello, > Fortunately it is possible to do something to the effect of: > // Proof of concept only > public static <T> Collector<T,?,T> singleton() { > class State { T value; boolean hasValue; } > return Collector.of( > State::new, > (state, e) -> { > if (state.hasValue) > throw new IllegalStateException("Stream has more than one element!"); > state.hasValue = true; > state.value = e; > }, > (left, right) -> { > if (left.hasValue && right.hasValue) > throw new IllegalStateException("Stream has more than one element!"); > else if (left.hasValue) > return left; > else > return right; > }, > (state) -> { > if (!state.hasValue) > throw new IllegalStateException("Stream has less than one element!"); > else > return state.value; > }); > } > Which means that you could write things like: > var neatlyFolded = stream.gather(fold(…)).collect(singleton()); I will ask for a step further and add an overload to collect that takes a Gatherer and calls gather(gatherer).collect(Collectors.singleton()) so we can directly write var neatlyFolded = stream.collect(fold(...)); As a user, I see value in using the more powerful Gatherer API like a collector without having to think too much. > Cheers, > √ regards, Rémi > Viktor Klang > Software Architect, Java Platform Group > Oracle > From: Tyler Kindy <m...@tylerkindy.com> > Sent: Monday, 30 October 2023 18:38 > To: Viktor Klang <viktor.kl...@oracle.com> > Cc: core-libs-dev@openjdk.org <core-libs-dev@openjdk.org> > Subject: Re: [External] : Re: Update on JEP-461: Stream Gatherers (Preview) > Hi Viktor, > Thanks for the response! I see what you mean, it’s a good point that > single-element streams are just as valid as streams with other numbers of > elements. > The weird part for me, though, is getting the folded result out of the stream > at > the end (which, while not always, I believe will be the most common thing to > want to do after `fold`). The obvious way, which you used in your > presentation, > is `findAny`. Since the stream could be empty, it makes sense that `findAny` > returns an `Optional`, but with `fold` we know that the stream will have > exactly one element in it. > Of course, this API is totally serviceable. You can use > `Optional::orElseThrow` > on the result of `findAny` to communicate your intent that you expect there to > be an element. But that feels a bit roundabout to me; I feel that a cleaner > API > would be to get the folded result out directly, like how `Collector` works. > To your point, implementing `fold` as a Gatherer doesn’t mean we can never > have > a terminal `fold`. And having `fold` in any way at all is great; that’s one > feature of Streams I commonly find myself wanting when writing code (the other > is windowing, which I think you’ve covered the need for well; no notes on > those > \uD83D\uDE04). > But it does make me wish that `Collector` was also flexible enough to > implement > `fold`. Maybe a topic for future work. \uD83D\uDE04 > Thanks again, > Tyler >> On Oct 30, 2023, at 10:39 AM, Viktor Klang <viktor.kl...@oracle.com> wrote: >> 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 <m...@tylerkindy.com> >> Sent: Monday, 30 October 2023 12:14 >> To: Viktor Klang <viktor.kl...@oracle.com> >> Cc: core-libs-dev@openjdk.org <core-libs-dev@openjdk.org> >> 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 <viktor.kl...@oracle.com> wrote: >>> Greetings, >>> As you may have already seen, Stream Gatherers is now a [ >>> https://openjdk.org/jeps/461 | Preview >>> JEP with Candidate status ] >>> Work-in-progress (interfaces, implementation, tests, benches, and >>> documentation) >>> for JEP-461 is currently available [ >>> https://urldefense.com/v3/__https://github.com/viktorklang-ora/jdk/tree/gatherer__;!!ACWV5N9M2RV99hQ!Lz2sC02xW35XpuwoaqBvD_iR80Xrzkbj-60oOKuQklUOm8e69-O3WC9N93leBFbMkFmULJmsat9k1dmt$ >>> | here ] . >>> While [ https://cr.openjdk.org/~vklang/Gatherers.html | the design ] 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 [ >>> https://urldefense.com/v3/__https://www.youtube.com/watch?v=8fMFa6OqlY8__;!!ACWV5N9M2RV99hQ!Lz2sC02xW35XpuwoaqBvD_iR80Xrzkbj-60oOKuQklUOm8e69-O3WC9N93leBFbMkFmULJmsam2MP-_S$ >>> | Gatherers >>> at Devoxx ] , which I'd recommend watching for an introduction to the >>> feature. >>> [ >>> https://urldefense.com/v3/__https://www.youtube.com/watch?v=8fMFa6OqlY8__;!!ACWV5N9M2RV99hQ!Lz2sC02xW35XpuwoaqBvD_iR80Xrzkbj-60oOKuQklUOm8e69-O3WC9N93leBFbMkFmULJmsam2MP-_S$ >>> | <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 ] >>> 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 ... >>> [ >>> https://urldefense.com/v3/__http://www.youtube.com/__;!!ACWV5N9M2RV99hQ!Lz2sC02xW35XpuwoaqBvD_iR80Xrzkbj-60oOKuQklUOm8e69-O3WC9N93leBFbMkFmULJmsalMQI96j$ >>> | www.youtube.com ] >>> Cheers, >>> √ >>> Viktor Klang >>> Software Architect, Java Platform Group >>> Oracle