Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit b6dff00

Browse files
authored
Transform the reductions and remove duplicates guides into a proposal (apple#233)
1 parent 4ba0797 commit b6dff00

File tree

1 file changed

+196
-0
lines changed

1 file changed

+196
-0
lines changed

Evolution/NNNN-reductions.md

+196
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,196 @@
1+
# Reductions
2+
* Proposal: [SAA-NNNN](https://github.com/apple/swift-async-algorithms/blob/main/Evolution/NNNN-reductions.md)
3+
* Author(s): [Philippe Hausler](https://github.com/phausler)
4+
* Status: **Implemented**
5+
* Implementation: [
6+
[Source](https://github.com/apple/swift-async-algorithms/blob/main/Sources/AsyncAlgorithms/AsyncExclusiveReductionsSequence.swift) |
7+
[Tests](https://github.com/apple/swift-async-algorithms/blob/main/Tests/AsyncAlgorithmsTests/TestReductions.swift)
8+
]
9+
* Decision Notes:
10+
* Bugs:
11+
12+
## Introduction
13+
14+
The family of algorithms for reduce are useful for converting a sequence or asynchronous sequence into a single value, but that can elide important intermediate information. The _reductions_ algorithm is often called "scan", but this name does not convey its heritage to the family of reducing.
15+
16+
There are two strategies that are usable for creating continuous reductions: exclusive reductions and inclusive reductions:
17+
18+
* Exclusive reductions take a value and incorporate values into that initial value. A common example is reductions by appending to an array.
19+
* Inclusive reductions transact only on the values provided. A common example is adding numbers.
20+
21+
There are also common specializations of this algorithm that are worth offering as a shorthand. Particularly removing duplications that is a common task of reducing a sequence. When processing values over time it is definitely possible that the same value may occur in a row. When the distinctness of the presence value is not needed it is useful to consider the values over time that are differing from the last. Particularly this can be expressed as removing duplicate values either in the case as they are directly `Equatable` or by a predicate.
22+
23+
## Proposed Solution
24+
25+
Exclusive reductions come in two variants: transforming by application, or transformation via mutation. This replicates the same interface as `reduce(_:_:)` and `reduce(into:_:)`. Unlike the `reduce` algorithms, the `reductions` algorithm also comes in two flavors: throwing or non throwing transformations.
26+
27+
```swift
28+
extension AsyncSequence {
29+
public func reductions<Result>(
30+
_ initial: Result,
31+
_ transform: @Sendable @escaping (Result, Element) async -> Result
32+
) -> AsyncExclusiveReductionsSequence<Self, Result>
33+
34+
public func reductions<Result>(
35+
into initial: Result,
36+
_ transform: @Sendable @escaping (inout Result, Element) async -> Void
37+
) -> AsyncExclusiveReductionsSequence<Self, Result>
38+
}
39+
40+
extension AsyncSequence {
41+
public func reductions<Result>(
42+
_ initial: Result,
43+
_ transform: @Sendable @escaping (Result, Element) async throws -> Result
44+
) -> AsyncThrowingExclusiveReductionsSequence<Self, Result>
45+
46+
public func reductions<Result>(
47+
into initial: Result,
48+
_ transform: @Sendable @escaping (inout Result, Element) async throws -> Void
49+
) -> AsyncThrowingExclusiveReductionsSequence<Self, Result>
50+
}
51+
```
52+
53+
These APIs can be used to reduce an initial value progressively or reduce into an initial value via mutation. In practice, a common use case for reductions is to mutate a collection by appending values.
54+
55+
```swift
56+
characters.reductions(into: "") { $0.append($1) }
57+
```
58+
59+
If the characters being produced asynchronously are `"a", "b", "c"`, then the iteration of the reductions is `"a", "ab", "abc"`.
60+
61+
Inclusive reductions do not have an initial value and therefore do not need an additional variations beyond the throwing and non throwing flavors.
62+
63+
```swift
64+
extension AsyncSequence {
65+
public func reductions(
66+
_ transform: @Sendable @escaping (Element, Element) async -> Element
67+
) -> AsyncInclusiveReductionsSequence<Self>
68+
69+
public func reductions(
70+
_ transform: @Sendable @escaping (Element, Element) async throws -> Element
71+
) -> AsyncThrowingInclusiveReductionsSequence<Self>
72+
}
73+
```
74+
75+
This is often used for scenarios like a running tally or other similar cases.
76+
77+
```swift
78+
numbers.reductions { $0 + $1 }
79+
```
80+
81+
In the above example, if the numbers are a sequence of `1, 2, 3, 4`, the produced values would be `1, 3, 6, 10`.
82+
83+
The `removeDuplicates()` and `removeDuplicates(by:)` APIs serve this purpose of removing duplicate values that occur. These are special case optimizations in the family of the reductions APIs. These algorithms test against the previous value and if the latest iteration of the base `AsyncSequence` is the same as the last it invokes `next()` again. The resulting `AsyncRemoveDuplicatesSequence` will ensure that no duplicate values occur next to each other. This should not be confused with only emitting unique new values; where each value is tested against a collected set of values.
84+
85+
```swift
86+
extension AsyncSequence where Element: Equatable {
87+
public func removeDuplicates() -> AsyncRemoveDuplicatesSequence<Self>
88+
}
89+
90+
extension AsyncSequence {
91+
public func removeDuplicates(
92+
by predicate: @escaping @Sendable (Element, Element) async -> Bool
93+
) -> AsyncRemoveDuplicatesSequence<Self>
94+
95+
public func removeDuplicates(
96+
by predicate: @escaping @Sendable (Element, Element) async throws -> Bool
97+
) -> AsyncThrowingRemoveDuplicatesSequence<Self>
98+
}
99+
```
100+
101+
The `removeDuplicates` family comes in three variants. One variant is conditional upon the `Element` type being `Equatable`. This variation is a shorthand for writing `.removeDuplicates { $0 == $1 }`. The next variation is the closure version that allows for custom predicates to be applied. This algorithm allows for the cases where the elements themselves may not be equatable but portions of the element may be compared. Lastly is the variation that allows for comparison when the comparison method may throw.
102+
103+
## Detailed Design
104+
105+
#### Reductions
106+
107+
The exclusive reduction variants come in two distinct cases: non-throwing and throwing. These both have corresponding types to encompass that throwing behavior.
108+
109+
For non-throwing exclusive reductions, the element type of the sequence is the result of the reduction transform. `AsyncExclusiveReductionsSequence` will throw if the base asynchronous sequence throws, and will not throw if the base does not throws.
110+
111+
```swift
112+
public struct AsyncExclusiveReductionsSequence<Base: AsyncSequence, Element> {
113+
}
114+
115+
extension AsyncExclusiveReductionsSequence: AsyncSequence {
116+
public struct Iterator: AsyncIteratorProtocol {
117+
public mutating func next() async rethrows -> Element?
118+
}
119+
120+
public func makeAsyncIterator() -> Iterator
121+
}
122+
123+
extension AsyncExclusiveReductionsSequence: Sendable
124+
where Base: Sendable, Element: Sendable { }
125+
126+
```
127+
128+
The sendability behavior of `AsyncExclusiveReductionsSequence` is such that when the base, base iterator, and element are `Sendable` then `AsyncExclusiveReductionsSequence` is `Sendable`.
129+
130+
```swift
131+
public struct AsyncThrowingExclusiveReductionsSequence<Base: AsyncSequence, Element> {
132+
}
133+
134+
extension AsyncThrowingExclusiveReductionsSequence: AsyncSequence {
135+
public struct Iterator: AsyncIteratorProtocol {
136+
public mutating func next() async throws -> Element?
137+
}
138+
139+
public func makeAsyncIterator() -> Iterator
140+
}
141+
142+
extension AsyncThrowingExclusiveReductionsSequence: Sendable
143+
where Base: Sendable, Element: Sendable { }
144+
145+
```
146+
147+
#### Remove Duplicates
148+
149+
In the cases where the `Element` type is `Equatable` or the non-trowing predicate variant these utilize the type `AsyncRemoveDuplicatesSequence`. The throwing predicate variant uses `AsyncThrowingRemoveDuplicatesSequence`. Both of these types are conditionally `Sendable` when the base, base element, and base iterator are `Sendable`
150+
151+
The `AsyncRemoveDuplicatesSequence` will rethrow if the base asynchronous sequence throws and will not throw if the base asynchronous sequence does not throw.
152+
153+
```swift
154+
public struct AsyncRemoveDuplicatesSequence<Base: AsyncSequence>: AsyncSequence {
155+
public typealias Element = Base.Element
156+
157+
public struct Iterator: AsyncIteratorProtocol {
158+
public mutating func next() async rethrows -> Element?
159+
}
160+
161+
public func makeAsyncIterator() -> Iterator {
162+
Iterator(iterator: base.makeAsyncIterator(), predicate: predicate)
163+
}
164+
}
165+
166+
extension AsyncRemoveDuplicatesSequence: Sendable
167+
where Base: Sendable, Base.Element: Sendable { }
168+
169+
```
170+
171+
The `AsyncThrowingRemoveDuplicatesSequence` will rethrow if the base asynchronous sequence throws and still may throw if the base asynchronous sequence does not throw due to the predicate having the potential of throwing.
172+
173+
```swift
174+
175+
public struct AsyncThrowingRemoveDuplicatesSequence<Base: AsyncSequence>: AsyncSequence {
176+
public typealias Element = Base.Element
177+
178+
public struct Iterator: AsyncIteratorProtocol {
179+
public mutating func next() async throws -> Element?
180+
}
181+
182+
public func makeAsyncIterator() -> Iterator
183+
}
184+
185+
extension AsyncThrowingRemoveDuplicatesSequence: Sendable
186+
where Base: Sendable, Base.Element: Sendable { }
187+
188+
```
189+
190+
## Alternatives Considered
191+
192+
One alternate name for `reductions` was to name it `scan`; however the naming from the Swift Algorithms package offers considerably more inference to the heritage of what family of functions this algorithm belongs to.
193+
194+
## Credits/Inspiration
195+
196+
This transformation function is a direct analog to the synchronous version [defined in the Swift Algorithms package](https://github.com/apple/swift-algorithms/blob/main/Guides/Reductions.md)

0 commit comments

Comments
 (0)