This whole dual meaning of async
continues to bother me across all these proposals. These proposals use that same word for both:
- indicating the presence of an effect and
- creating a context that handles that effect.
This creates confusion for programmers around the crucial question of “Am I introducing concurrency, handling it, or indicating that my code here requires that the calling code handle it?” The word async
can mean all these things.
As an analogy, consider what Swift would look like if we did the same thing for error handling, using the same word for both the effect and the context that handles it:
func createWidget(name: String) error -> Widget { ... }
let foo = error createWidget(name: "foo")
…or worse yet:
extension Array {
init(count: Int, generator: () error -> Element) reerror {
self = error (0..<count).map { _ in
error generator()
}
}
}
let widgets = error! Array(count: 10) {
error createWidget(name: "blarg")
}
What code above potentially causes an error? What code handles the error? It takes some head-scratching to figure out. The word “error” is scattered everywhere, meaning multiple things.
Code in the current proposals suffers to my eye from a similar problem: the word “async” is scattered everywhere, meaning multiple things.
We solve this problem by using completely separate words for the presence of the effect (throws
) and creating a context for handling the effect (try
):
extension Array {
init(count: Int, generator: () throws -> Element) rethrows {
self = try (0..<count).map { _ in
try generator()
}
}
}
let widgets = try! Array(count: 10) {
try createWidget(name: "blarg")
}
I wonder if it’s not too late to think hard about this, while we still have the chance. It does seem to me to be a significant part of the problem @kavon and @Chris_Lattner3 note above.
I do note that the Structured Concurrency proposal is moving partially away from async { … }
, renaming standalone task creation to Task { … }
(though that proposal does still have group.async
).