Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

It's not just about performance, but also safety and ergonomics. Since true coroutines[1] offer predictable scheduling, their behavior with regards to data races and deadlocks is also more predictable.

If programmers try to manually implement iterators, generators and interleaved state machines with their own goroutines and channels, it's not just performance that suffers - there is too much room for error.

[1] I'm using the qualifier "true" here, since many modern languages (such as Python, Kotlin) use the term "coroutines" for something that is more like Go's Goroutines than Lua's coroutines. Unlike Go, they are not preemptible, but they are (at least by default) implicitly resumed when necessary by some scheduler, and they may execute on different kernel threads and switch contexts.



> I'm using the qualifier "true" here, since many modern languages (such as Python, Kotlin) use the term "coroutines" for something that is more like Go's Goroutines than Lua's coroutines.

Python has both true (ish) coroutines (or at least coroutines which are entirely user controllable), which it mostly uses for iteration, and the concurrency specialised “async”.

Initially the goal was to reuse “yield” for concurrency (a big reason why “yield from” was added), but the ergonomics of mixing multiple coroutines uses was found to be awful, at least for the langage python is, and trying to provide relevant sugar difficult.


It's almost true. Python calls its "almost true" coroutines "generators", while the implicitly scheduled, async I/O-oriented coroutines are just called "coroutines".

I understand that the historical reasons for that ("generators" originally only supported "yield" and a separate async/await design with implicit scheduling seemed more ergonomic). But that doesn't remove the confusion.

But Python's generators are still fully "true" coroutines. Russ makes the distinction between coroutines and generators based on whether as "Generators provide less power than coroutines, because only the top-most frame in the coroutine is allowed to yield. ". I think this is a bad definition, as generators can clearly "pass" the yielding power to other generators with "yield from", and Russ talks about this feature in the same post. Generators require more ceremony, but I don't think they are "less powerful" in any meaningful way .

Roberto Ierusalimschy, the creator of Lua, and Ana Lucia de Moura proposed the distinction between stackful and stackless coroutines[1]. Their paper revived coroutines as a research subject and had a lasting impact on coroutine-related terminology. Ierusalimschy and Moura made the distinction between "Stackful" coroutines and "non-stackful coroutines" (the term "stackless coroutines" stuck later on, but they did not use it), where Stackful coroutines like Lua's coroutines can "suspend their execution from within nested functions". They also made the same allusion to power that Russ does: "powerful enough to constitute a general control abstraction; in particular, they can- not be used as a concurrent construct".

I think both Ierusalimschy and de Moura are onto something when they call generators "limited", but they are just incorrect when they say that "but are not powerful enough to constitute a general control abstraction; in particular, they cannot be used as a concurrent construct". Early Node.js libraries and frameworks such as "co" and "koa"[2] should count as a proof that you can very much use generators as a concurrency construct. Generators have all the power that is necessary for dealing with normal concurrency cases, although they don't have the best ergonomics.

But there is another distinction where "generators" are not the same as Lua coroutines, and clearly lack some expressive power (even though this power is NOT a necessary condition for implementing full-fledged concurrency). Lua allows coroutines to communicate bidrectionally: yield() can pass a value back the coroutine caller, which is returned from resume() - but when the coroutine caller calls resume() again they can pass an argument which will be returned from yield(). Generators are unidirectional: they only allow you to pass a value to yield(), but not to resume().

[1] http://www.inf.puc-rio.br/~roberto/docs/MCC15-04.pdf [2] https://news.ycombinator.com/item?id=6933358


> Lua allows coroutines to communicate bidrectionally: yield() can pass a value back the coroutine caller, which is returned from resume() - but when the coroutine caller calls resume() again they can pass an argument which will be returned from yield(). Generators are unidirectional: they only allow you to pass a value to yield(), but not to resume().

I might be missing something as I’m not familiar with Lua’s coroutines but that sounds like send (https://docs.python.org/3/reference/expressions.html#generat...). The parameter to `send` (from outside the coroutine) is the return value of `yield` (inside the coroutine).




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: