Skip to content

Commit b171b81

Browse files
authored
Add reductions(_:_:) (apple#46)
* Add scan function * Add initial conditional conformances to Collection and BidirectionalCollection * Rename scan to reductions * Add information to the readme about the reductions name * Add eager API * Remove Reductions' conformance to BidirectionalCollection * Implement Reductions subscript such that it occurs in constant time * Add a variant which includes the given initial result * Add a variant which takes no initial result value * Add excluding label to show the initial result is not included * Test lazy implementations * Remove reductions(including:_:) * Add conformance to LazySequenceProtocol and LazyCollectionProtocol when the base sequence conforms * Fix implementation of lazy reductions * Add test cases for sequences with one element * Tidy up tests * Improve ergonomics of no initial value eager reductions call and provide it as an extension on Sequence * Add lazy InclusiveReductions sequence * Rename Reductions to ExclusiveReductions * Add Collection implementation for InclusiveReductions * Update guide * Add links for C++ implementations * Add conformance to Collection for ExclusiveReductions * Improve ergonomics of ExclusiveReductions' index representation * Improve ergonomics of InclusiveReductions' index representation by sharing the Index implementation * Tidy up internal function * Add exclusive eager version of reductions(into:_:) * Use new lazy assertion functions * Correct the complexity claims for reductions * Separate the index types This will allow for future changes to either implementation without causing a breakage for the other. * Update guide to reflect that arrays are returned directly * Add scan as deprecated methods * Add lazy overload of reductions(into:_:) * Add the reductions(into:_:) functions * More succinctly introduce reductions * Add a note about the deprecated scan functions * Update documentation * Add @inlinable and @usableFromInline * Copy documentation to all variants of reductions * Test the value after the function is complete * Just use the += operator * Add an example for reductions(into:_:) * Improve the documentation of the return value * Update complexity note * Use the reduce documentation for inspiration for the discussion of reductions
1 parent e652f01 commit b171b81

File tree

4 files changed

+901
-0
lines changed

4 files changed

+901
-0
lines changed

Guides/Reductions.md

Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
# Reductions
2+
3+
[[Source](https://github.com/apple/swift-algorithms/blob/main/Sources/Algorithms/Reductions.swift) |
4+
[Tests](https://github.com/apple/swift-algorithms/blob/main/Tests/SwiftAlgorithmsTests/ReductionsTests.swift)]
5+
6+
Produces a sequence of values.
7+
8+
This has the behaviour of reduce, but also returns all intermediate results.
9+
10+
```swift
11+
let exclusiveRunningTotal = (1...5).reductions(0, +)
12+
print(exclusiveRunningTotal)
13+
// prints [0, 1, 3, 6, 10, 15]
14+
15+
var value = 0
16+
let intoRunningTotal = (1...5).reductions(into: &value, +=)
17+
print(intoRunningTotal)
18+
// prints [0, 1, 3, 6, 10, 15]
19+
print(value)
20+
// prints 15
21+
22+
let inclusiveRunningTotal = (1...5).reductions(+)
23+
print(inclusiveRunningTotal)
24+
// prints [1, 3, 6, 10, 15]
25+
```
26+
27+
## Detailed Design
28+
29+
One trio of methods are added to `LazySequenceProtocol` for a lazily evaluated
30+
sequence and another trio are added to `Sequence` which are eagerly evaluated.
31+
32+
```swift
33+
extension LazySequenceProtocol {
34+
35+
public func reductions<Result>(
36+
_ initial: Result,
37+
_ transform: @escaping (Result, Element) -> Result
38+
) -> ExclusiveReductions<Result, Self>
39+
40+
public func reductions<Result>(
41+
into initial: inout Result,
42+
_ transform: @escaping (inout Result, Element) -> Void
43+
) -> ExclusiveReductions<Result, Self>
44+
45+
public func reductions(
46+
_ transform: @escaping (Element, Element) -> Element
47+
) -> InclusiveReductions<Self>
48+
}
49+
```
50+
51+
```swift
52+
extension Sequence {
53+
54+
public func reductions<Result>(
55+
_ initial: Result,
56+
_ transform: (Result, Element) throws -> Result
57+
) rethrows -> [Result]
58+
59+
public func reductions<Result>(
60+
into initial: inout Result,
61+
_ transform: (inout Result, Element) throws -> Void
62+
) rethrows -> [Result]
63+
64+
public func reductions(
65+
_ transform: (Element, Element) throws -> Element
66+
) rethrows -> [Element]
67+
}
68+
```
69+
70+
### Complexity
71+
72+
Calling the lazy methods, those defined on `LazySequenceProtocol`, is O(_1_).
73+
Calling the eager methods, those returning an array, is O(_n_).
74+
75+
### Naming
76+
77+
While the name `scan` is the term of art for this function, it has been
78+
discussed that `reductions` aligns better with the existing `reduce` function
79+
and will aid newcomers that might not know the existing `scan` term.
80+
81+
Deprecated `scan` methods have been added for people who are familiar with the
82+
term, so they can easily discover the `reductions` methods via compiler
83+
deprecation warnings.
84+
85+
Below are two quotes from the Swift forum [discussion about SE-0045][SE-0045]
86+
which proposed adding `scan` to the standard library and one from
87+
[issue #25][Issue 25] on the swift-algorithms GitHub project. These provide
88+
the reasoning to use the name `reductions`.
89+
90+
[Brent Royal-Gordon][Brent_Royal-Gordon]:
91+
> I really like the `reduce`/`reductions` pairing instead of `reduce`/`scan`;
92+
it does a really good job of explaining the relationship between the two
93+
functions.
94+
95+
[David Rönnqvist][David Rönnqvist]:
96+
> As other have already pointed out, I also feel that `scan` is the least
97+
intuitive name among these and that the `reduce`/`reductions` pairing would do
98+
a good job at explaining the relation between the two.
99+
100+
[Kyle Macomber][Kyle Macomber]:
101+
> As someone unfamiliar with the prior art, `reductions` strikes me as very
102+
approachable—I feel like I can extrapolate the expected behavior purely from my
103+
familiarity with `reduce`.
104+
105+
As part of early discussions, it was decided to have two variants, one which
106+
takes an initial value to use for the first element in the returned sequence,
107+
and another which uses the first value of the base sequence as the initial
108+
value. C++ calls these variants exclusive and inclusive respectively and so
109+
these terms carry through as the name for the lazy sequences;
110+
`ExclusiveReductions` and `InclusiveReductions`.
111+
112+
[SE-0045]: https://forums.swift.org/t/review-se-0045-add-scan-prefix-while-drop-while-and-iterate-to-the-stdlib/2382
113+
[Issue 25]: https://github.com/apple/swift-algorithms/issues/25
114+
[Brent_Royal-Gordon]: https://forums.swift.org/t/review-se-0045-add-scan-prefix-while-drop-while-and-iterate-to-the-stdlib/2382/6
115+
[David Rönnqvist]: https://forums.swift.org/t/review-se-0045-add-scan-prefix-while-drop-while-and-iterate-to-the-stdlib/2382/8
116+
[Kyle Macomber]: https://github.com/apple/swift-algorithms/issues/25#issuecomment-709317894
117+
118+
### Comparison with other langauges
119+
120+
**C++:** As of C++17, the `<algorithm>` library includes both
121+
[`exclusive_scan`][C++ Exclusive] and [`inclusive_scan`][C++ Inclusive]
122+
functions.
123+
124+
**[Clojure][Clojure]:** Clojure 1.2 added a `reductions` function.
125+
126+
**[Haskell][Haskell]:** Haskell includes a `scan` function for its
127+
`Traversable` type, which is akin to Swift's `Sequence`.
128+
129+
**Python:** Python’s `itertools` includes an `accumulate` method. In version
130+
3.3, a function paramenter was added. Version 3.8 added the optional initial
131+
parameter.
132+
133+
**[Rust][Rust]:** Rust provides a `scan` function.
134+
135+
[C++ Exclusive]: https://en.cppreference.com/w/cpp/algorithm/exclusive_scan
136+
[C++ Inclusive]: https://en.cppreference.com/w/cpp/algorithm/inclusive_scan
137+
[Clojure]: http://clojure.github.io/clojure/clojure.core-api.html#clojure.core/reductions
138+
[Haskell]: http://hackage.haskell.org/package/base-4.8.2.0/docs/Prelude.html#v:scanl
139+
[Rust]: https://doc.rust-lang.org/std/iter/trait.Iterator.html#method.scan

0 commit comments

Comments
 (0)