@@ -17,33 +17,84 @@ a separator element.
17
17
## Proposed solution
18
18
19
19
We propose to add a new method on ` AsyncSequence ` that allows to intersperse
20
- a separator between each emitted element. This proposed API looks like this
20
+ a separator between every n emitted element. This proposed API looks like this
21
21
22
22
``` swift
23
- extension AsyncSequence {
24
- /// Returns a new asynchronous sequence containing the elements of this asynchronous sequence, inserting
25
- /// the given separator between each element.
26
- ///
27
- /// Any value of this asynchronous sequence's element type can be used as the separator.
28
- ///
29
- /// The following example shows how an async sequences of `String`s can be interspersed using `-` as the separator:
30
- ///
31
- /// ```
32
- /// let input = ["A", "B", "C"].async
33
- /// let interspersed = input.interspersed(with: "-")
34
- /// for await element in interspersed {
35
- /// print(element)
36
- /// }
37
- /// // Prints "A" "-" "B" "-" "C"
38
- /// ```
39
- ///
40
- /// - Parameter separator: The value to insert in between each of this async
41
- /// sequence’s elements.
42
- /// - Returns: The interspersed asynchronous sequence of elements.
43
- @inlinable
44
- public func interspersed (with separator : Element ) -> AsyncInterspersedSequence<Self > {
45
- AsyncInterspersedSequence (self , separator : separator)
46
- }
23
+ public extension AsyncSequence {
24
+ /// Returns a new asynchronous sequence containing the elements of this asynchronous sequence, inserting
25
+ /// the given separator between each element.
26
+ ///
27
+ /// Any value of this asynchronous sequence's element type can be used as the separator.
28
+ ///
29
+ /// The following example shows how an async sequences of `String`s can be interspersed using `-` as the separator:
30
+ ///
31
+ /// ```
32
+ /// let input = ["A", "B", "C"].async
33
+ /// let interspersed = input.interspersed(with: "-")
34
+ /// for await element in interspersed {
35
+ /// print(element)
36
+ /// }
37
+ /// // Prints "A" "-" "B" "-" "C"
38
+ /// ```
39
+ ///
40
+ /// - Parameters:
41
+ /// - every: Dictates after how many elements a separator should be inserted.
42
+ /// - separator: The value to insert in between each of this async sequence’s elements.
43
+ /// - Returns: The interspersed asynchronous sequence of elements.
44
+ @inlinable
45
+ func interspersed (every : Int = 1 , with separator : Element ) -> AsyncInterspersedSequence<Self > {
46
+ AsyncInterspersedSequence (self , every : every, separator : separator)
47
+ }
48
+
49
+ /// Returns a new asynchronous sequence containing the elements of this asynchronous sequence, inserting
50
+ /// the given separator between each element.
51
+ ///
52
+ /// Any value of this asynchronous sequence's element type can be used as the separator.
53
+ ///
54
+ /// The following example shows how an async sequences of `String`s can be interspersed using `-` as the separator:
55
+ ///
56
+ /// ```
57
+ /// let input = ["A", "B", "C"].async
58
+ /// let interspersed = input.interspersed(with: "-")
59
+ /// for await element in interspersed {
60
+ /// print(element)
61
+ /// }
62
+ /// // Prints "A" "-" "B" "-" "C"
63
+ /// ```
64
+ ///
65
+ /// - Parameters:
66
+ /// - every: Dictates after how many elements a separator should be inserted.
67
+ /// - separator: A closure that produces the value to insert in between each of this async sequence’s elements.
68
+ /// - Returns: The interspersed asynchronous sequence of elements.
69
+ @inlinable
70
+ func interspersed (every : Int = 1 , with separator : @Sendable @escaping () -> Element ) -> AsyncInterspersedSequence<Self > {
71
+ AsyncInterspersedSequence (self , every : every, separator : separator)
72
+ }
73
+
74
+ /// Returns a new asynchronous sequence containing the elements of this asynchronous sequence, inserting
75
+ /// the given separator between each element.
76
+ ///
77
+ /// Any value of this asynchronous sequence's element type can be used as the separator.
78
+ ///
79
+ /// The following example shows how an async sequences of `String`s can be interspersed using `-` as the separator:
80
+ ///
81
+ /// ```
82
+ /// let input = ["A", "B", "C"].async
83
+ /// let interspersed = input.interspersed(with: "-")
84
+ /// for await element in interspersed {
85
+ /// print(element)
86
+ /// }
87
+ /// // Prints "A" "-" "B" "-" "C"
88
+ /// ```
89
+ ///
90
+ /// - Parameters:
91
+ /// - every: Dictates after how many elements a separator should be inserted.
92
+ /// - separator: A closure that produces the value to insert in between each of this async sequence’s elements.
93
+ /// - Returns: The interspersed asynchronous sequence of elements.
94
+ @inlinable
95
+ func interspersed (every : Int = 1 , with separator : @Sendable @escaping () async -> Element ) -> AsyncInterspersedSequence<Self > {
96
+ AsyncInterspersedSequence (self , every : every, separator : separator)
97
+ }
47
98
}
48
99
```
49
100
@@ -53,83 +104,166 @@ The bulk of the implementation of the new `interspersed` method is inside the ne
53
104
` AsyncInterspersedSequence ` struct. It constructs an iterator to the base async sequence
54
105
inside its own iterator. The ` AsyncInterspersedSequence.Iterator.next() ` is forwarding the demand
55
106
to the base iterator.
56
- There is one special case that we have to call out. When the base async sequence throws
57
- then ` AsyncInterspersedSequence.Iterator.next() ` will return the separator first and then rethrow the error.
58
107
59
108
Below is the implementation of the ` AsyncInterspersedSequence ` .
60
109
``` swift
61
110
/// An asynchronous sequence that presents the elements of a base asynchronous sequence of
62
111
/// elements with a separator between each of those elements.
63
112
public struct AsyncInterspersedSequence <Base : AsyncSequence > {
64
- @usableFromInline
65
- internal let base: Base
66
-
67
- @usableFromInline
68
- internal let separator: Base .Element
69
-
70
- @usableFromInline
71
- internal init (_ base : Base , separator : Base .Element ) {
72
- self .base = base
73
- self .separator = separator
74
- }
75
- }
113
+ @usableFromInline
114
+ internal enum Separator {
115
+ case element (Element )
116
+ case syncClosure (@Sendable () -> Element )
117
+ case asyncClosure (@Sendable () async -> Element )
118
+ }
76
119
77
- extension AsyncInterspersedSequence : AsyncSequence {
78
- public typealias Element = Base . Element
120
+ @usableFromInline
121
+ internal let base: Base
79
122
80
- /// The iterator for an `AsyncInterspersedSequence` asynchronous sequence.
81
- public struct AsyncIterator : AsyncIteratorProtocol {
82
123
@usableFromInline
83
- internal enum State {
84
- case start
85
- case element (Result<Base .Element , Error >)
86
- case separator
87
- }
124
+ internal let separator: Separator
88
125
89
126
@usableFromInline
90
- internal var iterator: Base .AsyncIterator
127
+ internal let every: Int
91
128
92
129
@usableFromInline
93
- internal let separator: Base .Element
130
+ internal init (_ base : Base , every : Int , separator : Element ) {
131
+ precondition (every > 0 , " Separators can only be interspersed ever 1+ elements" )
132
+ self .base = base
133
+ self .separator = .element (separator)
134
+ self .every = every
135
+ }
94
136
95
137
@usableFromInline
96
- internal var state = State.start
138
+ internal init (_ base : Base , every : Int , separator : @Sendable @escaping () -> Element ) {
139
+ precondition (every > 0 , " Separators can only be interspersed ever 1+ elements" )
140
+ self .base = base
141
+ self .separator = .syncClosure (separator)
142
+ self .every = every
143
+ }
97
144
98
145
@usableFromInline
99
- internal init (_ iterator : Base .AsyncIterator, separator : Base .Element ) {
100
- self .iterator = iterator
101
- self .separator = separator
146
+ internal init (_ base : Base , every : Int , separator : @Sendable @escaping () async -> Element ) {
147
+ precondition (every > 0 , " Separators can only be interspersed ever 1+ elements" )
148
+ self .base = base
149
+ self .separator = .asyncClosure (separator)
150
+ self .every = every
102
151
}
152
+ }
103
153
104
- public mutating func next () async rethrows -> Base .Element ? {
105
- // After the start, the state flips between element and separator. Before
106
- // returning a separator, a check is made for the next element as a
107
- // separator is only returned between two elements. The next element is
108
- // stored to allow it to be returned in the next iteration. However, if
109
- // the checking the next element throws, the separator is emitted before
110
- // rethrowing that error.
111
- switch state {
112
- case .start :
113
- state = .separator
114
- return try await iterator.next ()
115
- case .separator :
116
- do {
117
- guard let next = try await iterator.next () else { return nil }
118
- state = .element (.success (next))
119
- } catch {
120
- state = .element (.failure (error))
121
- }
122
- return separator
123
- case .element (let result):
124
- state = .separator
125
- return try result._rethrowGet ()
126
- }
154
+ extension AsyncInterspersedSequence : AsyncSequence {
155
+ public typealias Element = Base .Element
156
+
157
+ /// The iterator for an `AsyncInterspersedSequence` asynchronous sequence.
158
+ public struct Iterator : AsyncIteratorProtocol {
159
+ @usableFromInline
160
+ internal enum State {
161
+ case start (Element ? )
162
+ case element (Int )
163
+ case separator
164
+ case finished
165
+ }
166
+
167
+ @usableFromInline
168
+ internal var iterator: Base .AsyncIterator
169
+
170
+ @usableFromInline
171
+ internal let separator: Separator
172
+
173
+ @usableFromInline
174
+ internal let every: Int
175
+
176
+ @usableFromInline
177
+ internal var state = State.start (nil )
178
+
179
+ @usableFromInline
180
+ internal init (_ iterator : Base .AsyncIterator, every : Int , separator : Separator) {
181
+ self .iterator = iterator
182
+ self .separator = separator
183
+ self .every = every
184
+ }
185
+
186
+ public mutating func next () async rethrows -> Base .Element ? {
187
+ // After the start, the state flips between element and separator. Before
188
+ // returning a separator, a check is made for the next element as a
189
+ // separator is only returned between two elements. The next element is
190
+ // stored to allow it to be returned in the next iteration. However, if
191
+ // the checking the next element throws, the separator is emitted before
192
+ // rethrowing that error.
193
+ switch state {
194
+ case var .start (element):
195
+ do {
196
+ if element == nil {
197
+ element = try await self .iterator .next ()
198
+ }
199
+
200
+ if let element = element {
201
+ if every == 1 {
202
+ state = .separator
203
+ } else {
204
+ state = .element (1 )
205
+ }
206
+ return element
207
+ } else {
208
+ state = .finished
209
+ return nil
210
+ }
211
+ } catch {
212
+ state = .finished
213
+ throw error
214
+ }
215
+
216
+ case .separator :
217
+ do {
218
+ if let element = try await iterator.next () {
219
+ state = .start (element)
220
+ switch separator {
221
+ case let .element (element):
222
+ return element
223
+
224
+ case let .syncClosure (closure):
225
+ return closure ()
226
+
227
+ case let .asyncClosure (closure):
228
+ return await closure ()
229
+ }
230
+ } else {
231
+ state = .finished
232
+ return nil
233
+ }
234
+ } catch {
235
+ state = .finished
236
+ throw error
237
+ }
238
+
239
+ case let .element (count):
240
+ do {
241
+ if let element = try await iterator.next () {
242
+ let newCount = count + 1
243
+ if every == newCount {
244
+ state = .separator
245
+ } else {
246
+ state = .element (newCount)
247
+ }
248
+ return element
249
+ } else {
250
+ state = .finished
251
+ return nil
252
+ }
253
+ } catch {
254
+ state = .finished
255
+ throw error
256
+ }
257
+
258
+ case .finished :
259
+ return nil
260
+ }
261
+ }
127
262
}
128
- }
129
263
130
- @inlinable
131
- public func makeAsyncIterator () -> AsyncInterspersedSequence<Base >.AsyncIterator {
132
- AsyncIterator (base.makeAsyncIterator (), separator : separator)
133
- }
264
+ @inlinable
265
+ public func makeAsyncIterator () -> AsyncInterspersedSequence<Base >.Iterator {
266
+ Iterator (base.makeAsyncIterator (), every : every , separator : separator)
267
+ }
134
268
}
135
269
```
0 commit comments