I've found that when talking about async/await stuff recently, I've mostly
dropped the word "coroutine" from my vocabulary and replaced it with "async
function". I'm writing to suggest that we might want to make this switch as
a community, and do it now, before the next 10x increase in the async/await
userbase. Rationale:
*Consistency*: If the only two words to keep track of were function vs
coroutine, that would be one thing. But here's a list of syntax constructs
we have now, and how we talk about them in English:
with -> applies a *context manager* to a *with block*
async with -> applies an *async context manager* to an *async with block*
for -> does a *loop* over an *iterator*
async for -> does an *async loop* over an *async iterator*
def + yield -> makes a *generator*
async def + yield -> makes an *async generator*
[... for ...] -> is a *comprehension*
[... async for ...] -> is an *async comprehension*
def -> creates a *function*
async def -> creates a *coroutine*
One of these is not like the others. Why? It's a pointless toe-stub for
newcomers.
*Expressivity:* Once I started talking about async functions, I've found my
vocabulary suddenly expanding with useful new bonus terms, like "async
method" (= an async def inside a class body), or "async callable" (= any
callable that returns an awaitable, emphasizing the duck type rather than
the concrete type).
*Approachability*: What is a "coroutine" anyway? Is it like a monad? Am I
going to have to learn about endofunctors and continuations next? Of course
we've all gotten used to the term now so we don't notice, but for a new
user this is an obviously jargony "if you don't have a PhD then this isn't
*for* you" kind of word. It's not hospitable.
Plus, it emphasizes the implementor under-the-hood point of view, not the
user point of view. (Which is totally understandable! Because we're just
now transitioning from the internal implementors hacking things out phase
to the broad general user base phase, but that's why we should fix this
now.) For an end-user, especially one starting out, they don't need to know
or care about all the coroutine stuff that async/await is using to make the
magic happen, they can learn that later. To get started, all you need to
know is that you do async stuff by using async functions and sticking an
'await' onto your I/O calls. A lot of work went into abstracting away that
magic and giving it a friendly Python syntax; the English syntax should
follow suit.
*Accuracy*: Speaking of jargon, the term "coroutine" *is* an existing piece
of jargon in computer science, and our term and their term don't quite
match up. This isn't a huge deal, but it's unnecessary confusion. According
to Wikipedia I guess technically we don't even have "true" coroutines, just
"semicoroutines"? And every generator has just as much claim to being a
coroutine-in-the-CS-sense as an async function does, but when we say
coroutine we don't mean generators. (Except when we do.) This confusion
might partly reflect the somewhat confusing transition from 'yield from' to
async/await, as demonstrated by the official doc's somewhat confusing
definition of "coroutine":
https://docs.python.org/3/library/asyncio-task.html#coroutine
But going forward, yield-from-based coroutines and @asyncio.coroutine and
all that are quickly becoming a historical curiosity, and most of the time
what we really want to be talking about are just async functions.
*So:* I'm not suggesting we modify Python itself to rename types.coroutine
or the .cr_* fields or anything, that's all expert internal stuff (possible
exception: the __repr__ for async functions). But I do propose that we all
agree to do a search/replace for s/coroutine/async function/g on our
end-user docs and when generally talking about these things, and announce
that we're doing so. It's a small thing and a bit of work, but if there's a
general agreement to do it in a coordinated way then I think it could be
pretty straightforward transition that will pay dividends for a long time.
-n
--
Nathaniel J. Smith -- https://vorpus.org