Short version: You cannot add async versions of forEach in your own library using the same name, i.e. if you would like to have it, it has to be defined where the original forEach is defined. For me this is a good reason to have async versions of forEach (throwing and not throwing) be defined inside the standard library.
More details:
+1
See tera's comment for an argument why one could like to use it.
You cannot add it outside the standard library (where forEach is defined) with the same name, you have to use a different name in another package (see there for a possible explanation).
extension Sequence {
func forEach (
_ operation: (Element) async -> Void
) async {
for element in self {
await operation(element)
}
}
func forEach (
_ operation: (Element) async throws -> Void
) async rethrows {
for element in self {
try await operation(element)
}
}
}
...and you cannot add those definitions to the async-algorithms package, you have to add them to the standard library (if you do not want to change the names).
Less surprise to a less informed user of an according library: If you use forEach and then "coincidentally" call an async function inside the according closure, the compiler just tells you twice that you should use await and you are happy.
But I also see the advantage of the async property as a general way to find your way to an async sequence giving you also an async map etc.
Long story short: forEach would be useful for async transformations and algorithms as the final consumption alternative to a for loop. It's easier for everyone to simply extend it in that package and require the user to transform a synchronous sequence into an asynchronous one before he/she can use the async forEach. Otherwise this would require the regular forEach to be extended to func forEach(_ operation: (Element) async throws -> Void) reasync rethrows. Notice the reasync there!
It's a win-win situation if it's added to the package. However I cannot speak for everyone and I think reasync is eventually coming anyways, so .
If I understand correctly, this is an overload resolution problem. I have two solutions for you:
Add @_disfavoredOverload to your async forEach.
Include your own regular forEach alongside your async version.
@_disfavoredOverload is an underscored attribute, so it's not an official, source-stable language feature and using it is not very cool, but the alternative is to rewrite a built-in sequence method, which also isn't brilliant.
func test(x: [Int]) async {
x.forEach { print($0) }
await x.forEach { await someAsyncFunction($0) }
}
func someAsyncFunction<T>(_: T) async {}
// Change to 'false' to try option 2.
#if true
extension Sequence {
@_disfavoredOverload
func forEach(_ body: (Element) async -> Void) async {
for element in self { await body(element) }
}
}
#else
extension Sequence {
func forEach(_ body: (Element) -> Void) {
for element in self { body(element) }
}
func forEach(_ body: (Element) async -> Void) async {
for element in self { await body(element) }
}
}
#endif
There is some ambiguity with an async forEach function. Do you mean that each application of the closure is async, or do you mean that the applications of the closure are concurrent? Some may infer or need the latter (which is not what has been suggested here).
Historically speaking; adding reasync stuff into the standard library has been blocked on a technical reason - using those annotations creates runtime calls to the _Concurrency library. This ended up making cycles between the swift standard library and the swift concurrency library. For some of the other functions that are very clear they really should be reasync the reason why it has not yet happened is mainly due to the technical limitation.
The .forEach function has an added wrinkle; it is viewed by some as superfluous, an optimizer hazard, as well as a cognitive hazard. So those concerns probably should be foremost before adding features to it.
Well, this could be a differentiator between both use cases:
βregularβ sequence with forEach, map etc.: process one element after the other, but allow some awaits within the closure, meaning the processing of the whole sequence might be suspended, nothing happening concurrently;
mySequence.async.forEach: When getting e g. an AsyncLazySequence I would at least consider that something more tricky could happen β I mean why else the ceremony instead of just adding versions of forEach, map etc. to the standard library allowing an async closure? (It is a serious question, my understanding of the Swift Async Algorithms package is quite superficial at the moment.)
I also need the first use case for a library where concurrent use of the elements of an according sequence is forbidden. But you might want to call an async function, e.g. get data from a database for each element (and applying the data to the element).