Has anyone ever tried to build a fully asynchronous programming language? I've had this concept in my mind for quite a while. This post is going to be an exploration of an idea.
Problems to solve:
There's a lot of talk about how C is sequential, modern processors are made for C and so they emulate a sequential interface while they are actually much better at processing many things in parallel. To mitigate this, processors use optimization techniques like branch prediction to execute things in parallel that are meant to be sequential but these techniques introduce inefficiencies and (sometimes security-critical) errors. Therefore the C model of sequential execution does not seem like a good fit anymore which is a constant source of tension. To compound the problem, all modern programming languages are made for C-processors and therefore also rely on sequential execution.
The speed at which modern computers process information is oftentimes not limited by the speed at which their CPU can process it but by the time it takes for new input to arrive. This input (from a CPU perspective) can come from the RAM (which takes a couple of cycles to load), an SSD which takes a couple more cycles or a hard disk which sometimes takes many cycles or even another computer on the network which takes an eternity in CPU time and so it is inefficient for a CPU to actually wait for that input. Instead every CPU needs to work on many things in parallel so it can process something else while waiting for each specific input.
There is also some talk about the colored function problem. Many programming languages today (such as JavaScript and Rust) have adopted the async/await paradigm which is essentially a way to express that specific functions can go to sleep and let other functions run while they wait for data from a slow source. This is great and helps a lot with optimizing CPU utilization but it introduces the problem that you now have two kinds of functions and a bunch of rules for how they can and cannot call each other.
I think there is a way to address all of these issues at once and it revolves around making it more comfortable to work with futures (aka promises in JavaScript, the datatypes programming languages use to deal with things that are still in the making or in delivery).
The idea:
Make it possible to actually work with futures. Right now all you can do with a future in all the languages I know is either await them or use chaining (getUser().then(user => user.email)) to derive new futures from your future.
What if that latter example was much easier to do? What if I could just use
const user = getUser()
const email = await user.emailand what if I did not need to await everything all the time and could just call most functions passing a future just as I would usually pass a value?
const currentUser = auth()
const ownProjects = database.getProjects().whereUserIs(currentUser.id)
const posts = database.getLatestPosts()
return (
<Layout>
<Header avatar={currentUser.imageUrl} />
<Sidebar>
<ul>
{ownProjects.map(project => <li>{project.name}</li>)}
</ul>
</Sidebar>
<Timeline posts={posts} />
</Layout>
)This is a UI example and the huge advantage is that when applied this way, the UI library can take responsibility for pre-building as much of the UI as possible and replacing missing values as soon as all the async operations resolve.
I think this also works kind of well for data pipelining operations:
const projects = getProjects() // Future<Project[]>
const users = getUsers() // Future<User[]>
const clients = getClients() // Future<Client[]>
const projectMembersByClient = clients.map(client => [
client.id,
projects
.filter(project => project.clientId === client.id)
.map(project => users.filter(user => project.memberIds.includes(user.id)))
]) // Future<[string, User[]][]>What this means
To consistently implement this pattern, we would need to create a programming language that supports it natively. This language would need to have a built-in future type that automatically has all the fields and methods of the contained value but with the difference that they all return futures.
It would result in a coding style that breaks with the assumption that code is executed in the order it is written. It would encourage code that is more declarative. It builds an execution graph (a long chain of futures) that leads up to the specific values that the program wants to calculate instead of waiting for long-running operations to complete before continuing with the next steps.
With this kind of future, future- and value-types would become interchangeable. A function
function calculateTax(person: { income: number; taxRate: number }) {
return person.income * person.taxRate
}could process a Future<Person> just like a Person and the compiler or runtime of the language could take care of whether the operations are sync or async. Therefore it would not make sense to explicitly declare functions as async anymore solving the function coloring problem. What would remain is some await operator that allows the programmer to force the program to wait for a specific value before it continues execution.
At the end of these chains (sometimes running across machines / HTTP requests etc) there will usually be some kind of UI component that shows the results to a human. UI frameworks like React could then deal with actually waiting for the data and take care of correctly rendering a declaratively defined loading state, taking the burden of orchestrating waiting for the data from the developer.
Try it out
While to my knowledge no language exists that does this (Haskell is probably as close as it gets) it is possible to get halfway there with Typescript. I've implemented a proof of concept library called Lact that implements this pattern. It is used as
const users = lacy(getUsers() /* Promise<User[]> */ ) /* LacyPromise<User[]> */
const userNames = users.map(user => user.name) /* LacyPromise<string[]> */
const userNamesAsRegularPromise = userNames.$ /* Promise<string[]> */
const userNamesAsPlainString = await userNames /* string[] */Summary
The idea of truly async-first programming is still more of a thought experiment than a finished solution — but I think it points in a direction worth exploring.
The async/await model we have today is a great improvement over callbacks and threads, yet it still forces developers to think in terms of sequencing. A language or runtime that treats futures as first-class values, making sync and async code genuinely interchangeable, could free us from that mental overhead entirely.
If you find the concept interesting or want to play with it, give lacy a try and let me know what you think.
