3 ms·
> The solution to this in Javascript would be to simply make every single function an async function, but that introduces a bunch of clutter because async/await
by WorldMaker 3y ago
> The solution to this in Javascript would be to simply make every single function an async function, but that introduces a bunch of clutter because async/await is a feature to be bolted onto an existing language, rather than a paradigm to build your language around.
I completely disagree with this, and I think that's a strong summary of why our opinions are so hugely diverged here. Async/Await was decades of programming language research in the making. It's "just" Haskell's do notation but with a lot of smart research into "okay, but how do we do that for beginners and junior developers". It may seem like an overnight success, but that's not because it is "tacked on", it's because it is well thought out, well tested, and well designed and therefore has spread to a number of programming languages. (I didn't say anything about Javascript. Javascript wasn't the first to add async/await and wasn't the last.)
I'm not sure it is a "failure state" of types, but I can agree it is a hard edge case, and there have been criticisms of it for decades. The same "why isn't every function in Haskell in the IO monad and written in do-notation" is the same "why isn't every function async/await" complaint. It's the same reason not every function in a language with iterators is written as a generator with yield instead of return. It's the same reason you don't solve every problem with a single RegEx. It's the same reason you use OO classes for encapsulation (or don't). Pragmatic programming languages are never just a single paradigm.
The callee knows what resources it is encapsulating and which of those need to be asynchronous. It knows better than the caller if there is going to be a suspend point what state it will need to transition into next and can prepare its internal state machine best for how it needs to operate. It's useful to bubble up that library knowledge into the type system and async/await gives a strong type safe way to do that (plus eases a lot of the trivia of building a state machine in languages that support async/await). Just like a generator function is a basic state machine for iterable results. Just like an OO library might encapsulate some amount of memory usage.
Your elixir example is a single function and you can do that in any of the async/await libraries to. The trick to async/await is that what you are writing in async/await isn't a single function but a complex state machine. Have you actually tried to rewrite async/await-heavy code to something like Elixir? It's not hard, but it is a lot like hand converting a RegEx to a DFA.
That doesn't mean that caller's "lose power", the power dynamic is shifted, but it isn't as deranged as you seem to think it is. async/await just defines the state machine possibilities, it doesn't know or care if the async/await state machines that have been defined complete synchronously or asynchronously. It doesn't care what threads/threadpools/green threads/greenlets/threadlets/whatever else those state machines run on, including the same thread as the caller. The state machines are generally agnostic to how their state transitions are triggered. Javascript doesn't give you a lot of power there, but that's because browsers have always been in charge of JS threading and that was true before async/await. That's not a deficit of async/await, that's a deficit of Javascript. (That you can't see past Javascript may indicate why you think it is a tacked on part of the language and not a success story from other languages modestly applied.) You can look up some of the tools that C# presents, for example, to let callers influence downstream scheduling. You can look up all the complaints that Python despite being the "there should be one clear way to do it" language decided that instead "explicit is better than implicit" beat out as the winning mantra and left scheduling to a handful of different libraries with different opinions of how async/await should be scheduled by callers, but the benefit is that apps have to explicitly opt in to one of those behaviors rather than a "works fine for most developers" out of the box default. The "power" remains you just have to learn new ways to apply it.