@@ -17,33 +17,134 @@ a separator element.
1717## Proposed solution
1818
1919We 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
2121
2222``` 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+ }
47148}
48149```
49150
@@ -53,83 +154,166 @@ The bulk of the implementation of the new `interspersed` method is inside the ne
53154` AsyncInterspersedSequence ` struct. It constructs an iterator to the base async sequence
54155inside its own iterator. The ` AsyncInterspersedSequence.Iterator.next() ` is forwarding the demand
55156to 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.
58157
59158Below is the implementation of the ` AsyncInterspersedSequence ` .
60159``` swift
61160/// An asynchronous sequence that presents the elements of a base asynchronous sequence of
62161/// elements with a separator between each of those elements.
63162public 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+ }
76169
77- extension AsyncInterspersedSequence : AsyncSequence {
78- public typealias Element = Base . Element
170+ @usableFromInline
171+ internal let base: Base
79172
80- /// The iterator for an `AsyncInterspersedSequence` asynchronous sequence.
81- public struct AsyncIterator : AsyncIteratorProtocol {
82173 @usableFromInline
83- internal enum State {
84- case start
85- case element (Result<Base .Element , Error >)
86- case separator
87- }
174+ internal let separator: Separator
88175
89176 @usableFromInline
90- internal var iterator: Base .AsyncIterator
177+ internal let every: Int
91178
92179 @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+ }
94186
95187 @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+ }
97194
98195 @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
102201 }
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 )
103228
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+ }
127312 }
128- }
129313
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+ }
134318}
135319```
0 commit comments