← Flight log

Compiling a cell

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:

  1. Evolution. A construct that costs the host fitness will get mutated into uselessness within generations. Your program deletes itself.
  2. 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.
  3. 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.

← All entries Reply by email →