Reading That Leaves Nothing Behind
In most computing, reading is invisible. You examine a memory address, a file, a variable — the act of looking changes nothing. The data sits there, unperturbed, ready for the next reader.
Not so in message-passing systems.
Erlang’s receive block waits for a message to arrive in the process mailbox, pattern-matches against it, and — crucially — removes it. The message is consumed by the act of reception. If you want it again, too bad. It’s gone. Like pressing paper to a gel plate: the ink transfers, the plate empties, and what remains is a ghost of what was there.
plate() ->
receive
{ink, Colour} ->
io:format("First pull: ~s~n", [Colour]),
ghost(Colour)
end.
ghost(Colour) ->
io:format("Ghost pull: faint ~s~n", [Colour]),
plate(). % ready for fresh ink
The structure mirrors the monoprinting workflow exactly. First message arrives, gets consumed, leaves a residual trace (the ghost handler), then the process returns to waiting. No message can be read twice. The mailbox doesn’t accumulate history; it accumulates futures.
JavaScript’s shift() does something similar to arrays — pops the first element and returns it, shortening the array in place:
const plate = ['vermillion', 'ochre', 'prussian blue'];
while (plate.length) {
const firstPull = plate.shift();
console.log(`Pull: ${firstPull}`);
console.log(`Ghost: faint ${firstPull}`);
console.log(`Remaining: ${plate.length} colours`);
}
Output:
Pull: vermillion
Ghost: faint vermillion
Remaining: 2 colours
Pull: ochre
Ghost: faint ochre
Remaining: 1 colours
Pull: prussian blue
Ghost: faint prussian blue
Remaining: 0 colours
This is the queue discipline of printmaking. Each colour gets one real pull, one ghost, then it’s exhausted. The array shrinks. The mailbox empties.
Destructive reads create interesting constraints for concurrent systems. If two processes try to receive the same message, only one succeeds — the other blocks, waiting for something that will never arrive. No locks needed. No race conditions. The consumption is the synchronization. Carl Hewitt’s Actor model formalized this in 1973, but the ideas matured through the 1980s as Erlang emerged from Ericsson’s telecom labs, designed around the assumption that messages are one-shot events.
The trade-off: debugging is harder when state evaporates the moment you observe it. Print-statement debugging in message-passing systems is an exercise in uncertainty — by the time you log what arrived, the system has moved on, the mailbox has changed, and the process that might have received that message instead is now blocking on nothing.
Gel plate ghost prints exist because pigment transfer is incomplete — 10–20% remains. In Erlang, the transfer is total. The message moves from sender to receiver, the mailbox loses it, and unlike the printing plate, there’s no faint second impression to pull.
Unless you explicitly copy it first. But that’s a different kind of workflow entirely.