@@ -17,33 +17,134 @@ 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
+ }
98
+
99
+ /// Returns a new asynchronous sequence containing the elements of this asynchronous sequence, inserting
100
+ /// the given separator between each element.
101
+ ///
102
+ /// Any value of this asynchronous sequence's element type can be used as the separator.
103
+ ///
104
+ /// The following example shows how an async sequences of `String`s can be interspersed using `-` as the separator:
105
+ ///
106
+ /// ```
107
+ /// let input = ["A", "B", "C"].async
108
+ /// let interspersed = input.interspersed(with: "-")
109
+ /// for await element in interspersed {
110
+ /// print(element)
111
+ /// }
112
+ /// // Prints "A" "-" "B" "-" "C"
113
+ /// ```
114
+ ///
115
+ /// - Parameters:
116
+ /// - every: Dictates after how many elements a separator should be inserted.
117
+ /// - separator: A closure that produces the value to insert in between each of this async sequence’s elements.
118
+ /// - Returns: The interspersed asynchronous sequence of elements.
119
+ @inlinable
120
+ public func interspersed (every : Int = 1 , with separator : @Sendable @escaping () throws -> Element ) -> AsyncThrowingInterspersedSequence<Self > {
121
+ AsyncThrowingInterspersedSequence (self , every : every, separator : separator)
122
+ }
123
+
124
+ /// Returns a new asynchronous sequence containing the elements of this asynchronous sequence, inserting
125
+ /// the given separator between each element.
126
+ ///
127
+ /// Any value of this asynchronous sequence's element type can be used as the separator.
128
+ ///
129
+ /// The following example shows how an async sequences of `String`s can be interspersed using `-` as the separator:
130
+ ///
131
+ /// ```
132
+ /// let input = ["A", "B", "C"].async
133
+ /// let interspersed = input.interspersed(with: "-")
134
+ /// for await element in interspersed {
135
+ /// print(element)
136
+ /// }
137
+ /// // Prints "A" "-" "B" "-" "C"
138
+ /// ```
139
+ ///
140
+ /// - Parameters:
141
+ /// - every: Dictates after how many elements a separator should be inserted.
142
+ /// - separator: A closure that produces the value to insert in between each of this async sequence’s elements.
143
+ /// - Returns: The interspersed asynchronous sequence of elements.
144
+ @inlinable
145
+ public func interspersed (every : Int = 1 , with separator : @Sendable @escaping () async throws -> Element ) -> AsyncThrowingInterspersedSequence<Self > {
146
+ AsyncThrowingInterspersedSequence (self , every : every, separator : separator)
147
+ }
47
148
}
48
149
```
49
150
@@ -53,83 +154,166 @@ The bulk of the implementation of the new `interspersed` method is inside the ne
53
154
` AsyncInterspersedSequence ` struct. It constructs an iterator to the base async sequence
54
155
inside its own iterator. The ` AsyncInterspersedSequence.Iterator.next() ` is forwarding the demand
55
156
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
157
59
158
Below is the implementation of the ` AsyncInterspersedSequence ` .
60
159
``` swift
61
160
/// An asynchronous sequence that presents the elements of a base asynchronous sequence of
62
161
/// elements with a separator between each of those elements.
63
162
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
- }
163
+ @usableFromInline
164
+ internal enum Separator {
165
+ case element (Element )
166
+ case syncClosure (@Sendable () -> Element )
167
+ case asyncClosure (@Sendable () async -> Element )
168
+ }
76
169
77
- extension AsyncInterspersedSequence : AsyncSequence {
78
- public typealias Element = Base . Element
170
+ @usableFromInline
171
+ internal let base: Base
79
172
80
- /// The iterator for an `AsyncInterspersedSequence` asynchronous sequence.
81
- public struct AsyncIterator : AsyncIteratorProtocol {
82
173
@usableFromInline
83
- internal enum State {
84
- case start
85
- case element (Result<Base .Element , Error >)
86
- case separator
87
- }
174
+ internal let separator: Separator
88
175
89
176
@usableFromInline
90
- internal var iterator: Base .AsyncIterator
177
+ internal let every: Int
91
178
92
179
@usableFromInline
93
- internal let separator: Base .Element
180
+ internal init (_ base : Base , every : Int , separator : Element ) {
181
+ precondition (every > 0 , " Separators can only be interspersed ever 1+ elements" )
182
+ self .base = base
183
+ self .separator = .element (separator)
184
+ self .every = every
185
+ }
94
186
95
187
@usableFromInline
96
- internal var state = State.start
188
+ internal init (_ base : Base , every : Int , separator : @Sendable @escaping () -> Element ) {
189
+ precondition (every > 0 , " Separators can only be interspersed ever 1+ elements" )
190
+ self .base = base
191
+ self .separator = .syncClosure (separator)
192
+ self .every = every
193
+ }
97
194
98
195
@usableFromInline
99
- internal init (_ iterator : Base .AsyncIterator, separator : Base .Element ) {
100
- self .iterator = iterator
101
- self .separator = separator
196
+ internal init (_ base : Base , every : Int , separator : @Sendable @escaping () async -> Element ) {
197
+ precondition (every > 0 , " Separators can only be interspersed ever 1+ elements" )
198
+ self .base = base
199
+ self .separator = .asyncClosure (separator)
200
+ self .every = every
102
201
}
202
+ }
203
+
204
+ extension AsyncInterspersedSequence : AsyncSequence {
205
+ public typealias Element = Base .Element
206
+
207
+ /// The iterator for an `AsyncInterspersedSequence` asynchronous sequence.
208
+ public struct Iterator : AsyncIteratorProtocol {
209
+ @usableFromInline
210
+ internal enum State {
211
+ case start (Element ? )
212
+ case element (Int )
213
+ case separator
214
+ case finished
215
+ }
216
+
217
+ @usableFromInline
218
+ internal var iterator: Base .AsyncIterator
219
+
220
+ @usableFromInline
221
+ internal let separator: Separator
222
+
223
+ @usableFromInline
224
+ internal let every: Int
225
+
226
+ @usableFromInline
227
+ internal var state = State.start (nil )
103
228
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
- }
229
+ @usableFromInline
230
+ internal init (_ iterator : Base .AsyncIterator, every : Int , separator : Separator) {
231
+ self .iterator = iterator
232
+ self .separator = separator
233
+ self .every = every
234
+ }
235
+
236
+ public mutating func next () async rethrows -> Base .Element ? {
237
+ // After the start, the state flips between element and separator. Before
238
+ // returning a separator, a check is made for the next element as a
239
+ // separator is only returned between two elements. The next element is
240
+ // stored to allow it to be returned in the next iteration. However, if
241
+ // the checking the next element throws, the separator is emitted before
242
+ // rethrowing that error.
243
+ switch state {
244
+ case var .start (element):
245
+ do {
246
+ if element == nil {
247
+ element = try await self .iterator .next ()
248
+ }
249
+
250
+ if let element = element {
251
+ if every == 1 {
252
+ state = .separator
253
+ } else {
254
+ state = .element (1 )
255
+ }
256
+ return element
257
+ } else {
258
+ state = .finished
259
+ return nil
260
+ }
261
+ } catch {
262
+ state = .finished
263
+ throw error
264
+ }
265
+
266
+ case .separator :
267
+ do {
268
+ if let element = try await iterator.next () {
269
+ state = .start (element)
270
+ switch separator {
271
+ case let .element (element):
272
+ return element
273
+
274
+ case let .syncClosure (closure):
275
+ return closure ()
276
+
277
+ case let .asyncClosure (closure):
278
+ return await closure ()
279
+ }
280
+ } else {
281
+ state = .finished
282
+ return nil
283
+ }
284
+ } catch {
285
+ state = .finished
286
+ throw error
287
+ }
288
+
289
+ case let .element (count):
290
+ do {
291
+ if let element = try await iterator.next () {
292
+ let newCount = count + 1
293
+ if every == newCount {
294
+ state = .separator
295
+ } else {
296
+ state = .element (newCount)
297
+ }
298
+ return element
299
+ } else {
300
+ state = .finished
301
+ return nil
302
+ }
303
+ } catch {
304
+ state = .finished
305
+ throw error
306
+ }
307
+
308
+ case .finished :
309
+ return nil
310
+ }
311
+ }
127
312
}
128
- }
129
313
130
- @inlinable
131
- public func makeAsyncIterator () -> AsyncInterspersedSequence<Base >.AsyncIterator {
132
- AsyncIterator (base.makeAsyncIterator (), separator : separator)
133
- }
314
+ @inlinable
315
+ public func makeAsyncIterator () -> AsyncInterspersedSequence<Base >.Iterator {
316
+ Iterator (base.makeAsyncIterator (), every : every , separator : separator)
317
+ }
134
318
}
135
319
```
0 commit comments