If you want to program a cell the way you program a computer, the first thing you notice is that the cell is a terrible target and a fascinating one for exactly the same reasons: it’s massively parallel, stateful, noisy, and it makes copies of itself while you’re trying to reason about it.
The dream is a language where you write intent and a compiler lowers it to DNA. We are further along than people think and further away than the demos suggest.
What “source code” even is here
A genetic circuit is built from parts — promoters, ribosome binding sites, coding sequences, terminators — composed into transcription units. The foundational insight of tools like Cello was that you can treat a boolean specification as the source and synthesize a circuit that implements it.
You write something close to this:
// Sense two inputs; express output only when both are present.
module detector(input a, input b, output y);
assign y = a & b;
endmodule
…and the compiler assigns real biological gates to &, checks that their
signal ranges (their “voltage levels”, roughly) actually line up, and emits a
DNA sequence. That last check is the hard part.
Types, but for molecules
The reason you need a language and not just a CAD tool is that biology is full of invariants a type system could enforce before you ever touch a wet lab:
- Signal compatibility. A gate’s output concentration must fall inside the next gate’s input dynamic range. This is unit-checking with a biological unit.
- Orthogonality. Two parts that shouldn’t interact must not share regulatory molecules. Aliasing analysis, essentially.
- Load. Every construct you add taxes the host’s finite transcription and translation machinery. There’s a resource budget, and overspending it silently degrades everything — a very literal notion of performance regression.
A serious language for cells is one where a well-typed program can’t wire an output into an incompatible input, the same way a good type system won’t let you add a string to a socket.
Undefined behavior is not a metaphor
In C, undefined behavior means the compiler may do anything. In a cell, the “anything” is real and it fights back:
- Evolution. A construct that costs the host fitness will get mutated into uselessness within generations. Your program deletes itself.
- Context. The same part behaves differently depending on what’s next to it on the DNA. Composition is not as clean as the abstraction pretends.
- Noise. Expression is stochastic. Two genetically identical cells run the same program and get different answers.
The uncomfortable truth: our abstractions leak because the substrate is alive and has its own agenda. A cell is less like a CPU and more like a datacenter that occasionally rewrites its own source to save power.
Where this is going
The near-term win isn’t a general-purpose language; it’s good domain-specific languages with honest type systems that catch the mechanical errors — signal mismatch, part reuse, resource overrun — before synthesis. The long-term question is whether we can build abstractions robust enough that “it compiles” means “it will still be doing what you asked twenty generations from now.”
That’s the payload I keep coming back to: not just writing a phenotype, but writing one that holds.