Lazy and fast concat and prepend (#30252)

This commit is contained in:
Johan Andrén 2021-05-27 17:18:47 +02:00 committed by GitHub
parent cbb12e6ef3
commit 4ade8ef2d1
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
17 changed files with 825 additions and 77 deletions

View file

@ -14,6 +14,17 @@ After completion of the original upstream the elements of the given source will
After completion of the original upstream the elements of the given source will be emitted.
Both streams will be materialized together.
@@@ note
The `concat` operator is for backwards compatibility reasons "detached" and will eagerly
demand an element from both upstreams when the stream is materialized and will then have a
one element buffer for each of the upstreams, this is most often not what you want, instead
use @ref(concatLazy)[concatLazy.md]
@@@
## Example
Scala
: @@snip [FlowConcatSpec.scala](/akka-stream-tests/src/test/scala/akka/stream/scaladsl/FlowConcatSpec.scala) { #concat }

View file

@ -0,0 +1,40 @@
# concatLazy
After completion of the original upstream the elements of the given source will be emitted.
@ref[Fan-in operators](../index.md#fan-in-operators)
## Signature
@apidoc[Source.concat](Source) { scala="#concatLazy[U>:Out,Mat2](that:akka.stream.Graph[akka.stream.SourceShape[U],Mat2]):FlowOps.this.Repr[U]" java="#concatLazy(akka.stream.Graph)" }
@apidoc[Flow.concat](Flow) { scala="#concatLazy[U>:Out,Mat2](that:akka.stream.Graph[akka.stream.SourceShape[U],Mat2]):FlowOps.this.Repr[U]" java="#concatLazy(akka.stream.Graph)" }
## Description
After completion of the original upstream the elements of the given source will be emitted.
Both streams will be materialized together, however, the given stream will be pulled for the first time only after the original upstream was completed. (In contrast, @ref(concat)[concat.md], introduces single-element buffers after both, original and given sources so that the given source is also pulled once immediately.)
To defer the materialization of the given source (or to completely avoid its materialization if the original upstream fails or cancels), wrap it into @ref(Source.lazySource)[../Source/lazySource.md].
If materialized values needs to be collected `concatLazyMat` is available.
## Example
Scala
: @@snip [FlowConcatSpec.scala](/akka-stream-tests/src/test/scala/akka/stream/scaladsl/FlowConcatSpec.scala) { #concatLazy }
Java
: @@snip [SourceOrFlow.java](/akka-docs/src/test/java/jdocs/stream/operators/SourceOrFlow.java) { #concatLazy }
## Reactive Streams semantics
@@@div { .callout }
**emits** when the current stream has an element available; if the current input completes, it tries the next one
**backpressures** when downstream backpressures
**completes** when all upstreams complete
@@@

View file

@ -14,8 +14,26 @@ Prepends the given source to the flow, consuming it until completion before the
Prepends the given source to the flow, consuming it until completion before the original source is consumed.
@@@ note
The `prepend` operator is for backwards compatibility reasons "detached" and will eagerly
demand an element from both upstreams when the stream is materialized and will then have a
one element buffer for each of the upstreams, this is most often not what you want, instead
use @ref(prependLazy)[prependLazy.md]
@@@
If materialized values needs to be collected `prependMat` is available.
@@@ note
The `prepend` operator is for backwards compatibility reasons "detached" and will eagerly
demand an element from both upstreams when the stream is materialized and will then have a
one element buffer for each of the upstreams, this is not always what you want, if not,
use @ref(prependLazy)[prependLazy.md]
@@@
## Example
Scala
: @@snip [FlowOrElseSpec.scala](/akka-stream-tests/src/test/scala/akka/stream/scaladsl/FlowPrependSpec.scala) { #prepend }

View file

@ -0,0 +1,40 @@
# prependLazy
Prepends the given source to the flow, consuming it until completion before the original source is consumed.
@ref[Fan-in operators](../index.md#fan-in-operators)
## Signature
@apidoc[Source.prepend](Source) { scala="#prepend[U>:Out,Mat2](that:akka.stream.Graph[akka.stream.SourceShape[U],Mat2]):FlowOps.this.Repr[U]" java="#prepend(akka.stream.Graph)" }
@apidoc[Flow.prepend](Flow) { scala="#prepend[U>:Out,Mat2](that:akka.stream.Graph[akka.stream.SourceShape[U],Mat2]):FlowOps.this.Repr[U]" java="#prepend(akka.stream.Graph)" }
## Description
Prepends the given source to the flow, consuming it until completion before the original source is consumed.
Both streams will be materialized together, however, the original stream will be pulled for the first time only after the prepended upstream was completed. (In contrast, @ref(prepend)[prepend.md], introduces single-element buffers after both, original and given sources so that the original source is also pulled once immediately.)
If materialized values needs to be collected `prependLazyMat` is available.
See also @ref[prepend](prepend.md) which is detached.
## Example
Scala
: @@snip [FlowPrependSpec.scala](/akka-stream-tests/src/test/scala/akka/stream/scaladsl/FlowPrependSpec.scala) { #prependLazy }
Java
: @@snip [SourceOrFlow.java](/akka-docs/src/test/java/jdocs/stream/operators/SourceOrFlow.java) { #prependLazy }
## Reactive Streams semantics
@@@div { .callout }
**emits** when the given stream has an element available; if the given input completes, it tries the current one
**backpressures** when downstream backpressures
**completes** when all upstreams complete
@@@

View file

@ -262,6 +262,7 @@ the inputs in different ways.
|--|--|--|
| |<a name="mergesequence"></a>@ref[MergeSequence](MergeSequence.md)|Merge a linear sequence partitioned across multiple sources.|
|Source/Flow|<a name="concat"></a>@ref[concat](Source-or-Flow/concat.md)|After completion of the original upstream the elements of the given source will be emitted.|
|Source/Flow|<a name="concatlazy"></a>@ref[concatLazy](Source-or-Flow/concatLazy.md)|After completion of the original upstream the elements of the given source will be emitted.|
|Source/Flow|<a name="interleave"></a>@ref[interleave](Source-or-Flow/interleave.md)|Emits a specifiable number of elements from the original source, then from the provided source and repeats.|
|Source/Flow|<a name="merge"></a>@ref[merge](Source-or-Flow/merge.md)|Merge multiple sources.|
|Source/Flow|<a name="mergelatest"></a>@ref[mergeLatest](Source-or-Flow/mergeLatest.md)|Merge multiple sources.|
@ -270,6 +271,7 @@ the inputs in different ways.
|Source/Flow|<a name="mergesorted"></a>@ref[mergeSorted](Source-or-Flow/mergeSorted.md)|Merge multiple sources.|
|Source/Flow|<a name="orelse"></a>@ref[orElse](Source-or-Flow/orElse.md)|If the primary source completes without emitting any elements, the elements from the secondary source are emitted.|
|Source/Flow|<a name="prepend"></a>@ref[prepend](Source-or-Flow/prepend.md)|Prepends the given source to the flow, consuming it until completion before the original source is consumed.|
|Source/Flow|<a name="prependlazy"></a>@ref[prependLazy](Source-or-Flow/prependLazy.md)|Prepends the given source to the flow, consuming it until completion before the original source is consumed.|
|Source/Flow|<a name="zip"></a>@ref[zip](Source-or-Flow/zip.md)|Combines elements from each of multiple sources into @scala[tuples] @java[*Pair*] and passes the @scala[tuples] @java[pairs] downstream.|
|Source/Flow|<a name="zipall"></a>@ref[zipAll](Source-or-Flow/zipAll.md)|Combines elements from two sources into @scala[tuples] @java[*Pair*] handling early completion of either source.|
|Source/Flow|<a name="ziplatest"></a>@ref[zipLatest](Source-or-Flow/zipLatest.md)|Combines elements from each of multiple sources into @scala[tuples] @java[*Pair*] and passes the @scala[tuples] @java[pairs] downstream, picking always the latest element of each.|
@ -393,6 +395,7 @@ For more background see the @ref[Error Handling in Streams](../stream-error.md)
* [completionStageSource](Source/completionStageSource.md)
* [completionTimeout](Source-or-Flow/completionTimeout.md)
* [concat](Source-or-Flow/concat.md)
* [concatLazy](Source-or-Flow/concatLazy.md)
* [conflate](Source-or-Flow/conflate.md)
* [conflateWithSeed](Source-or-Flow/conflateWithSeed.md)
* [cycle](Source/cycle.md)
@ -504,6 +507,7 @@ For more background see the @ref[Error Handling in Streams](../stream-error.md)
* [prefixAndTail](Source-or-Flow/prefixAndTail.md)
* [preMaterialize](Sink/preMaterialize.md)
* [prepend](Source-or-Flow/prepend.md)
* [prependLazy](Source-or-Flow/prependLazy.md)
* [queue](Source/queue.md)
* [queue](Sink/queue.md)
* [range](Source/range.md)

View file

@ -19,7 +19,9 @@ import akka.japi.function.Function2;
// #zip-with-index
// #or-else
// #prepend
// #prependLazy
// #concat
// #concatLazy
// #interleave
// #merge
// #merge-sorted
@ -33,7 +35,9 @@ import java.util.*;
// #merge
// #interleave
// #concat
// #concatLazy
// #prepend
// #prependLazy
// #or-else
// #zip-with-index
// #zip-with
@ -124,6 +128,16 @@ class SourceOrFlow {
// #prepend
}
void prependLazyExample() {
// #prepend
Source<String, NotUsed> ladies = Source.from(Arrays.asList("Emma", "Emily"));
Source<String, NotUsed> gentlemen = Source.from(Arrays.asList("Liam", "William"));
gentlemen.prependLazy(ladies).runWith(Sink.foreach(System.out::print), system);
// this will print "Emma", "Emily", "Liam", "William"
// #prepend
}
void concatExample() {
// #concat
Source<Integer, NotUsed> sourceA = Source.from(Arrays.asList(1, 2, 3, 4));
@ -134,6 +148,16 @@ class SourceOrFlow {
// #concat
}
void concatLazyExample() {
// #concat
Source<Integer, NotUsed> sourceA = Source.from(Arrays.asList(1, 2, 3, 4));
Source<Integer, NotUsed> sourceB = Source.from(Arrays.asList(10, 20, 30, 40));
sourceA.concatLazy(sourceB).runWith(Sink.foreach(System.out::print), system);
// prints 1, 2, 3, 4, 10, 20, 30, 40
// #concat
}
void interleaveExample() {
// #interleave
Source<Integer, NotUsed> sourceA = Source.from(Arrays.asList(1, 2, 3, 4));