Async and IO
The redefinition: async is colorless, and what suspends is the process, not the function. There is no
async/await/Future.
The idea
Section titled “The idea”IO concurrency requires that something “wait without freezing everything”. The mainstream languages solve this
by coloring functions: async fn in Rust/JS/Python marks a function as suspendable, and that mark
propagates: whoever calls an async must be async (or await), all the way to the top. The “function
coloring” is born: half the language is one color, half another, and the two do not mix.
How Makoto redefines it
Section titled “How Makoto redefines it”There is no color. No function is async. Blocking on IO is transparent in the syntax: you write
data := -> chan or fs.read(...) as if it were synchronous, and the process suspends there. The scheduler
picks up another process while this one waits. The suspension points are explicit and few (io.*
operations, channel send/recv, the synchronous <->), but none of them appears as a type mark.
The direct consequence: parallelism is spawn of processes, not composition of Futures. To do
N things at the same time, you spawn N processes and receive the results over a channel (ch. 04). There is no
Future/await/executor to manage.
The reasoning
Section titled “The reasoning”The underlying question is: should a function’s signature say how it runs? Makoto’s position is
no. A signature says what the function does, never how it is scheduled. “Runs in a
suspendable way” is an implementation detail, and an implementation detail should not color the type. That is
the distinction that unlocks everything: contract coloring vs implementation coloring. unsafe is
contract coloring (it informs you of a real danger you need to know), and it is acceptable. async is implementation
coloring (it leaks an execution detail into the type), and it is refused. The operational difference seals the
verdict: async is uncontainable (the color rises infinitely up to main); unsafe is containable
(any layer can absorb the danger and stop the propagation). A color that does not contain itself is exactly the
kind of stain the philosophy (ch. 01) exists to prevent.
This fits into the memory and concurrency models seamlessly: since the process is the unit of execution with a single line (ch. 02/04), “suspend the process” is a natural operation, and it does not need a coroutine state machine exposed to the programmer. The runtime does the work; the language stays colorless.
Why not the alternatives
Section titled “Why not the alternatives”async/await/Future(Rust/JS/Python). Implementation coloring: it infects the type system, forces composition viaasyncblocks, and addsFutureas one more first-class type to manage. Real gain over “the process suspends invisibly”: none.- Callbacks / promises. Inversion of control, “callback hell”, manual state machines. Worse ergonomics for the same effect.
- OS threads per request. Too expensive for the grain of concurrency that the BEAM-like wants (millions of processes); and they would reopen shared memory (ch. 04 explains why not).
The concrete pain
Section titled “The concrete pain”The classic “what color is your function”: you have a perfectly good synchronous function and discover
that you need to call something async inside it. Now it has to be async, and whoever calls it too, and
so on, until you rewrite the entire tree or keep two versions of everything. The color that does not
contain itself costs real rework, and buys no correctness that the process model does not give for
free.
The mental model
Section titled “The mental model”The unit of suspension is the process. You write IO as if it were synchronous; the process stops, the scheduler moves on. Blocking is a fact, not a type mark.
The accepted friction
Section titled “The accepted friction”Colorless does not mean “everything implicit”. The <-> (synchronous request-reply) requires an explicit
timeout, a mandatory mark, unlike the silent <-. It is a deliberate asymmetry:
a deadlock between two processes waiting on each other is structural, and the deadline is the honest way out (ch. 04). And
shared-nothing has a copy/message-passing cost; Makoto accepts correctness and native-distribution
over raw shared-memory throughput.
Next: 04 · Concurrency