Looks good, but coming from someone who used Twisted for a few years, I had found deferreds to be messy and switched to yield-type co-routines (http://twistedmatrix.com/documents/10.2.0/api/twisted.intern...), but eventually I found those pretty verbose as well. Small demo examples always look clean and fun, but large applications at the top level will end up looking basically like this:
y = yield f(x)
z = yield g(y)
w = yield h(z)
...
In this case it would be async/await instead of pure yields.
The worst thing was having to hunt for Twisted version of libraries. "Oh you want to talk XMPP? Nah, can't use this Python library, have to find the Twisted version of it". It basically split the library ecosystem. Now presumably it will be having to look for async/await version of libraries that do IO.
> but eventually I found those pretty verbose as well...
It is definitely a bit verbose, but I decided that the clarity for the rest of the code is worth putting a yield before each function call. Also I've found a few projects (for Tornado at least) that cut down on this boiler plate and make the yield only required at the lowest level where the async really happens. [0]
> The worst thing was having to hunt for Twisted version of libraries. "Oh you want to talk XMPP? Nah, can't use this Python library, have to find the Twisted version of it". It basically split the library ecosystem. Now presumably it will be having to look for async/await version of libraries that do IO.
I work with Tornado and this is absolutely the worst part. At least with Tornado the newest version is embracing interoperability with python 3.4+ native AsynIO.
I found the same, working with AppEngine NDB in an app that talked to a dozen or so backends. It's still a lot better than the Node.js (pre-ES6) style of:
What would be a better syntax? The Java/C++ way of threads & hidden shared-state concurrency is a total mess; it hides all the potential yield points, so you never know when your flow of control might block or what shared state might need locking. Channels in Golang are better - they at least have some syntactic support - but the reification of the yield point into a concrete channel can end up creating a fair bit of boilerplate in the common case where you make a bunch of one-off async RPCs to remote services. Maybe Erlang has it right, where the pid is an implicit channel you can send messages to - but then you still need to pass that into any async library function, and it gives you no typing discipline. Maybe we really need something like futures where the syntax:
y <- f(x)
z <- g(y)
w <- h(z)
means "wait for the promise returned by expression f(z) to resolve, returning control to the executor, and then assign the result to y."
The splitting-the-library-ecosystem thing is a big part of it too (and why ES6 won't magically fix the Node ecosystem), but that's why Python is putting async into the language & standard library itself. At least then the stdlib will support it, and there will be strong social pressure to use the same concurrency mechanism in all libraries.
So you would basically want "<-" instead of "yield from"? Or is there something additional I'm missing? My main thing against this, is that it's very opposed with general Python principles. Think "||" vs "or", "&&" vs "and", and "test ? value : alternate" vs "value if test else alternate".
Yeah, it's just syntactic sugar. "<-" is chosen to match roughly-equivalent constructs in other languages, like Erlang, Go, or CSP. The reasoning behind it is that 'yield' is common enough to warrant special syntactic sugar - there are other precedents, like allowing dict['value'] instead of getitem(dict, value) or the @ matrix multiplication operator that was just added in Python 3.5.
The worst thing was having to hunt for Twisted version of libraries. "Oh you want to talk XMPP? Nah, can't use this Python library, have to find the Twisted version of it". It basically split the library ecosystem. Now presumably it will be having to look for async/await version of libraries that do IO.