RFR: 8072727 - add variation of Stream.iterate() that's finite
Tagir F. Valeev
amaembo at gmail.com
Fri Mar 11 14:57:12 UTC 2016
Hello!
In general IntGenerator looks good, though I still like my Emitter
idea :-) I will probably add it to my library regardless of this
discussion.
The problem with spliterator is that you should be very careful to
fulfill the contract:
- You must call the action at most once
- If you called it you must return true
- Otherwise you must return false
So it's quite low-level thing which should be carefully tested.
Also note that your Collatz solution does not emit the final 1 which
is actually the most problematic thing. Without final 1 it can be
expressed easily with three-arg iterate:
IntStream.iterate(start, curr -> curr != 1,
curr -> curr % 2 == 0 ? curr / 2 : curr * 3 + 1).forEach(...);
With best regards,
Tagir Valeev.
PL> Hi Tagir,
PL> I wonder if lambdas are the right tool in Java to express such
PL> generators. They have something in common - each generator uses a very
PL> specific internal state. So maybe an abstract Splitterator class that
PL> just needs one method to be implemented is perhaps concise enough and
PL> easier to understand.
PL> For example, with the following utility class:
PL> public abstract class IntGenerator
PL> extends Spliterators.AbstractIntSpliterator {
PL> public IntGenerator() {
PL> super(Long.MAX_VALUE, ORDERED | IMMUTABLE | NONNULL);
PL> }
PL> public IntStream stream() {
PL> return StreamSupport.intStream(this, false);
PL> }
PL> }
PL> ... uses cases can be expressed as good old anonymous inner classes:
PL> // collatz
PL> int start = 10;
PL> new IntGenerator() {
PL> int curr = start;
PL> public boolean tryAdvance(IntConsumer action) {
PL> if (curr == 1) return false;
PL> action.accept(curr);
PL> curr = curr % 2 == 0 ? curr / 2 : curr * 3 + 1;
PL> return true;
PL> }
PL> }.stream().forEach(System.out::println);
PL> // scanner
PL> Scanner sc = new Scanner("1 2 3 4 foo");
PL> new IntGenerator() {
PL> public boolean tryAdvance(IntConsumer action) {
PL> if (sc.hasNextInt()) {
PL> action.accept(sc.nextInt());
PL> return true;
PL> } else {
PL> return false;
PL> }
PL> }
PL> }.stream().forEach(System.out::println);
PL> // craps
PL> Random r = new Random();
PL> IntSupplier dice = () -> r.nextInt(6) + r.nextInt(6) + 2;
PL> new IntGenerator() {
PL> int point;
PL> boolean end;
PL> public boolean tryAdvance(IntConsumer action) {
PL> if (!end) {
PL> int roll = dice.getAsInt();
PL> action.accept(roll);
PL> if (roll == 7
PL> || (point == 0 && (roll > 10 || roll < 4))
PL> || (point != 0 && roll == point)) {
PL> end = true;
PL> } else if (point == 0) {
PL> point = roll;
PL> }
PL> }
PL> return !end;
PL> }
PL> }.stream().forEach(System.out::println);
PL> It is general enough, uses as little state as possible and does not
PL> produce garbage for each emitted element.
PL> Regards, Peter
PL> On 03/11/2016 09:27 AM, Tagir F. Valeev wrote:
>> Using the IntEmitter the Craps round could also be implemented easily
>> and without additional classes:
>>
>> public static IntEmitter crapsEmitter(IntSupplier dice, int point) {
>> return action -> {
>> int roll = dice.getAsInt();
>> action.accept(roll);
>> return roll == 7 || (point == 0 && (roll > 10 || roll < 4)) || (point != 0 && roll == point)
>> ? null : crapsEmitter(dice, point == 0 ? roll : point);
>> };
>> }
>>
>> Usage:
>>
>> Random r = new Random();
>> IntSupplier dice = () -> r.nextInt(6) + r.nextInt(6) + 2;
>> crapsEmitter(dice, 0).stream().forEach(System.out::println);
>>
>> TFV> Here's the source code I wrote for this test:
>>
>> TFV> http://cr.openjdk.java.net/~tvaleev/patches/produce/ProduceTest.java
>>
>> TFV> I also have somewhat weird idea of emitters (added to this test class
>> TFV> as well). Here's example:
>>
>> TFV> public static IntEmitter collatzEmitter(int val) {
>> TFV> return action -> {
>> TFV> action.accept(val);
>> TFV> return val == 1 ? null : collatzEmitter(val % 2 == 0 ? val / 2 : val * 3 + 1);
>> TFV> };
>> TFV> }
>>
>> TFV> Usage:
>>
>> TFV> collatzEmitter(17).stream().forEach(System.out::println);
>>
>> TFV> It does not need any special value as well as special handling of
>> TFV> starting value. No additional code for finishing is also necessary.
>> TFV> However such solution surely produces garbage (at least one lambda
>> TFV> instance per iteration), so the performance would degrade. However I
>> TFV> like it.
>>
>> TFV> With best regards,
>> TFV> Tagir Valeev.
>>
>> TFV>> Hello!
>>
>> TFV>> Just for your information I implemented two alternative stream
>> TFV>> producers as you suggested:
>>
>> TFV>> // Produces stream of values returned by producer until the first
>> TFV>> // empty optional is produced
>> TFV>> public static IntStream produce(Supplier<OptionalInt> producer);
>>
>> TFV>> // Produces stream of values until predicate returns false
>> TFV>> // iff predicate returns true it must call the supplied IntConsumer
>> TFV>> // exactly once (like in Spliterator.tryAdvance)
>> TFV>> public static IntStream produce(Predicate<IntConsumer> producer);
>>
>> TFV>> // Produces stream of values until advancer doesn't call the
>> TFV>> // IntConsumer at least once.
>> TFV>> // Calling the IntConsumer multiple times is also acceptable.
>> TFV>> public static IntStream produce(Consumer<IntConsumer> advancer);
>>
>> TFV>> I tried to produce the Collatz sequence starting from given number and
>> TFV>> including the trailing one. Unfortunately I don't see how such
>> TFV>> signatures might simplify the thing. Here's three-arg iterate
>> TFV>> implementation posted by me before, for reference:
>>
>> TFV>> IntStream.iterate(start, val -> val != -1,
>> TFV>> val -> val == 1 ? -1 : val % 2 == 0 ? val / 2 : val * 3 + 1);
>>
>> TFV>> Here's plain-old-loop implementation (without stream):
>>
>> TFV>> public static void collatzLoop(int start) {
>> TFV>> int val = start;
>> TFV>> System.out.println(val);
>> TFV>> while(val != 1) {
>> TFV>> val = val % 2 == 0 ? val / 2 : val * 3 + 1;
>> TFV>> System.out.println(val);
>> TFV>> }
>> TFV>> }
>>
>> TFV>> The problem here is that in the easiest implementation we need to emit
>> TFV>> the value at two places. Otherwise we either have problems with the
>> TFV>> last value or with the first value. Here's Supplier<OptionalInt>
>> TFV>> implementation:
>>
>> TFV>> public static IntStream collatzSupplier(int start) {
>> TFV>> int[] cur = {-1};
>> TFV>> return produce(() ->
>> TFV>> cur[0] == 1 ? OptionalInt.empty()
>> TFV>> : OptionalInt.of(cur[0] =
>> TFV>> (cur[0] == -1 ? start :
>> TFV>> cur[0] % 2 == 0 ?
>> TFV>> cur[0] / 2 : cur[0] * 3 + 1)));
>> TFV>> }
>>
>> TFV>> Of course we have to maintain the shared state (here I used dirty
>> TFV>> one-element array trick). Here we have the same three conditions: to
>> TFV>> detect starting case, to detect finishing case and to separate
>> TFV>> even/odd cases. Also we also need some special value. Predicate-based
>> TFV>> solution is not simpler and has the same three conditions:
>>
>> TFV>> public static IntStream collatzPredicate(int start) {
>> TFV>> int[] cur = {-1};
>> TFV>> return produce(action -> {
>> TFV>> if(cur[0] == 1) return false;
>> TFV>> action.accept(cur[0] = (
>> TFV>> cur[0] == -1 ? start :
>> TFV>> cur[0] % 2 == 0 ? cur[0] / 2 : cur[0] * 3 + 1));
>> TFV>> return true;
>> TFV>> });
>> TFV>> }
>>
>> TFV>> So, at least for Collatz problem these signatures are not
>> TFV>> well-suitable (at least not better than three-arg iterate).
>>
>> TFV>> Using Consumer<IntConsumer> advancer is somewhat better:
>>
>> TFV>> public static IntStream collatzConsumer(int start) {
>> TFV>> int[] cur = {-1};
>> TFV>> return produce(action -> {
>> TFV>> if(cur[0] == -1) action.accept(cur[0] = start);
>> TFV>> if(cur[0] != 1) action.accept((cur[0] = cur[0] % 2 == 0 ? cur[0] / 2 : cur[0] * 3 + 1));
>> TFV>> });
>> TFV>> }
>>
>> TFV>> However the implementation of such producer is not very good
>> TFV>> performance-wise as in tryAdvance we need to buffer emitted values.
>> TFV>> The forEachRemaining performs much better though.
>>
>> TFV>> In general I like Supplier<OptionalInt> version better (it also used
>> TFV>> in your Scanner example), though we need to check how much overhead it
>> TFV>> creates. We cannot rely on JIT inlining here as functional interfaces
>> TFV>> are really ubiquitous.
>>
>> TFV>> Having continuations in Java would actually solve this problem, but I
>> TFV>> guess we will unlikely to see them in the near future.
>>
>> TFV>> With best regards,
>> TFV>> Tagir Valeev.
>>
>> SM>>> Finally getting back to this.
>>
>> SM>>> So, now that this has been clarified to model a for-loop, this seems fine. It
>> SM>>> looks like Paul is sponsoring this for you. Great, let's move ahead with it.
>>
>> SM>>> As the author of this particular RFE, though, I should say that this isn't what
>> SM>>> I had in mind when I wrote the RFE. :-)
>>
>> SM>>> There are two main issues I'm concerned about:
>>
>> SM>>> 1) modeling loops that aren't for-loops; and
>>
>> SM>>> 2) modeling loops where the termination condition is known to the stream source
>> SM>>> but isn't part of the stream value.
>>
>> SM>>> If covering these requires a different API and RFE, then that's fine. I just
>> SM>>> want to make sure these cases aren't dropped.
>>
>> SM>>> For case (1) I think the Collatz example is a good one, since you basically had
>> SM>>> to cheat in order to get it to work with this API, either by appending 1, or by
>> SM>>> introducing a sentinel value. It's not always possible to do this.
>>
>> SM>>> The kind of loops I'm interested in look like this:
>>
>> SM>>> do {
>> SM>>> emitValue();
>> SM>>> } while (condition);
>>
>> SM>>> or
>>
>> SM>>> while (true) {
>> SM>>> emitValue();
>> SM>>> if (condition)
>> SM>>> break;
>> SM>>> stuff();
>> SM>>> }
>>
>> SM>>> Clearly it's possible to rearrange these to fit into the 3-arg iterate() method,
>> SM>>> but it's unnatural to do so. For example, to implement the craps dice game, the
>> SM>>> obvious Java approach is to maintain the game state in an object or even local
>> SM>>> variables that are mutated as the game progresses. To use the 3-arg iterate()
>> SM>>> method, you wrote a State object (a value), which knows how to generate its
>> SM>>> successor State, and from which the int values are extracted. This is a very
>> SM>>> nice functional approach, but I expect that many people will have data sources
>> SM>>> that rely on mutation or external resources. It doesn't seem reasonable to
>> SM>>> require rewriting the source logic in order to produce a stream from it. And in
>> SM>>> general it might be arbitrarily difficult.
>>
>> SM>>> It's probably easier to wrap a Spliterator around the source logic and simply
>> SM>>> call it from tryAdvance(). In fact, this is what I'd like to see in a
>> SM>>> stream-source API, to make it easier to create such stream sources without
>> SM>>> having to subclass AbstractSpliterator.
>>
>> SM>>> For (2) it's critical that the stream source have control over production of
>> SM>>> values. Placing takeWhile() / takeWhileInclusive() downstream doesn't work,
>> SM>>> since the stream machinery (particularly in parallel) may aggressively pull
>> SM>>> elements from the source before they're presented to takeWhile().
>>
>> SM>>> For example, consider parsing int values from a Scanner into an IntStream, until
>> SM>>> there are no more, leaving the scanner at a known state. Suppose there were an
>> SM>>> iterate(Supplier<OptionalInt>) overload. Then something like this would work:
>>
>> SM>>> Scanner sc = new Scanner("1 2 3 4 foo");
>> SM>>> IntStream.iterate(
>> SM>>> () -> sc.hasNextInt() ? OptionalInt.of(sc.nextInt())
>> SM>>> : OptionalInt.empty())
>> SM>>> .forEach(System.out::println);
>>
>> SM>>> assert sc.hasNext();
>> SM>>> System.out.println(sc.next()); // prints "foo"
>>
>> SM>>> Boxing things into optionals is a bit cumbersome, and I'm also concerned about
>> SM>>> the overhead. But it illustrates the point that the termination condition is
>> SM>>> separate from the values that go into the stream. In particular there's no
>> SM>>> "seed" value to which a termination condition can be applied. (And "iterate"
>> SM>>> might not be the best name for this method, either.)
>>
>> SM>>> Oh well, I think this is turning into another RFE.
>>
>> SM>>> s'marks
>>
>>
>>
>>
>> SM>>> On 2/16/16 9:48 PM, Tagir F. Valeev wrote:
>>>>>> Hello, Stuart!
>>>>>>
>>>>>> Thank you for your comments.
>>>>>>
>>>>>> SM> I'd suggest focusing on the API first before worrying about how to track the
>>>>>> SM> stream state with booleans, etc. Is the API convenient to use, and how well does
>>>>>> SM> it support the use cases we envision for it?
>>>>>>
>>>>>> As Brian already noted, the most benefit of such signature is the
>>>>>> resemblance to the good old for loop. Also it's good, because the
>>>>>> lambdas don't need to maintain external mutable state in this case
>>>>>> (the state is encapsulated in the current element). Most of your
>>>>>> proposed examples, however, need to do it as they don't receive the
>>>>>> existing state. Also I see no reason to create a method which is
>>>>>> compatible with iterator::hasNext/iterator::next or even
>>>>>> spliterator::tryAdvance. If you already have a spliterator, you can
>>>>>> create a stream using StreamSupport.stream(spliterator, false). If you
>>>>>> have an iterator, you can convert it to spliterator using
>>>>>> Spliterators.spliterator[UnknownSize]. Well, this probably looks ugly,
>>>>>> but more flexible and signals that some low-level stuff is performed.
>>>>>>
>>>>>> Supplier<Stream<T>> is definitely bad in terms of performance.
>>>>>> Creating a new stream is not nearly free. To illustrate this I wrote a
>>>>>> simple benchmark which compares .flatMapToInt(OptionalInt::stream)
>>>>>> with Java8 way .filter(OptionalInt::isPresent).mapToInt(OptionalInt::getAsInt)
>>>>>>
>>>>>> Here's source code and full output:
>>>>>> http://cr.openjdk.java.net/~tvaleev/jmh/optionalstream/
>>>>>>
>>>>>> Benchmark (n) Mode Cnt Score Error Units
>>>>>> OptionalTest.testOptionalFilterGet 10 avgt 30 0,171 ± 0,011 us/op
>>>>>> OptionalTest.testOptionalFilterGet 1000 avgt 30 6,295 ± 0,046 us/op
>>>>>> OptionalTest.testOptionalFilterGet 1000000 avgt 30 12597,706 ± 69,214 us/op
>>>>>> OptionalTest.testOptionalStream 10 avgt 30 0,330 ± 0,002 us/op
>>>>>> OptionalTest.testOptionalStream 1000 avgt 30 27,552 ± 0,577 us/op
>>>>>> OptionalTest.testOptionalStream 1000000 avgt 30 30837,240 ± 812,420 us/op
>>>>>>
>>>>>> Involving intermediate streams makes the thing at least twice slower.
>>>>>> Surely this delay could become negligible in some scenarios, but I
>>>>>> think it's inacceptable to enforce users to create new source with a
>>>>>> bunch of streams. At least primitive specializations will become
>>>>>> meaningless in this case: boxing would eat much less time compared to
>>>>>> stream creation.
>>>>>>
>>>>>> As for elements drawn from the queue, it's much better to use existing
>>>>>> takeWhile method:
>>>>>>
>>>>>> queue.stream().takeWhile(x -> x.equals(sentinel));
>>>>>>
>>>>>> True, such approach will not include the sentinel element to the
>>>>>> result, and there's no easy way to do it with current API. Probably
>>>>>> additional method (takeWhileInclusive?) could be considered to solve
>>>>>> such problems. Still, I think, drawing from the queue is not the
>>>>>> problem which should be solved with new iterate() method.
>>>>>>
>>>>>> As for Collatz conjecture, it's quite easy to iterate without trailing
>>>>>> one:
>>>>>>
>>>>>> IntStream.iterate(start, val -> val != 1,
>>>>>> val -> val % 2 == 0 ? val / 2 : val * 3 + 1)
>>>>>>
>>>>>> If having one is desired, then it would be easier just to append one
>>>>>> to the stream (even if Collatz conjecture is false we will have an
>>>>>> infinite stream, so appended one will never appear):
>>>>>>
>>>>>> IntStream.concat(
>>>>>> IntStream.iterate(start, val -> val != 1,
>>>>>> val -> val % 2 == 0 ? val / 2 : val * 3 + 1),
>>>>>> IntStream.of(1))
>>>>>>
>>>>>> A side note: having IntStream.append(int... numbers) would be really
>>>>>> nice:
>>>>>>
>>>>>> IntStream.iterate(start, val -> val != 1,
>>>>>> val -> val % 2 == 0 ? val / 2 : val * 3 + 1).append(1)
>>>>>>
>>>>>> Another approach would be to introduce a special stop value (for
>>>>>> example, -1):
>>>>>>
>>>>>> IntStream.iterate(start, val -> val != -1,
>>>>>> val -> val == 1 ? -1 : val % 2 == 0 ? val / 2 : val * 3 + 1)
>>>>>>
>>>>>> This stream produces Collatz series, including the trailing one.
>>>>>>
>>>>>> As for Craps, I never heard about such game. If I understood the rules
>>>>>> correctly, it's good to represent the state as separate object and
>>>>>> define state transition via its method. Something like this should
>>>>>> work:
>>>>>>
>>>>>> Random r = new Random();
>>>>>> IntSupplier dice = () -> r.nextInt(6)+r.nextInt(6)+2;
>>>>>> class State {
>>>>>> int roll, point;
>>>>>>
>>>>>> State(int roll, int point) {
>>>>>> this.roll = roll;
>>>>>> this.point = point;
>>>>>> }
>>>>>>
>>>>>> State() {
>>>>>> this(dice.getAsInt(), 0);
>>>>>> }
>>>>>>
>>>>>> boolean isStopRound() {
>>>>>> return roll == 7 || (point == 0 && (roll > 10 || roll < 4)) || (point != 0 && roll == point);
>>>>>> }
>>>>>>
>>>>>> State next() {
>>>>>> return isStopRound() ? null : new State(dice.getAsInt(), point == 0 ? roll : point);
>>>>>> }
>>>>>> }
>>>>>> Stream.iterate(new State(), Objects::nonNull, State::next)
>>>>>> .mapToInt(state -> state.roll)
>>>>>> .forEach(System.out::println);
>>>>>>
>>>>>> With best regards,
>>>>>> Tagir Valeev.
>>>>>>
>>>>>>
>>>>>> SM> In particular, I can imagine a number of cases where it would be very helpful to
>>>>>> SM> be able to support an empty stream, or where the computation to produce the
>>>>>> SM> first element is the same as the computation to produce subsequent elements.
>>>>>> SM> Requiring a value for the first stream element is at odds with that.
>>>>>>
>>>>>> SM> Here are some ideas for use cases to try out:
>>>>>>
>>>>>> SM> - a series of dice rolls representing a round of craps [1]
>>>>>> SM> - elements drawn from a queue until the queue is empty or until
>>>>>> SM> a sentinel is reached
>>>>>> SM> - a sequence of numbers that (probably) terminates but whose length
>>>>>> SM> isn't necessarily known in advance (e.g. Collatz sequence [2])
>>>>>>
>>>>>> SM> [1] https://en.wikipedia.org/wiki/Craps
>>>>>>
>>>>>> SM> [2] https://en.wikipedia.org/wiki/Collatz_conjecture
>>>>>>
>>>>>> SM> Note that in some cases the sentinel value that terminates the stream should be
>>>>>> SM> part of the stream, and in other cases it's not.
>>>>>>
>>>>>> SM> I'm sure you can find more uses cases by perusing Stack Overflow. :-)
>>>>>>
>>>>>> SM> I'm a bit skeptical of the use of "iterate" for producing a finite stream. There
>>>>>> SM> are the usual issues with overloading, but there's also potential confusion as
>>>>>> SM> some forms of iterate() are infinite and others finite. I'll suggest the name
>>>>>> SM> "produce" instead, but there are surely better terms.
>>>>>>
>>>>>> SM> One thing to think about is where the state of the producer is stored. Is it
>>>>>> SM> expected to be in an argument that's passed to each invocation of the functional
>>>>>> SM> argument, or is it expected to be captured? I don't think there's an answer in
>>>>>> SM> isolation; examining use cases would probably shed some light here.
>>>>>>
>>>>>> SM> Here are a few API ideas (wildcards elided):
>>>>>>
>>>>>> SM> --
>>>>>>
>>>>>> SM> <T> Stream<T> iterate(T seed, Predicate<T> predicate, UnaryOperator<T> f)
>>>>>>
>>>>>> SM> The API from your proposal, for comparison purposes.
>>>>>>
>>>>>> SM> --
>>>>>>
>>>>>> SM> <T> Stream<T> produce(Supplier<Optional<T>>)
>>>>>>
>>>>>> SM> Produces elements until empty Optional is returned. This box/unboxes every
>>>>>> SM> element, maybe(?) alleviated by Valhalla.
>>>>>>
>>>>>> SM> --
>>>>>>
>>>>>> SM> <T> Stream<T> produce(BooleanSupplier, Supplier<T>)
>>>>>>
>>>>>> SM> Calls the BooleanSupplier; if true the next stream element is what's returned by
>>>>>> SM> calling the Supplier. If BooleanSupplier returns false, end of stream. If you
>>>>>> SM> have an iterator already, this enables
>>>>>>
>>>>>> SM> produce(iterator::hasNext, iterator::next)
>>>>>>
>>>>>> SM> But if you don't have an iterator already, coming up with the functions to
>>>>>> SM> satisfy the iterator-style protocol is sometimes painful.
>>>>>>
>>>>>> SM> --
>>>>>>
>>>>>> SM> <T> Stream<T> produce(Predicate<Consumer<T>> advancer)
>>>>>>
>>>>>> SM> This has an odd signature, but the function is like Spliterator.tryAdvance(). It
>>>>>> SM> must either call the consumer once and return true, or return false without
>>>>>> SM> calling the consumer.
>>>>>>
>>>>>> SM> --
>>>>>>
>>>>>> SM> <T> Stream<T> produce(Consumer<Consumer<T>> advancer)
>>>>>>
>>>>>> SM> A variation of the above, without a boolean return. The advancer calls the
>>>>>> SM> consumer one or more times to add elements to the stream. End of stream occurs
>>>>>> SM> when the advancer doesn't call the consumer.
>>>>>>
>>>>>> SM> --
>>>>>>
>>>>>> SM> <T> Stream<T> produce(Supplier<Stream<T>>)
>>>>>>
>>>>>> SM> A variation of Supplier<Optional<T>> where the supplier returns a stream
>>>>>> SM> containing zero or more elements. The stream terminates if the supplier returns
>>>>>> SM> an empty stream. There "boxing" overhead here, but we don't seem to be bothered
>>>>>> SM> by this with flatMap().
>>>>>>
>>>>>> SM> --
>>>>>>
>>>>>> SM> s'marks
>>>>>>
>>>>>>
>>>>>> SM> On 2/14/16 6:53 AM, Tagir F. Valeev wrote:
>>>>>>>> Hello!
>>>>>>>>
>>>>>>>> I wanted to work on foldLeft, but Brian asked me to take this issue
>>>>>>>> instead. So here's webrev:
>>>>>>>> http://cr.openjdk.java.net/~tvaleev/webrev/8072727/r1/
>>>>>>>>
>>>>>>>> I don't like iterator-based Stream source implementations, so I made
>>>>>>>> them AbstractSpliterator-based. I also implemented manually
>>>>>>>> forEachRemaining as, I believe, this improves the performance in
>>>>>>>> non-short-circuiting cases.
>>>>>>>>
>>>>>>>> I also decided to keep two flags (started and finished) to track the
>>>>>>>> state. Currently existing implementation of infinite iterate() does
>>>>>>>> not use started flag, but instead reads one element ahead for
>>>>>>>> primitive streams. This seems wrong to me and may even lead to
>>>>>>>> unexpected exceptions (*). I could get rid of "started" flag for
>>>>>>>> Stream.iterate() using Streams.NONE, but this would make object
>>>>>>>> implementation different from primitive implementations. It would also
>>>>>>>> be possible to keep single three-state variable (byte or int,
>>>>>>>> NOT_STARTED, STARTED, FINISHED), but I doubt that this would improve
>>>>>>>> the performance or footprint. Having two flags looks more readable to
>>>>>>>> me.
>>>>>>>>
>>>>>>>> Currently existing two-arg iterate methods can now be expressed as a
>>>>>>>> partial case of the new method:
>>>>>>>>
>>>>>>>> public static<T> Stream<T> iterate(final T seed, final UnaryOperator<T> f) {
>>>>>>>> return iterate(seed, x -> true, f);
>>>>>>>> }
>>>>>>>> (same for primitive streams). I may do this if you think it's
>>>>>>>> reasonable.
>>>>>>>>
>>>>>>>> I created new test class and added new iterate sources to existing
>>>>>>>> data providers.
>>>>>>>>
>>>>>>>> Please review and sponsor!
>>>>>>>>
>>>>>>>> With best regards,
>>>>>>>> Tagir Valeev.
>>>>>>>>
>>>>>>>> (*) Consider the following code:
>>>>>>>>
>>>>>>>> int[] data = {1,2,3,4,-1};
>>>>>>>> IntStream.iterate(0, x -> data[x])
>>>>>>>> .takeWhile(x -> x >= 0)
>>>>>>>> .forEach(System.out::println);
>>>>>>>>
>>>>>>>> Currently this unexpectedly throws an AIOOBE, because
>>>>>>>> IntStream.iterate unnecessarily tries to read one element ahead.
>>>>>>>>
More information about the core-libs-dev
mailing list