Adding async variant of reduce to Swift collections
Yesterday finally I find some time to work on my own HTTP client written in Swift (LightSessionKit) using Swift brand new support for asynchronous functions with async / await and I immediately stumbled across a problem: It’s not possible to use reduce
API within a collection that conforms to the Sequence
protocol. (although it’s possible to use with AsyncSequence
).
I did a quick google search and I found this library by John Sundell which adds asynchronoussupport for most Sequence
functions such as map
, flatMap
, compactMap
and forEach
but not for reduce
so let’s go ahead and add support ourselves.
But before doing so, let’s understand what reduce
does and how it works. According to the official Apple documentation this is what reduce
does:
Returns the result of combining the elements of the sequence using the given closure.
Easy right?, for a given sequence of elements it returns a unique element starting from a initial value and applying a closure to all elements of the sequence. Taking a look at the original method signature it takes two values, an initial value and a closure.
-
initialResult
: our initial accumulating value (this will be the same for an asynchronous variant). -
nextPartialResult
: According to the official documentation, a closure that combines an accumulating value and an element of the sequence into a new accumulating value. In our case, signature will be a bit different as it has to be asynchronous. -
Return Value: As closures results are cascading down we will also need to add the async keyword here.
This will be our final signature:
func asyncReduce<Result>(
_ initialResult: Result,
_ nextPartialResult: ((Result, Element) async throws -> Result)
) async rethrows -> Result {
// TODO: Implementation goes here
}
Now let’s get our hands dirty and do our own implementation for an asynchronous reduce
. We just need to start with initialResult
as starting point, then loop over all elements of the Sequence
and execute the given closure with initialResult
and all elements inside. This will be the final implementation:
public extension Sequence {
func asyncReduce<Result>(
_ initialResult: Result,
_ nextPartialResult: ((Result, Element) async throws -> Result)
) async rethrows -> Result {
var result = initialResult
for element in self {
result = try await nextPartialResult(result, element)
}
return result
}
}
And that’s it, full implementation is here.
PD: Bear in mind that although this will allow to execute asynchronous closures with reduce
, these closures will perform each of their operations in sequence, one after the other.