> On Dec 16, 2015, at 03:25, Paul Sokolovsky <pmis...@gmail.com> wrote:
> 
> Hello,
> 
> On Tue, 15 Dec 2015 17:29:26 -0800
> Roy Williams <rwilli...@lyft.com> wrote:
> 
>> @Kevin correct, that's the point I'd like to discuss.  Most other
>> mainstream languages that implements async/await expose the
>> programming model with Tasks/Futures/Promises as opposed to
>> coroutines  PEP 492 states 'Objects with __await__ method are called
>> Future-like objects in the rest of this PEP.' but their behavior
>> differs from that of Futures in this core way.  Given that most other
>> languages have standardized around async returning a Future as
>> opposed to a coroutine I think it's worth exploring why Python
>> differs.
> 
> Sorry, but what makes you think that it's worth exploring why Python
> Python differs, and not why other languages differ?

They're really the same question.

Python differs from C# in that it builds async on top of language-level 
coroutines instead of hiding them under the hood, it only requires a simple 
event loop (which can be trivially built on a select-like function and a loop) 
rather than a powerful OS/VM-level task scheduler, it's designed to allow 
pluggable schedulers (maybe even multiple schedulers in one app), it doesn't 
have a static type system to assist it, ... Turn it around and ask how C# 
differs from Python and you get the same differences. And there's no value 
judgment either way.

So, do any of those explain why some Python awaitables aren't safely 
re-awaitable? Yes: the fact that Python uses language-level coroutines instead 
of hiding them under the covers means that it makes sense to be able to 
directly await coroutines (and to make async functions return those coroutines 
when called), which raises a question that doesn't exist in C#.

What happens when you await an already-consumed awaitables? That question 
doesn't arise in C# because it doesn't have consumable awaitables. Python 
_could_ just punt on that by not allowing coroutines to be awaitable, or 
auto-wrapping them, but that would be giving up a major positive benefit over 
C#. So, that means Python instead has to decide what happens.

In general, the semantics of awaiting an awaitable are that you get its value 
or an exception. Can you preserve those semantics even with raw coroutines as 
awaitables? Sure; as two people have pointed out in this thread, just make 
awaiting a consumed coroutine raise. Problem solved. But if nobody had asked 
about the differences between Python and C#, it would have been a lot harder to 
solve (or even see) the question.

> Also, what "most other languages" do you mean?

Well, what he said was "Most other mainstream languages that implements 
async/await". But you're right; clearly what he meant was just C#, because 
that's the only other mainstream language that implements async/await today. 
Others (JS, Scala) are implementing it or considering doing so, but, just like 
Python, they're borrowing it from C# anyway. (Unless you want to call F# async 
blocks and let! binding the same feature--but if so, C# borrowed from F# and 
everyone else borrowed from C#, so it's still the same.)

> Lua was a pioneer of
> coroutine usage in scripting languages, with research behind that.
> It doesn't have any "futures" or "promises" as part of the language.
> It has only coroutines. For niche cases when "futures" or "promises"
> needed, they can be implemented on top of coroutines.
> 
> And that's actually the problem with Python's asyncio - it tries to
> marry all the orthogonal concurrency concepts, unfortunately good
> deal o'mess ensues.

The fact that futures can be built on top of coroutines, or on top of promises 
and callbacks, means they're a way to tie together pieces of asynchronous code 
written in different styles. And the idea of a simple supertype of both futures 
and coroutines that's sufficient for a large set of problems, means you rarely 
need wrappers to transform one into the other; just use whichever one you have 
as an awaitable and it works.

So, you can write 80% of your code in terms of awaitables, but if the last 20% 
needs to get at the native coroutines, or to integrate with legacy code using 
callbacks, it's easy to do so. In C#, you instead have to simulate those 
coroutines with promises even when you're not integrating with legacy code; in 
a language without futures you'd have to wrap each call into and out of legacy 
code manually.

If you were designing a new language, you could probably get away with 
something a lot simpler. (If the only thing you could ever need a future for is 
to cache an awaitable value, it's a one-liner.) But for Python (and JS, Scala, 
C#, etc.) that isn't an option.

> It doesn't help on "PR" side too, because coroutine
> lovers blame it for not being based entirely on language's native
> coroutines, strangers from other languages want to twist it to be based
> entirely on foreign concepts like futures, Twisted haters hate that it
> has too much complication taken from Twisted, etc.

There is definitely a PR problem, but I think that's tied directly to the 
documentation problem, not anything about the design. Unless you've come to 
things in the same order as Guido, it's hard to figure out even where to dive 
in to start learning. So you try to write something, fail, get frustrated, and 
write an angry blog post about why Python asyncio sucks, which actually just 
exposes your own ignorance of how it works, but since 90% of your readers are 
just as ignorant of how it works, they believe you're right.

Part of the problem is that there are so many different mediocre paradigms for 
async programming that each have a million people who sort of know them just 
well enough to use them. A tutorial that would explain asyncio to someone who's 
written lots of traditional JS-style callbacks will be useless to someone who's 
written C-style reactors or Lua-style coroutines. So we probably need a bunch 
of separate tutorials just to get different classes of people thinking in the 
right terms before they can read the more detailed documentation.

Also, as with every async design, the first 30 tutorials anyone writes all 
completely neglect the problem of communicating between tasks (e.g., building a 
chat server instead of an echo server), so people think that what was easy in 
their familiar paradigm (because they've gotten used to it, and it's been years 
since they had to figure it out for themselves because none of the tutorials 
covered it so they forgot that part) is hard in the new one, and therefore the 
new one sucks.

>> There's a lot of benefits to making the programming model coroutines
>> without a doubt.  It's absolutely brilliant that I can just call code
>> annotated with @asyncio.coroutine and have it just work.  Code using
>> the old @asyncio.coroutine/yield from syntax should absolutely stay
>> the same. Similarly, since ES7 async/await is backed by Promises
>> it'll just work for any existing code out there using Promises.
>> 
>> My proposal would be to automatically wrap the return value from an
>> `async` function or any object implementing `__await__` in a future
>> with `asyncio.ensure_future()`.  This would allow async/await code to
>> behave in a similar manner to other languages implementing
>> async/await and would remain compatible with existing code using
>> asyncio.
>> 
>> What's your thoughts?
> 
> My thought is "what other languages told when you approached them with
> the proposal to behave like Python?".

I'm pretty sure if you approached the C# team and asked them why re-awaiting a 
coroutine doesn't produce nil, they'd explain that they deliberately chose not 
to expose coroutines (actually, I believe they were thinking in terms of 
continuations, as in F#, but...) under the theory that awaitables are all 
you'll ever need, which means that problem doesn't come up in the first place. 
The language can implicitly add such a wrapper and then easily optimize it away 
when possible because the user never sees inside the wrapper. And if you asked 
the ES7 committee, they might tell you they actually wanted something closer to 
Python, but it was just too hard to fit it into their brittle language, so they 
can't expose awaitables as anything but futures and hope their clever 
interpreters can optimize out the extra abstraction that you usually don't 
need, so the question doesn't arise for them either. And if you asked the Scala 
await fork developers, they'd probably point out that the idiomatic Sc
 ala equivalents to returning None and to raising are both returning an empty 
optional value, so the question doesn't arise for them for a different reason. 
And in F#, you can build a let!-awaitable out of a raw continuation instead of 
an async expression, but you have to write the code for that yourself, so you 
can decide what it does when re-awaited; it's not up to the language or stdlib. 
And so on. But, even if I'm wrong, and asking those questions would improve 
those languages, it still wouldn't improve Python.

> Also, wrapping objects in other objects is expensive. Especially if
> the latter kind of objects isn't really needed - it's perfectly
> possibly to write applications which don't use or need any futures at
> all, using just coroutines. Moreover, some people argue that most apps
> real people would write are such, and Futures are niche feature, so
> can't be center of the world.

Well, the whole point of the async model is that most apps real people write 
only depend on awaitables, and they almost never care whether they're futures 
or coroutines. This means a language can avoid the overhead of wrapping 
coroutines in futures (like Python), or keep coroutines out of the user-visible 
data model (like C#), and work almost the same way.

The problem is that Python is the first mainstream language to adopt awaitables 
built on top of native, user-visible coroutines, so it has to answer a few 
questions that C# dodged--like what happens when you await the same coroutine 
multiple times. That's not a negative judgment on Python, it's just a natural 
consequence of Python being a little more powerful here than the language it's 
borrowing from. Refusing to look at the differences between Python and C# would 
mean not noticing that and leaving it for some future language to solve instead 
of letting future languages copy from Python (which is always the best way to 
be consistent with everyone else, of course).


_______________________________________________
Python-Dev mailing list
Python-Dev@python.org
https://mail.python.org/mailman/listinfo/python-dev
Unsubscribe: 
https://mail.python.org/mailman/options/python-dev/archive%40mail-archive.com

Reply via email to