Proposal: Stream.iterateWhile(T seed, Function<T, Optional<T>> mapper)

Tomasz Linkowski t.linkowski at gmail.com
Mon Oct 29 18:07:45 UTC 2018


Two more things:

1) The example I provided might have been too simple to be convincing. Note
that the proposed method is particularly useful when the Predicate
(hasNext) has some common expensive part with the UnaryOperator (next). For
example:

    boolean hasNext(Foo foo) {
        Bar bar = computeBar(foo); // expensive
        return hasNextFoo(bar);
    }

    Foo next(Foo foo) {
        Bar bar = computeBar(foo); // expensive
        return computeNextFoo(bar);
    }

which can instead become:

    Optional<Foo> findNext(Foo foo) {
        Bar bar = computeBar(foo); // expensive
        return hasNextFoo(bar)
            ? Optional.of(computeNextFoo(bar))
            : Optional.empty();
    }


2) I forgot about the primitive specializations - here they are:

    static IntStream iterateWhile(int seed, IntFunction<OptionalInt>
mapper) {
        return Stream.iterate(
                OptionalInt.of(seed), OptionalInt::isPresent,
                optionalInt -> mapper.apply(optionalInt.getAsInt())
        ).mapToInt(OptionalInt::getAsInt);
    }

    static LongStream iterateWhile(int seed, LongFunction<OptionalLong>
mapper) {
        return Stream.iterate(
                OptionalLong.of(seed), OptionalLong::isPresent,
                optionalLong -> mapper.apply(optionalLong.getAsLong())
        ).mapToLong(OptionalLong::getAsLong);
    }

    static DoubleStream iterateWhile(int seed,
DoubleFunction<OptionalDouble> mapper) {
        return Stream.iterate(
                OptionalDouble.of(seed), OptionalDouble::isPresent,
                optionalDouble -> mapper.apply(optionalDouble.getAsDouble())
        ).mapToDouble(OptionalDouble::getAsDouble);
    }

The implementations above are just examples (most likely, dedicated
spliterators should be used for better performance).

Note, however, that there is one caveat here, namely:
- `Stream.iterateWhile` can produce an empty `Stream` (if null seed is
given),
- `[Primitive]Stream.iterateWhile` cannot (because the seed cannot be null).

One way to provide consistency here (if desirable) would be to require the
seed in `Stream.iterateWhile` to be non-null.

Regards,
Tomasz Linkowski


On Fri, Oct 26, 2018 at 5:44 PM Tomasz Linkowski <t.linkowski at gmail.com>
wrote:

> Hi,
>
> Please, consider adding a new static method to the `Stream` interface
> (names TBD):
>
>         static <T> Stream<T> iterateWhile(
>             T seed, Function<? super T, ? extends Optional<? extends T>>
> mapper
>         );
>
>
> == OVERVIEW ==
>
> + non-null equivalent of `Stream.iterate(seed, hasNext, next)` [1]
> + `mapper` like in `Optional.flatMap(mapper)` [2]
> + shift from two operations to one operation, like in:
>     - `Iterator.hasNext/next` => `Spliterator.tryAdvance`
>     - pattern matching (test/bind)
> + intent: "nudge towards writing clearer code" (Brian Goetz about LVTI [3])
> + useful for `Optional`-based APIs
> + trivial implementation
>
>
> == JUSTIFICATION ==
>
> `Stream.iterate(seed, hasNext, next)` is great for nullable-return-based
> APIs. Example: returning a chain of `Throwable` causes:
>
>         Stream.iterate(throwable, Objects::nonNull, Throwable::getCause)
>
> For `Optional`-based APIs, using `Stream.iterate` becomes cumbersome.
> Example (assume `Throwable.findCause()` returns `Optional`):
>
>         Stream.iterate(throwable, Objects::nonNull, t ->
> t.findCause().orElse(null))
>
> Using the proposed method, the above can become much clearer:
>
>         Stream.iterateWhile(throwable, Throwable::findCause)
>
> This is just one example - I can provide more if needed.
>
>
> == IMPLEMENTATION ==
>
> Preferred implementation:
>
>         iterateWhile(seed, mapper) -> Stream.iterate(
>             seed, Objects::nonNull,
>             t ->  mapper.apply(t).orElse(null)
>         );
>
> Equivalent `Optional`-based implementation:
>
>         iterateWhile(seed, mapper) -> Stream.iterate(
>                 Optional.ofNullable(seed), Optional::isPresent,
>                 optional ->  optional.flatMap(mapper)
>         ).map(Optional::get);
>
> Note that both implementations assume that `null` seed yields an empty
> `Stream`.
>
>
> == NAMING ==
>
> Name `iterate` cannot be safely overloaded because of
> `Stream.iterate(UnaryOperator)` so another name needs to be used. I
> proposed `iterateWhile` inspired by `takeWhile` but maybe it's a wrong
> trail (`takeWhile` takes a `Predicate`). Other names that come to my mind:
> `iterateWhilePresent`, `iterateOptional`, `iterateNonNull`.
>
>
> [1]
> https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/stream/Stream.html#iterate(T,java.util.function.Predicate,java.util.function.UnaryOperator)
> [2]
> https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/Optional.html#flatMap(java.util.function.Function)
> [3]
> http://mail.openjdk.java.net/pipermail/amber-spec-experts/2018-October/000826.html
>
> --
> Regards,
> Tomasz Linkowski
>


More information about the core-libs-dev mailing list