Hello all,
Thank you for your thoughtful feedback. I will attempt to consolidate some answers/updates/comments into one reply. I will also update the proposal at the link above to include this content shortly.
Append/Prepend
This was intended to be more like Array
's +
function. For now, to simplify the proposal, I've removed them.
Async Cancel
We think cancel should be synchronous, especially since the compiler will call it for you (see below for more on this). This is discussed in Alternatives Considered as well.
We will also update the proposal to suggest that deinit should be equivalent to cancel if it exists on the iterator.
Awaiting Many Things
A future enhancement (either in the standard library or a higher level library) could be to introduce a kind of buffering AsyncSequence
. As an initializer argument it could be told how many things it should attempt to eagerly fetch and act as a kind of signal smoother.
We've investigated the plans for the overhead of the await
keyword itself (not including any user code) and we believe it is low enough to not be an issue on its own.
Naming
We considered AsyncGenerator
but would prefer to leave the Generator
name for future language enhancements. Stream
is a type in Foundation, so we did not reuse it here to avoid confusion.
await in
We considered a shorter syntax of await...in
. However, since the behavior here is fundamentally a loop, we feel it is important to use the existing for
keyword as a strong signal of intent to readers of the code. Although there are a lot of keywords, each one has purpose and meaning to readers of the code.
Add APIs to iterator instead of sequence
We went off and explored this idea in depth. It is certainly a compelling argument. However, we ultimately came back around to the decision that consistency with the existing Sequence
APIs is the tradeoff decision we would like to propose.
We discussed applying the fundamental API (map
, reduce
, etc.) to the AsyncIterator
protocol instead of AsyncSequence
. There has been a long-standing (albeit deliberate) ambiguity in the Sequence
API -- is it supposed to be single-pass or multi-pass? This new kind of iterator & sequence could provide an opportunity to define this more concretely.
While it is tempting to use this new API to right past wrongs, we maintain that the high level goal of consistency with existing Swift concepts is more important.
For example, for...in
cannot be used on an Iterator
-- only a Sequence
. If we chose to make AsyncIterator
use for...in
as described here, that leaves us with the choice of either introducing an inconsistency between AsyncIterator
and Iterator
or giving up on the familiar for...in
syntax. Even if we decided to add for...in
to Iterator
, it would still be inconsistent because we would be required to leave for...in
syntax on the existing Sequence
.
Another point in favor of consistency is that implementing an AsyncSequence
should feel familiar to anyone who knows how to implement a Sequence
.
We are hoping for widespread adoption of the protocol in API which would normally have instead used a Notification
, informational delegate pattern, or multi-callback closure argument. In many of these cases we feel like the API should return the 'factory type' (an AsyncSequence
) so that it can be iterated again. It will still be up to the caller to be aware of any underlying cost of performing that operation, as with iteration of any Sequence
today.
Move-only iterator and removing Cancel
We discussed waiting to introduce this feature until move-only types are available in the future. This is a tradeoff in which we look to the Core Team for advice, but the authors believe the benefit of having this functionality now has the edge. It will likely be the case that move-only types will bring changes to other Sequence
and Iterator
types when it arrives in any case.
Prototyping of the patch does not seem to indicate undue complexity in the compiler implementation. In fact, it appears that the existing ideas around defer
actually match this concept cleanly. I've updated the proposal to show how this could work.
We have included a __consuming
attribute on the cancel
function, which should allow move-only iterators to exist in the future.