Glyph Lefkowitz, founder of the Twisted project, writes about Python, concurrency, software design and developer tooling.
Commenters recommend his posts, especially "Unyielding" on concurrency, calling them excellent, and cite his writing on email and Python design.
> Thank you, I've read the links, but without an example of Haskell/Scala code the benefits for any particular real problem are not clear to me.
Do you have cases where you've used AOP/reflection/metaclasses/decorators/macros or similar "magic"? (Exceptions or mutable variables sort of work as examples, but most people don't have experience of working on codebases that don't have them). It's hard to give simple examples because these things exist to solve complex problems - one should never reach for a monad where a plain function will do.
> Specifically, speaking of the second link, what happens if you need to get rid of two types of 'hell' at the same time?
There are several possibilities. First, it's worth noticing that most forms of "hell" are fine on their own, it's the interaction between two that tends to be surprising - e.g. https://glyph.twistedmatrix.com/2014/02/unyielding.html talks about how important it is to explicitly manage async, but it's making an underlying assumption that your language will have mutable variables. Unmanaged, pervasive mutation in a language without async is ok; equally, unmanaged, pervasive async is ok in a language without mutation. So often you can get away with managing one effect and letting the other happen naturally according to the program control flow. (This is less true in Haskell because laziness is built into the language, so Haskell has kind of "spent its freebie" there - but it can be a very effective style in…
One of the comments on the original post claims open source apps don't count because they "are not subject to many of the challenges the average developer faces in a corporate environment" so it appears TDD fans can't win either way.
But since you're looking for open source success stories there was some very positive noises about TDD from a Twisted dev recently:
"I would strongly recommend at least practicing some TDD, too. Learning how to write tests was useful for me, but practicing TDD really expanded my design skills in a bunch of surprising directions that were almost unrelated to testing. I originally felt as you do, that I wasn't disciplined enough to do it; now I feel like I'm too undisciplined not to do it ;-)."
From: http://glyph.twistedmatrix.com/2009/09/diesel-case-study-in-that-thing-i-just_24.html
Though he specifically states that Twisted didn't start out TDD, which he regrets, but maybe that disqualifies it.
Regarding Rubinius (and JRuby that was listed in the original post) I'm not sure trying to create a spec for an existing language implementation and then try to meet it would count as "true" TDD if people care about such differences. But maybe they do TDD for other bits lower in the stack, I'm not sure.
Goroutines are, for all practical purposes, threads. They're cheaper than real threads but they still have all the subtle complexity that comes with shared-memory preemptive threading (everything shared needs to be appropriately locked, scheduling is non-deterministic and can happen any time, etc). Async/await is different; context switches can only happen at an `await` expression. This greatly reduces the number of possibilities that must be considered and simplifies the task of concurrent programming (for more, see https://glyph.twistedmatrix.com/2014/02/unyielding.html).
In Python, the closest equivalent to `go foo()` is `threading.Thread(target=foo).start()`. Or you can use gevent to get goroutine-like lightweight threads, but I find this is works better in go than in python because the entire language is designed around it. In python it's common to find libraries that are not compatible with gevent's monkey-patching and so you lose concurrency in a way that is difficult to detect or guard against.
I think the promise-based model is the ideal model. After reading [1], I'm convinced that it's very close to realizing the underlying state machine ([2]) of the program, and I find that valuable. I agree that fibers are easier to use, but not simpler. I understand that my hard-line approach is not practical everywhere, but at least the async model used by Rust is not just a concession to the C ABI and whatnot, but an important model in its own right. So I think that it is quite sensible, if not practical for most programmers to make sense of :). Add fiber abstractions on top if appropriate, of course.
[1] https://glyph.twistedmatrix.com/2014/02/unyielding.html [2] https://lamport.azurewebsites.net/pubs/state-machine.pdf (read the introduction)
there's a very popular argument often made that if you dont use an explicit async-on-evented-IO approach for concurrency in general, and instead use threads or even an event-IO approach that conceals the IO wait just like a thread scheduler, then your program is impossible to reason about and will have bugs forever. Glyphs "Unyielding" at https://glyph.twistedmatrix.com/2014/02/unyielding.html is one well known blog post that describes this concept. For a lot of developers, the original reasons for event-based IO (e.g. many stateful network connections) is lost. The popularity of Javascript, which in recent years has exploded as a server side platform as well, is a key driver in this trend.
I'm not sure from what perspective you were looking to compare asyncio to green threads (performance, readability, etc.) but there is an interesting blog post [0] by the lead architect of Twisted about the fundamental difference in programming model between explicit yielding (e.g. asyncio) and implicit yielding (e.g. green threads, regular threads). It may give you some answers.
PEP 492, from what I understood, is more about streamlining the syntax, and the major differences between asyncio (pre this proposed syntax) and green threads in terms of programming model still apply.
[0] https://glyph.twistedmatrix.com/2014/02/unyielding.html