|
| 1 | +# Joined |
| 2 | + |
| 3 | +* Proposal: [SAA-0004](https://github.com/apple/swift-async-algorithms/blob/main/Evolution/0004-joined.md) |
| 4 | +* Authors: [Philippe Hausler](https://github.com/phausler) |
| 5 | +* Review Manager: [Franz Busch](https://github.com/FranzBusch) |
| 6 | +* Status: **Implemented** |
| 7 | + |
| 8 | +* Implementation: [[Source](https://github.com/apple/swift-async-algorithms/blob/main/Sources/AsyncAlgorithms/AsyncJoinedSequence.swift) | |
| 9 | +[Tests](https://github.com/apple/swift-async-algorithms/blob/main/Tests/AsyncAlgorithmsTests/TestJoin.swift)] |
| 10 | +* Decision Notes: |
| 11 | +* Bugs: |
| 12 | + |
| 13 | +## Introduction |
| 14 | + |
| 15 | +The `joined()` and `joined(separator:)` algorithms on `AsyncSequence`s provide APIs to concatenate an `AsyncSequence` of `AsyncSequence`s. |
| 16 | + |
| 17 | +```swift |
| 18 | +extension AsyncSequence where Element: AsyncSequence { |
| 19 | + public func joined() -> AsyncJoinedSequence<Self> |
| 20 | +} |
| 21 | + |
| 22 | +extension AsyncSequence where Element: AsyncSequence { |
| 23 | + public func joined<Separator: AsyncSequence>(separator: Separator) -> AsyncJoinedBySeparatorSequence<Self, Separator> |
| 24 | +} |
| 25 | +``` |
| 26 | + |
| 27 | +## Detailed Design |
| 28 | + |
| 29 | +These algorithms iterate over the elements of each `AsyncSequence` one bye one, i.e. only after the iteration of one `AsyncSequence` has finished the next one will be started. |
| 30 | + |
| 31 | +```swift |
| 32 | + let appleFeed = URL("http://www.example.com/ticker?symbol=AAPL").lines |
| 33 | + let nasdaqFeed = URL("http://www.example.com/ticker?symbol=^IXIC").lines |
| 34 | + |
| 35 | + for try await line in [appleFeed, nasdaqFeed].async.joined() { |
| 36 | + print("\(line)") |
| 37 | + } |
| 38 | + ``` |
| 39 | + |
| 40 | + Given some sample inputs the following combined events can be expected. |
| 41 | + |
| 42 | + | Timestamp | appleFeed | nasdaqFeed | output | |
| 43 | + | ----------- | --------- | ---------- | ----------------------------- | |
| 44 | + | 11:40 AM | 173.91 | | 173.91 | |
| 45 | + | 12:25 AM | | 14236.78 | | |
| 46 | + | 12:40 AM | | 14218.34 | | |
| 47 | + | 1:15 PM | 173.00 | | 173.00 | |
| 48 | + | 1:15 PM | | | 14236.78 | |
| 49 | + | 1:15 PM | | | 14218.34 | |
| 50 | + |
| 51 | + |
| 52 | +The `joined()` and `joined(separator:)` methods are available on `AsyncSequence`s with elements that are `AsyncSequence`s themselves and produce either an `AsyncJoinedSequence` or an `AsyncJoinedBySeparatorSequence`. |
| 53 | + |
| 54 | +As soon as an inner `AsyncSequence` returns `nil` the algorithm continues with iterating the next inner `AsyncSequence`. |
| 55 | + |
| 56 | +The throwing behaviour of `AsyncJoinedSequence` and `AsyncJoinedBySeparatorSequence` is that if any of the inner `AsyncSequence`s throws, then the composed sequence throws on its iteration. |
| 57 | + |
| 58 | +### Naming |
| 59 | + |
| 60 | +The naming follows to current method naming of the standard library's [`joined`](https://developer.apple.com/documentation/swift/array/joined(separator:)-7uber) method. |
| 61 | +Prior art in the reactive community often names this method `concat`; however, we think that an alignment with the current method on `Sequence` is better. |
| 62 | + |
| 63 | +### Comparison with other libraries |
| 64 | + |
| 65 | +**ReactiveX** ReactiveX has an [API definition of Concat](https://reactivex.io/documentation/operators/concat.html) as a top level function for concatenating Observables. |
| 66 | + |
| 67 | +**Combine** Combine has an [API definition of append](https://developer.apple.com/documentation/combine/publisher/append(_:)-5yh02) which offers similar functionality but limited to concatenating two individual `Publisher`s. |
0 commit comments