- Proposal: SE-NNNN
- Authors: Soroush Khanlou, Tim Vermeulen
- Review Manager: TBD
- Status: Awaiting implementation
While Swift’s Sequence models brings a lot of niceties that we didn’t have access to in Objective-C, like map and filter, there are other useful operations on sequences that the standard library doesn’t support yet. One operation that is currently missing is summing numeric values on elements in a sequence.
Summing a sequence of numbers is useful on its own, but Swift users don’t frequently keep lists of numbers devoid of any context. The numbers that need be summed up are frequently a property of some richer sequence element.
As a more concrete example, one might want to add up all the followers that some collection of users might have. This can currently be done with map and reduce:
users.map(\.followerCount).reduce(0, +) // => sum of all users' followers
This approach is suboptimal because it does two passes over the data and map needlessly creates an intermediate array, which is discarded right away.
To avoid this, you can use a lazy sequence:
users.lazy.map(\.followerCount).reduce(0, +) // => sum of all users' followers
However, the closure parameter of the lazy map is marked @escaping
and cannot throw an error, which means this is not always an option. And if the passed closure does throw an error or cannot escape, Swift will silently call the eager version of map, nullifying any performance benefits you think you’re getting.
A third option without these downsides is to stuff the extraction logic inside a reduce closure, making it less readable:
users.reduce(0, { $0 + $1.followerCount }) // => sum of all users' followers
These three solutions all lie on a spectrum between readability and performance.
The proposed solution would avoid a performance trap and provide a simple interface for users to both read and write. Autocomplete should present it to them handily as well.
users.sum(\.followerCount) // => sum of all users' followers
In addition, if the sequence is already made up of numbers, a version without parameters (sum()
) is provided to make the code even clearer.
A reference implementation for the function is included here:
extension Sequence {
func sum<T>(_ transform: (Element) throws -> T) rethrows -> T where T: AdditiveArithmetic {
var sum = T.zero
for element in self {
sum += try transform(element)
}
return sum
}
}
extension Sequence where Element: AdditiveArithmetic {
func sum() -> Element {
return sum { $0 }
}
}
This change is additive only.
This change is additive only.
This change is additive only.
- Introduce a new
Summable
protocol rather than usingAdditiveArithmetic
. BecuaseAdditiveArithmetic
is a protocol for values that can support subtraction in as well as addition, there is a small benefit to aSummable
protocol that provides only addition and a zero value. However, there is also a cost to additional protocols in the hierarchy, and we think thatAdditiveArithmetic
is specific enough. - Allow the closure passed to
sum(_:)
to return an optional value, where nil is not added to the sum. We decided that this behavior is not in line with Swift’s handling of optionals and default values, and we think that requiring the use of?? 0
to make this explicit makes the code more readable. - Add an overload of
sum(_:)
that allows returning an optional value from the closure, where nil short-circuits the operation and makes the entire function return nil. We think this functionality is not worth adding for a few reasons: first, this is ambiguous in case one expects nil to be treated as zero; second, short-circuiting behavior is already available via thethrows
/rethrows
feature. - Also add a product method that multiplies the values instead of adding them. We feel like this operation is a lot less common than taking the sum, and therefore we don’t think it’s worth adding.