Join the discussion

Write your take first — we'll ask for email only when you're ready to publish.

  • Hacker News
  • I answered the quiz and it said "you must be a Javascript developer", which is true enough - that's probably my second-most-proficient language. In fact, I'm a Ruby developer partially because I hate the idea of async/await and I'm feeling very smug about my choice after reading this.

    Some of these design decisions seem indefensible to me. For example, what the authors call "Suspension":

    -> Static: Await points guaranteed to suspend -- JavaScript

    -> Dynamic: No guarantees on awaiting tasks -- C# · Swift · Tokio · Smol · Asyncio · Trio

    What is "await" if not a synonym for "suspend"?!?

    async/await is one product of a long line of thought that says "threads are too hard for programmers to get right". Threads (really, shared memory) have real usability issues for developers, but once you grok the semantics (which largely map to the physical execution model in a CPU) that knowledge is transferrable across virtually all languages and runtimes.

  • At last someone took the time to pore over all the tedious crap that I have been trying and failing to keep straight in my head since forever.
  • I was wondering "hopefully C++ allows you to pick across these axes so that you can build yourself the async primitives that work best for the problem at hand" and then: yes!

    > We cannot attribute C++ to any particular design point in the taxonomy provided in Table 1 because each axis is configurable. Although elegant and neutral, the choice of full programmability makes each library an async dsl; knowledge transfer between projects within the same language becomes exceedingly difficult.

    It is not if you think in terms of these axes and which solve your particular problem and not any particular specific design. Take for instance the simplest program one can imagine: a network video player. E.g. some server sends you RTP audio & video frames and you have to play them back correctly, with a nice GUI on top. If you want to do this in a way that is as efficient as possible you need to be aware of all possible ways of async interoperation:

    - connecting & receiving packets from the network in a classic network state machine where coroutines shine

    - handling vsync vs not-vsync for displaying the video frame

    - conforming to whatever async paradigm the hardware video decoding system you want to use is going to provide you with, e.g. Intel QuickSync vs VideoToolbox vs NVDEC...

    - handling the synchronous model of audio playback driven in pull mode

    - handling the synchronisation between audio / video, and thus the async patterns that support multi-threading as your audio thread can't be your video or GUI thread

    - handling the async model of your GUI library for your play / stop button's callbacks.

    There's zero chance that a single async model fits all of these equally well without tradeoffs, so you have to have the knowledge anyways.

  • The problem I encounter with async/await (in JS) is that while an async method can call a non-async-function and do somewthing with the result of that, the reverse is not true, a sync function can call async-function but can not us the result of that in any way, except pass it on or upwards.

    What makes it worse is that you can not simply modify a sync-function to become an async-function, if it has existing callers because those would likely break them.

    This affects the whole tree of possible function calls in my program. If at some level I have a sync function but I see it needs to get to some data that only async fuction can provide, I may need to change a whole call-chain of my call-tree, not just modify a single function in that tree.

    Then as I develop my program I need to make the decision for every function; should it be sync or async? In many cases it could be either one so which should I choose? Making it async would seem to make it easier to evolve the program later. But then would it make sense to make every function async?

  • Great post, but the quiz is unfair - it assumes there is only one "true way", but a lot of frameworks give you options

    For example, Trio has no global "spawn" method, by design. Judging by the results, authors assumed "with trio.open_nursery() as n: n.start_soon(write_to_log())", and so they got eager execution, dynamic extent, destructive propagation.

    But opening a nursery just to write a single log line is absolutely crazy! The real program would use an appropriately scoped shared nursery: either per-request or global. Later option allows indefinite extent and "never" propagation.

    Also, that "()" after write_to_log matters! If one follow trio's own examples, you'd write "n.start_soon(write_to_log)" - note no (). This will switch to lazy execution.

    I am not familiar with non-python frameworks listed, but I would not be surprised if they allow for similarly wide range of behaviors.

  • Wow, this is really helpful and timely!

    I'm building a new language with async/await and had to make a lot of these decisions, but I didn't have this organized of a framework to ground myself in. I'm happy to see it clearly that I choose mostly Trio with a bit of JavaScript.

    My language (Zena's) async docs page: https://zena-lang.dev/guide/async/ I think I might do a pass and try to call out the decision points more explicitly.

    fwiw, I found this post on cancellation by the author of Trio to be vey compelling: https://vorpus.org/blog/timeouts-and-cancellation-for-humans... and I based the cancellation design of Zena on it.

    Edit to add: I do wish this included JavaScript's AbortSignal in the Cancellation section. Not because it's good, but because passing cancel tokens is a pattern that exists. There's also the dimension of who can cancel and, like AbortSignal, whether tasks have to opt-in to cancellation checks.

  • This kind of semantics-first comparative analysis of programming languages is so important.

    I had a course at uni where we dissected how different languages approached concurrency, parallelism, modules/OOP, metaprogramming, eager vs lazy evaluation, types, exceptions... Understanding the trade-offs each language made (and their historical lineage) taught me much more about programming than any Python/Java/C course and made it much easier to pick up new languages.

    by cpa
  • It's long been clear that there were fundamental implementation choices that mattered between async runtimes, but _nine_ design dimensions? Damn.

    I think async is deceptive in that it seems like a self-contained and relatively straightforward aspect of a language. But there are many design choices to be made and they all have wide implications.

    Plus I think the implications of many of these dimensions are not fully understood and that collectively we are still trying to understand how they are playing out in implementations. Add to this the subtle nature of some of the implications plus the combinations...

    I think a good comparison is lexical vs dynamic scope in programming languages. This is a design dimension that was argued over for a decade or two in the early years of programming language design. It was only as time went by, and experience gained by working with concrete implementations that it became clear that lexical scoping should be the default choice and dynamic scoping should be restricted to various niches.

Explore Birbla archives