Ok, I'm late to the party - way later than I wanted to be.
Brief comment on above conversation: I'd be more in favor of let async
than async let
because my mental model of async
is more like syntactic sugar around a type.
Also regarding types: Which type would the compiler infer for veggies
, meat
and oven
? Something like Task<Vegetable>
and so on? Well, ok. Now, what is the type of [veggies, meat]? Probably [Task<Ingredient>]
(assuming that you once again apply some magic that we don't have access to to tell the compiler that tasks are covariant in their generic argument).
Finally, the compiler then somehow knows that await
, when applied to arrays of tasks, should execute the tasks concurrently (I see no other point where the compiler could possibly infer that it's safe to concurrently execute tasks). Well, that's something! Implicitly, a conversion must have take place between [Task<X>]
and Task<[X]>
.
Conclusion: if await
should be applicable to any expression containing Task
s (which I guess is intended), the user would have to know that specifically for arrays this entails concurrent execution. Honestly, I'd be more in favour of an explicit conversion with, e.g., a zip
function. There are reasons why Swift is very restrictive with implicit type conversions, so we should be here as well.
Now, let's have a look at the return type of Task.withNursery
. Since it is a static method of Task
, it is obviously free to return anything it likes, but I'd usually assume this would be a named initializer somehow. The nursery in the example consumes tasks that produce (Int, Vegetable)
and the closure returns [Vegetable]
, so the whole thing - if it should be understood as a named initializer - would return a Task<[Vegetable]>
.
But no! If we look at the return type of func chopVegetables()
, we actually get async throws -> [Vegetable]
, i.e., Task.withNursery
has to be async throws -> <ClosureReturnType>
. Curiously, no await
in front of the method call.
That leads to the following question: is it safe then to think of async
(applied to func
s or let
) simply as an alternative spelling for Task
??? And is it possible that this whole await
thing mostly serves as a means to "unwrap" the task (without passing visually nasty continuations with their own scope) so we can chain them just the way we chain optionals or throwing functions? If that should be our mental model, I would argue that this should be pointed out somewhere so we better understand what is going on. It might also help with the design and implementation of future primitives/combinators.
Edit: Regarding naming: Task
may be a bit unfortunate because it can easily be confused with Process
.