[External] : Re: Update on JEP-461: Stream Gatherers (Preview)
Remi Forax
forax at univ-mlv.fr
Mon Oct 30 22:39:31 UTC 2023
> From: "Viktor Klang" <viktor.klang at oracle.com>
> To: "Tyler Kindy" <me at tylerkindy.com>
> Cc: "core-libs-dev" <core-libs-dev at 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 <me at tylerkindy.com>
> Sent: Monday, 30 October 2023 18:38
> To: Viktor Klang <viktor.klang at oracle.com>
> Cc: core-libs-dev at openjdk.org <core-libs-dev at 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.klang at 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 <me at tylerkindy.com>
>> Sent: Monday, 30 October 2023 12:14
>> To: Viktor Klang <viktor.klang at oracle.com>
>> Cc: core-libs-dev at openjdk.org <core-libs-dev at 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.klang at 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
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://mail.openjdk.org/pipermail/core-libs-dev/attachments/20231030/e7d39474/attachment-0001.htm>
More information about the core-libs-dev
mailing list