Skip to content

Commit 17194ac

Browse files
committed
Integrate review feedback
This integrates all of the feedback from the review thread. Here is a quick summary: - Change the trailing separator behaviour. We are no longer returning a separator before we are forwarding the error - Add a synchronous and asynchronous closure based `interspersed` method. - Support interspersing every n elements
1 parent b339466 commit 17194ac

File tree

3 files changed

+548
-232
lines changed

3 files changed

+548
-232
lines changed

Evolution/0011-interspersed.md

Lines changed: 216 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -17,33 +17,84 @@ a separator element.
1717
## Proposed solution
1818

1919
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
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+
}
4798
}
4899
```
49100

@@ -53,83 +104,166 @@ The bulk of the implementation of the new `interspersed` method is inside the ne
53104
`AsyncInterspersedSequence` struct. It constructs an iterator to the base async sequence
54105
inside its own iterator. The `AsyncInterspersedSequence.Iterator.next()` is forwarding the demand
55106
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.
58107

59108
Below is the implementation of the `AsyncInterspersedSequence`.
60109
```swift
61110
/// An asynchronous sequence that presents the elements of a base asynchronous sequence of
62111
/// elements with a separator between each of those elements.
63112
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+
}
76119

77-
extension AsyncInterspersedSequence: AsyncSequence {
78-
public typealias Element = Base.Element
120+
@usableFromInline
121+
internal let base: Base
79122

80-
/// The iterator for an `AsyncInterspersedSequence` asynchronous sequence.
81-
public struct AsyncIterator: AsyncIteratorProtocol {
82123
@usableFromInline
83-
internal enum State {
84-
case start
85-
case element(Result<Base.Element, Error>)
86-
case separator
87-
}
124+
internal let separator: Separator
88125

89126
@usableFromInline
90-
internal var iterator: Base.AsyncIterator
127+
internal let every: Int
91128

92129
@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+
}
94136

95137
@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+
}
97144

98145
@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
102151
}
152+
}
103153

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+
}
127262
}
128-
}
129263

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+
}
134268
}
135269
```

0 commit comments

Comments
 (0)