Programming Languages – Why Don’t They Automatically Manage Synchronous/Asynchronous Problems?

asynchronous-programmingprogramming-languagessyntax

I have not found many resources about this: I was wondering if it's possible/a good idea to be able to write asynchronous code in a synchronous way.

For example, here is some JavaScript code which retrieves the number of users stored in a database (an asynchronous operation):

getNbOfUsers(function (nbOfUsers) { console.log(nbOfUsers) });

It would be nice to be able to write something like this:

const nbOfUsers = getNbOfUsers();
console.log(getNbOfUsers);

And so the compiler would automatically take care of waiting for the response and then execute console.log. It will always wait for the asynchronous operations to complete before the results have to be used anywhere else. We would make so much less use of callbacks promises, async/await or whatever, and would never have to worry whether the result of an operation is available immediately or not.

Errors would still be manageable (did nbOfUsers get an integer or an error?) using try/catch, or something like optionals like in the Swift language.

Is it possible? It may be a terrible idea/a utopia… I don't know.

Best Answer

Async/await is exactly that automated management that you propose, albeit with two extra keywords. Why are they important? Aside from backwards compatibility?

  • Without explicit points where a coroutine may be suspended and resumed, we would need a type system to detect where an awaitable value must be awaited. Many programming languages do not have such a type system.

  • By making awaiting a value explicit, we can also pass awaitable values around as first class objects: promises. This can be super useful when writing higher-order code.

  • Async code has very deep effects for the execution model of a language, similar to the absence or presence of exceptions in the language. In particular, an async function can only be awaited by async functions. This affects all calling functions! But what if we change a function from non-async to async at the end of this dependency chain? This would be a backwards-incompatible change … unless all functions are async and every function call is awaited by default.

    And that is highly undesirable because it has very bad performance implications. You wouldn't be able to simply return cheap values. Every function call would become a lot more expensive.

Async is great, but some kind of implicit async won't work in reality.

Pure functional languages like Haskell have a bit of an escape hatch because execution order is largely unspecified and unobservable. Or phrased differently: any specific order of operations must be explicitly encoded. That can be rather cumbersome for real-world programs, especially those I/O-heavy programs for which async code is a very good fit.