Skip to content

Commit bef63de

Browse files
Added iterator map functionality
1 parent 3b5c306 commit bef63de

File tree

2 files changed

+411
-0
lines changed

2 files changed

+411
-0
lines changed
Lines changed: 210 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,210 @@
1+
//
2+
// AsyncIteratorMapSequence.swift
3+
// AsyncSequenceReader
4+
//
5+
// Created by Dimitri Bouniol on 2021-11-17.
6+
// Copyright © 2021 Mochi Development, Inc. All rights reserved.
7+
//
8+
9+
#if compiler(>=5.5) && canImport(_Concurrency)
10+
extension AsyncSequence {
11+
12+
/// Creates an asynchronous sequence that maps the given closure over an iterator for the sequence, which can itself accept multiple reads.
13+
///
14+
/// When finished reading from the iterator, return your completed object, and the closure will be called again with an terator configured to continue where the first one finished.
15+
///
16+
/// In this example, an asynchronous sequence of Strings encodes sentences by prefixing each word sequence with a number.
17+
/// The number indicates how many words will be read and concatenated into a complete sentence.
18+
///
19+
/// The closure provided to the `iteratorMap(_:)` first reads the first available string, interpreting it as a number.
20+
/// Then, it will loop the specified number of times, accumulating those words into an array, that is finally assembled into a sentence.
21+
///
22+
/// let dataStream = ... // "2", "Hello,", "World!", "4", "My", "name", "is", "Dimitri.", "0", "1", "Bye!"
23+
///
24+
/// let sentenceStream = dataStream.iteratorMap { iterator -> String? in
25+
/// var count = Int(try await iterator.next() ?? "")!
26+
///
27+
/// var results: [String] = []
28+
///
29+
/// while count > 0, let next = try await iterator.next() {
30+
/// results.append(next)
31+
/// count -= 1
32+
/// }
33+
///
34+
/// return results.joined(separator: " ")
35+
/// }
36+
///
37+
/// for await sentence in sentenceStream {
38+
/// print("\"\(sentence)\"", terminator: ", ")
39+
/// }
40+
/// // Prints: "Hello, World!", "My name is Dimitri.", "", "Bye!"
41+
///
42+
/// - Parameter transform: A mapping closure. `transform` accepts an iterator representing the original sequence as its parameter and returns a transformed value. Returning `nil` will stop the sequence early.
43+
/// - Returns: An asynchronous sequence that contains, in order, elements produced by the `transform` closure.
44+
@inlinable
45+
public func iteratorMap<Transformed>(_ transform: @escaping (_ iterator: inout AsyncBufferedIterator<AsyncIterator>) async -> Transformed) -> AsyncIteratorMapSequence<Self, Transformed> {
46+
AsyncIteratorMapSequence(self, transform: transform)
47+
}
48+
49+
/// Creates an asynchronous sequence that maps the given closure over an iterator for the sequence, which can itself accept multiple reads.
50+
///
51+
/// When finished reading from the iterator, return your completed object, and the closure will be called again with an terator configured to continue where the first one finished.
52+
///
53+
/// In this example, an asynchronous sequence of Strings encodes sentences by prefixing each word sequence with a number.
54+
/// The number indicates how many words will be read and concatenated into a complete sentence.
55+
///
56+
/// The closure provided to the `iteratorMap(_:)` first reads the first available string, interpreting it as a number.
57+
/// Then, it will loop the specified number of times, accumulating those words into an array, that is finally assembled into a sentence.
58+
///
59+
/// let dataStream = ... // "2", "Hello,", "World!", "4", "My", "name", "is", "Dimitri.", "0", "1", "Bye!"
60+
///
61+
/// let sentenceStream = dataStream.iteratorMap { iterator -> String? in
62+
/// guard var count = Int(try await iterator.next() ?? "") else {
63+
/// throw SentenceParsing.invalidWordCount
64+
/// }
65+
///
66+
/// var results: [String] = []
67+
///
68+
/// while count > 0, let next = try await iterator.next() {
69+
/// results.append(next)
70+
/// count -= 1
71+
/// }
72+
///
73+
/// guard count == 0 else {
74+
/// throw SentenceParsing.missingFinalWords
75+
/// }
76+
///
77+
/// return results.joined(separator: " ")
78+
/// }
79+
///
80+
/// for try await sentence in sentenceStream {
81+
/// print("\"\(sentence)\"", terminator: ", ")
82+
/// }
83+
/// // Prints: "Hello, World!", "My name is Dimitri.", "", "Bye!"
84+
///
85+
/// - Parameter transform: A mapping closure. `transform` accepts an iterator representing the original sequence as its parameter and returns a transformed value. Returning `nil` will stop the sequence early, as will throwing an error.
86+
/// - Returns: An asynchronous sequence that contains, in order, elements produced by the `transform` closure.
87+
@inlinable
88+
public func iteratorMap<Transformed>(_ transform: @escaping (_ iterator: inout AsyncBufferedIterator<AsyncIterator>) async throws -> Transformed) -> AsyncThrowingIteratorMapSequence<Self, Transformed> {
89+
AsyncThrowingIteratorMapSequence(self, transform: transform)
90+
}
91+
}
92+
93+
/// An asynchronous sequence that maps the given closure over the asynchronous sequence’s elements by providing it with the base sequence's iterator to assemble multiple reads into a single transformed object.
94+
public struct AsyncIteratorMapSequence<Base : AsyncSequence, Transformed> {
95+
@usableFromInline
96+
let base: Base
97+
98+
@usableFromInline
99+
let transform: (_ iterator: inout AsyncBufferedIterator<Base.AsyncIterator>) async -> Transformed
100+
101+
@usableFromInline
102+
init(
103+
_ base: Base,
104+
transform: @escaping (_ iterator: inout AsyncBufferedIterator<Base.AsyncIterator>) async -> Transformed
105+
) {
106+
self.base = base
107+
self.transform = transform
108+
}
109+
}
110+
111+
extension AsyncIteratorMapSequence: AsyncSequence {
112+
/// The type of element produced by this asynchronous sequence.
113+
///
114+
/// The map sequence produces whatever type of element its transforming closure produces.
115+
public typealias Element = Transformed
116+
117+
/// The iterator that produces elements of the map sequence.
118+
public struct AsyncIterator: AsyncIteratorProtocol {
119+
@usableFromInline
120+
var baseIterator: AsyncBufferedIterator<Base.AsyncIterator>
121+
@usableFromInline
122+
let transform: (_ iterator: inout AsyncBufferedIterator<Base.AsyncIterator>) async -> Transformed
123+
124+
@usableFromInline
125+
init(
126+
_ baseIterator: AsyncBufferedIterator<Base.AsyncIterator>,
127+
transform: @escaping (inout AsyncBufferedIterator<Base.AsyncIterator>) async -> Transformed
128+
) {
129+
self.baseIterator = baseIterator
130+
self.transform = transform
131+
}
132+
133+
/// Produces the next element in the map sequence.
134+
///
135+
/// This iterator calls `next()` on its (wrapped) base iterator, and stores the result; if this call returns `nil`, `next()` returns `nil`. Otherwise, `next()` returns the result of calling the transforming closure on the received element.
136+
@inlinable
137+
public mutating func next() async rethrows -> Transformed? {
138+
guard try await baseIterator.hasMoreData() else {
139+
return nil
140+
}
141+
return await transform(&baseIterator)
142+
}
143+
}
144+
145+
@inlinable
146+
public func makeAsyncIterator() -> AsyncIterator {
147+
AsyncIterator(AsyncBufferedIterator(base.makeAsyncIterator()), transform: transform)
148+
}
149+
}
150+
151+
public struct AsyncThrowingIteratorMapSequence<Base, Transformed> where Base : AsyncSequence {
152+
@usableFromInline
153+
let base: Base
154+
155+
@usableFromInline
156+
let transform: (_ iterator: inout AsyncBufferedIterator<Base.AsyncIterator>) async throws -> Transformed
157+
158+
@usableFromInline
159+
init(
160+
_ base: Base,
161+
transform: @escaping (_ iterator: inout AsyncBufferedIterator<Base.AsyncIterator>) async throws -> Transformed
162+
) {
163+
self.base = base
164+
self.transform = transform
165+
}
166+
}
167+
168+
extension AsyncThrowingIteratorMapSequence: AsyncSequence {
169+
public typealias Element = Transformed
170+
171+
public struct AsyncIterator: AsyncIteratorProtocol {
172+
@usableFromInline
173+
var baseIterator: AsyncBufferedIterator<Base.AsyncIterator>
174+
175+
@usableFromInline
176+
let transform: (_ iterator: inout AsyncBufferedIterator<Base.AsyncIterator>) async throws -> Transformed
177+
178+
@usableFromInline
179+
var encounteredError = false
180+
181+
@usableFromInline
182+
init(_ baseIterator: AsyncBufferedIterator<Base.AsyncIterator>, transform: @escaping (inout AsyncBufferedIterator<Base.AsyncIterator>) async throws -> Transformed) {
183+
self.baseIterator = baseIterator
184+
self.transform = transform
185+
}
186+
187+
/// Produces the next element in the map sequence.
188+
///
189+
/// This iterator calls `next()` on its (wrapped) base iterator, and stores the result; if this call returns `nil`, `next()` returns `nil`. Otherwise, `next()` returns the result of calling the transforming closure on the received element. If calling the closure throws an error, the sequence ends and `next()` rethrows the error.
190+
@inlinable
191+
public mutating func next() async throws -> Transformed? {
192+
guard !encounteredError, try await baseIterator.hasMoreData() else {
193+
return nil
194+
}
195+
do {
196+
return try await transform(&baseIterator)
197+
} catch {
198+
encounteredError = true
199+
throw error
200+
}
201+
}
202+
}
203+
204+
@inlinable
205+
public func makeAsyncIterator() -> AsyncIterator {
206+
AsyncIterator(AsyncBufferedIterator(base.makeAsyncIterator()), transform: transform)
207+
}
208+
}
209+
210+
#endif

0 commit comments

Comments
 (0)