Sum with Block

Hi everyone!

@timv and I have been working on a small proposal for a Nice Thing that you all deserve. It's called sum with block, and I've been finding it super useful in my own codebases.

Motivation

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.

Proposed solution

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.

Detailed design

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 }
	}
}

You can read the full proposal here! Please let us know what you think. Tim and I are happy to provide a reference implementation and tests if the community likes the pitch!

31 Likes