The blog post uses a fictitious “red/blue” programming language to illustrate the pain of mixing synchronous and asynchronous functions in JavaScript‑style ecosystems. It shows how callbacks, promises, async/await, and generators each try to hide the underlying problem, and why languages with true concurrency primitives (Go, Lua, Ruby) avoid it altogether.
The straw‑man language and its "colors"
The author invents a language where every function is either red or blue. A blue function is called with a …blue suffix, a red one with …red. Red functions are harder to invoke, can only be called from other red functions, and some core library functions are forced to be red. The rules make higher‑order functions a nightmare: you must decide a color for every callback you pass around, and the colour propagates through the call chain.
What the colours really represent
The colour metaphor maps directly onto synchronous vs. asynchronous functions in JavaScript‑like runtimes:
| Colour | Real‑world analogue |
|---|---|
| Blue | Normal, synchronous functions that return a value immediately. |
| Red | Asynchronous functions that return nothing directly but invoke a callback later (or return a Promise). |
Because an async function cannot return a concrete value, a synchronous caller cannot simply use its result. This forces the caller to become async as well, propagating the “red” colour up the stack. The author’s rules therefore capture the impedance mismatch between the two worlds.
Why the rules feel painful
- Different call syntax – In reality you write
await foo()vs.foo(). The extra keyword is the “blue/red suffix”. - Calling red from blue is illegal – A sync function cannot
awaitan async one without becoming async itself. - Red functions are “painful” – Before
async/await, you had to nest callbacks, manage error‑first signatures, and remember to propagate errors manually. The author’s “prime‑line‑number” restriction is a hyperbolic way of saying each call adds boilerplate. - Core library functions are red – Node’s standard library (e.g.,
fs.readFile) historically exposed only async APIs, forcing every consumer to adopt the red style.
The evolution of work‑arounds
Callbacks → Promises → Async/Await
- Callbacks – The raw form of red functions. They require you to build a continuation‑passing style (CPS) by hand, scattering tiny anonymous functions throughout the code.
- Promises/Futures – A thin wrapper that packages a callback and an error handler into an object. They let you chain operations with
.then()but still keep the colour distinction: you receive aPromise<T>instead ofT. - Async‑await – The compiler rewrites
awaitinto the same CPS transformation the author describes, but it hides the boilerplate. The function becomes async (red) automatically, and the surrounding code looks almost synchronous.
Even with async‑await, the colour division remains: you still have Task<T>/Future<T> objects and must mark the caller as async. The underlying problem—the need to suspend and later resume execution—has not vanished.
Languages that sidestep the colour problem
The post points out three ecosystems where the red/blue split is largely absent:
- Go – Goroutines are lightweight threads managed by the runtime. An IO call simply parks the goroutine; when the operation completes, the scheduler resumes it without any syntactic change. The function appears synchronous, eliminating the colour distinction.
- Lua – Coroutines (or “fibers”) let you yield execution and later resume, again without a separate async API surface.
- Ruby – Fibers provide similar capabilities, and many libraries use them to hide async behavior behind a synchronous‑looking API.
These languages rely on multiple independent call stacks that can be paused and resumed, rather than forcing the programmer to unwind the stack manually.
The core technical issue: reifying the call stack
When an async operation is started, the runtime must discard the current C‑level call stack because the OS will invoke the callback later. To keep the logical program state, the language captures the surrounding variables in a closure (heap‑allocated). This is the CPS transformation the author mentions. The cost is two‑fold:
- Memory overhead – Every pending async step stores its environment on the heap.
- Cognitive overhead – Developers must reason about which functions are red, which are blue, and where the colour propagates.
If a language provides true suspendable stacks (green threads, goroutines, fibers), it can pause the whole stack instead of flattening it into closures, removing the need for a colour system.
Takeaways for developers and language designers
- Don’t mistake syntax for a solution.
async/awaitmakes code prettier but does not eliminate the underlying async/sync split. - Look for concurrency models that keep the call stack alive. Go’s goroutine model, Lua’s coroutines, and Ruby’s fibers demonstrate practical approaches.
- When evaluating a new language, ask how it treats IO. If the standard library forces you to choose between a red and a blue version of every function, you’ll inherit the same pain.
- Consider libraries that abstract over colour. In JavaScript, libraries like
asyncorbluebirdprovide utilities for composing callbacks, but they cannot remove the fundamental distinction.
Final thought
The “color” allegory is a clever way to surface a long‑standing ergonomics problem in callback‑centric ecosystems. While syntactic sugar (promises, async/await) reduces the visible friction, the deeper issue—how a language represents a paused call stack—remains. Languages that give you real suspendable execution contexts avoid the whole red/blue drama, and that’s why the author praises Go as the cleanest solution.
Comments
Please log in or register to join the discussion