Skip to content

Commit 356241f

Browse files
committed
Fleshing out of Joined
1 parent 6ad19c5 commit 356241f

File tree

1 file changed

+73
-7
lines changed

1 file changed

+73
-7
lines changed

Guides/Joined.md

Lines changed: 73 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,20 +9,72 @@
99

1010
## Introduction
1111

12+
Concatenates an asynchronous sequence of asynchronous sequences that share an `Element` type together sequentially where the elements from the resulting asynchronous sequence are comprised in order from the elements of the first asynchronous sequence and then the second (and so on) or until an error occurs. Similar to `chain()`, except the number of asynchronous sequences to concatenate is not known up front.
13+
14+
Optionally allows inserting the elements of a separator asynchronous sequence in between each of the other sequences.
15+
16+
```swift
17+
let sequenceOfURLs: AsyncSequence<URL> = ...
18+
let sequenceOfLines = sequenceOfURLs.map { $0.lines }
19+
let joinedWithSeparator = sequenceOfLines.joined(separator: ["===================="].async)
20+
21+
for try await lineOrSeparator in joinedWithSeparator {
22+
print(lineOrSeparator)
23+
}
24+
```
25+
26+
This example shows how an `AsyncSequence` of `URL`s can be turned into an `AsyncSequence` of the lines of each of those files in sequence, with a separator line in between each file.
27+
1228
## Proposed Solution
1329

1430
```swift
1531
extension AsyncSequence where Element: AsyncSequence {
16-
public func joined<Separator: AsyncSequence>(
17-
separator: Separator
18-
) -> AsyncJoinedSequence<Self, Separator>
32+
public func joined() -> AsyncJoinedSequence<Self> {
33+
return AsyncJoinedSequence(self)
34+
}
35+
}
36+
```
37+
38+
```swift
39+
extension AsyncSequence where Element: AsyncSequence {
40+
public func joined<Separator: AsyncSequence>(separator: Separator) -> AsyncJoinedBySeparatorSequence<Self, Separator> {
41+
return AsyncJoinedBySeparatorSequence(self, separator: separator)
42+
}
1943
}
2044
```
2145

2246
## Detailed Design
2347

2448
```swift
25-
public struct AsyncJoinedSequence<Base: AsyncSequence, Separator: AsyncSequence>: AsyncSequence
49+
public struct AsyncJoinedSequence<Base: AsyncSequence>: AsyncSequence where Base.Element: AsyncSequence {
50+
public typealias Element = Base.Element.Element
51+
52+
public struct Iterator: AsyncIteratorProtocol {
53+
public mutating func next() async rethrows -> Base.Element.Element?
54+
}
55+
56+
public func makeAsyncIterator() -> Iterator
57+
}
58+
59+
extension AsyncJoinedSequence: Sendable
60+
where
61+
Base: Sendable,
62+
Base.Element: Sendable,
63+
Base.Element.Element: Sendable,
64+
Base.AsyncIterator: Sendable,
65+
Base.Element.AsyncIterator: Sendable { }
66+
67+
extension AsyncJoinedSequence.Iterator: Sendable
68+
where
69+
Base: Sendable,
70+
Base.Element: Sendable,
71+
Base.Element.Element: Sendable,
72+
Base.AsyncIterator: Sendable,
73+
Base.Element.AsyncIterator: Sendable { }
74+
```
75+
76+
```swift
77+
public struct AsyncJoinedBySeparatorSequence<Base: AsyncSequence, Separator: AsyncSequence>: AsyncSequence
2678
where Base.Element: AsyncSequence, Separator.Element == Base.Element.Element {
2779
public typealias Element = Base.Element.Element
2880

@@ -33,7 +85,7 @@ public struct AsyncJoinedSequence<Base: AsyncSequence, Separator: AsyncSequence>
3385
public func makeAsyncIterator() -> Iterator
3486
}
3587

36-
extension AsyncJoinedSequence: Sendable
88+
extension AsyncJoinedBySeparatorSequence: Sendable
3789
where
3890
Base: Sendable,
3991
Base.Element: Sendable,
@@ -43,7 +95,7 @@ extension AsyncJoinedSequence: Sendable
4395
Separator.AsyncIterator: Sendable,
4496
Base.Element.AsyncIterator: Sendable { }
4597

46-
extension AsyncJoinedSequence.Iterator: Sendable
98+
extension AsyncJoinedBySeparatorSequence.Iterator: Sendable
4799
where
48100
Base: Sendable,
49101
Base.Element: Sendable,
@@ -54,8 +106,22 @@ extension AsyncJoinedSequence.Iterator: Sendable
54106
Base.Element.AsyncIterator: Sendable { }
55107
```
56108

109+
The resulting `AsyncJoinedSequence` or `AsyncJoinedBySeparatorSequence` type is an asynchronous sequence, with conditional conformance to `Sendable` when the arguments conform.
110+
111+
When any of the asynchronous sequences being joined together come to their end of iteration, the `Joined` sequence iteration proceeds to the separator asynchronous sequence, if any. When the separator asynchronous sequence terminates, or if no sepaerator was specified, it proceeds on to the next asynchronous sequence. When the last asynchronous sequence reaches the end of iteration the `AsyncJoinedSequence` or `AsyncJoinedBySeparatorSequence` then ends its iteration. At any point in time if one of the comprising asynchronous sequences ever throws an error during iteration the `AsyncJoinedSequence` or `AsyncJoinedBySeparatorSequence` iteration will throw that error and end iteration.
112+
113+
## Future Directions
114+
115+
The Swift Algorithms package has [additional synchronous variants of `joined()`](https://github.com/apple/swift-algorithms/blob/main/Guides/Joined.md). It is conceivable to bring asynchronous variants of those over to `AsyncSequence`.
116+
117+
The variant that takes a single element as a separator is straightforward, but can be trivially replicated with `[element].async`. However, it may be beneficial for performance to reimplement this directly.
118+
119+
There is another variant that takes a closure that allows one to customize the separator based on the return value of a closure. That closure is passed each of the two consecutive asynchronous sequences (not the two neighboring elements in consecutive sequences). This variant arguably has the greatest utility when the sequence type conforms to `Collection`, allowing either the `count` or any arbitrary element to be obtained directly. With `AsyncSequence`, it is less likely that this function with provide a similar level of utility, so it has been omitted.
120+
57121
## Alternatives Considered
58122

123+
Because `joined()` is essentially identical to `flatMap { $0 }`, it was considered that this should be called `flatten()` instead. However, it is preferable to follow the lead of the Swift standard library's `joined()` method.
124+
59125
## Credits/Inspiration
60126

61-
The Swift standard library has a [function on synchronous sequences](https://developer.apple.com/documentation/swift/sequence/1641166-joined) that performs a similar (but synchronous) task.
127+
The Swift standard library has functions on synchronous sequences ([`joined()`](https://developer.apple.com/documentation/swift/sequence/1641166-joined), [`joined(separator:)`](https://developer.apple.com/documentation/swift/sequence/2431985-joined)) that perform similar (but synchronous) tasks.

0 commit comments

Comments
 (0)