Build systems are spreadsheets / Ideas for free

samir : coffee → nonsense ·

9 min read Original article ↗

by Samir Talwar

Monday, 17 November 2025 at 08:00 CET

Sometimes, I have ideas, and while it’s unlikely I’ll ever pursue them, I can’t stop thinking about them. So I write them down. I hope to get them out of my head, and ideally, into the head of someone else’s, who might benefit from them. If you want to make this, or even just take one small idea from the pile, please do. It’s yours.


For many years now, I’ve been trying to create a build system.

I keep failing, because I want it to make sense, and I’ve never quite got it. I’ve made, oh, I don’t know, 7 attempts? Each one teaches me something, and yet, doesn’t do what I want it to do.

I hit on a revelation this year, though, that I’m looking forward to trying.

Build systems are just spreadsheets.1

Sheets worth spreading

Spreadsheets have been around since approximately forever, or at least longer than I. They are, at their heart, very simple, which explains why.

In a spreadsheet, you have cells, which are represented in a grid. Cells can have values, which have types, or formulas, which are computations over other cells. You can chain formulas, because they refer to cells that can themselves contain formulas, but you cannot recurse by making a cell refer to itself, or by creating cycles between cells. (Trying to do this just results in an error.)

We visualize these cells in a grid, and by default, they are all visible, and all individually modifiable.

This is a hell of a powerful programming model. One might argue that no other form of debugging has even come close to spreadsheets. You can inspect every intermediate value in production, and because long formulas are hard to maintain, you’re encouraged to break your work into small, understandable steps!

They also don’t really have a concept of “modes”, which is incredibly useful for learning. Every spreadsheet is always in its final state. Update a value, and every other cell will update accordingly; there is no “staleness”. This is functional reactive programming at its finest, and predates the term by decades.

I won’t go into much more detail here, and instead I’ll let the wonderful Felienne Hermans explain it.

There are downsides to spreadsheets, of course. It’s really hard to test them, and it’s way too easy to accidentally edit a cell in a column that’s supposed to be completely formulaic. But most importantly (to me), they are locked into this 2D grid structure (or 3D, if you hack it in using multiple sheets), and I think this is a travesty. I wish they could be free of it!

And that got me thinking… perhaps they can be?

Spread, without the sheets

I love the idea of spreading all the data around.

Let’s imagine a cell looks something like this (in a strange, TypeScript-ish pseudocode):

CellName = String

Value = null | String | Integer | Float | DateTime | etc.

Formula = {
    inputs: CellName[]
    formula: String
}

Cell = {
    name: CellName
    contents: Value | Formula
}

We could create some cells:

price = Cell { name: "price", value: 7 }
quantity = Cell { name: "quantity", value: 3 }
tax = Cell { name: "tax", value: 0.2 }
total = Cell {
    name: "total",
    value: Formula {
        inputs: ["price", "quantity", "tax"]
        formula: "price * quantity * (tax + 1)"
    },
}

And then render them, looking something like this:

Price
7
Quantity
3
Tax
0.2
Total
25.2

So far, so spreadsheet! We can make the value-based cells editable, and provide an interface for modifying the formulas too. On updating the price, the total would change immediately.

We might even invent a programming language around this paradigm:

cell price = 7
cell quantity = 3
cell tax = 0.2
cell total = price * quantity * (tax + 1)

And what we have… is pretty much analogous to a build system.

A build system, in essence

Many things that claim to be build systems are actually task runners. Task runners are useful, but that’s not what we’re describing here.

A build system works as follows: an output is computed by running a process over inputs. For example, a file might be generated by concatenating two other files. In make, possibly the most venerable build system, this looks something like this:

output.txt: one.txt two.txt
    cat $^ > $@  # cat one.txt two.txt

output.txt is processed by concatenating one.txt and two.txt using the cat program.

make works with files, and doesn’t really understand other kinds of inputs and outputs, but the principles are the same. These are cells, and the processing step is a formula. We might write it as above:

cell ./one.txt = src ./one.txt
cell ./two.txt = src ./two.txt
cell ./output.txt = cat ./one.txt ./two.txt

(Let’s pretend we’ve also invented a shell-alike that knows what its inputs are.)

When updating ./one.txt, ./output.txt will automatically update, just like a spreadsheet.

Deployment systems are build systems, with memory

I write a lot of Terraform at work. It looks a bit like this:

resource "aws_instance" "app_server" {
  ami           = data.aws_ami.ubuntu.id
  instance_type = "t2.micro"
}

This creates an AWS EC2 instance, or in other words, a virtual machine on an Amazon server, probably somewhere in the infamous us-east-1 region.

Now, this looks like a build system: when applied, it creates (builds) a VM. But it has an important difference: Terraform is a build system with state.

The definition of the VM doesn’t include any information about identity, because we can’t know its identity up-front. It gets generated by the provider; in this case, by AWS. This means that we need to store this information so that we don’t make another VM the next time we apply (build) it.

But that’s alright! No one is claiming that a formula must be a pure computation. (Well, I’m sure lots of people are, but… we shall ignore them.) If it’s alright for a formula to have side effects, as long as it is idempotent: running it twice has the same effect as running it once. This means that for any formula that modifies the outside world, it needs to record its outcome, so that the second time, it can simply re-output it without changing anything.

cell appServer = aws.instance {
    ami: aws.ami.ubuntu.id
    instanceType: "t2.micro"
}

Here, the output of the cell is the state (the ID of the instance), or null if it’s not. If we were to visualize it, we’d see the ID, and perhaps a little progress spinner until it’s deployed.

Here we can see the value of spreadsheets over traditional programming paradigms: they offer a way to preserve state across modifications. We do need to make one improvement: a formula needs to be able to access both the old and the new values of its inputs, if necessary. If it can, it can migrate from old to new with minimum fuss, in the event that completely recomputing the output is difficult or destructive, such as when modifying a running VM instance, or resizing a database.

Builds memoise; that’s what they do

Remember the Fibonacci sequence? It goes, 0, 1, 1, 2, 3, 5, 8, 13, 21…

Maybe it looks something like this:

cell fib 0 = 0
     fib 1 = 1
     fib n = fib (n - 1) + fib (n - 2)

This is pretty similar to the conventional definition, except these are cells. What does that mean? We get memoisation built-in.

After all, a cell is another way to phrase a build system, and a build system is mostly a way to make sure we don’t constantly rebuild the same thing over and over again.

Spreadsheets work the same way. When you change a value, they recompute the parts that need to be recomputed, and then persist them. They try quite hard not to compute the same thing twice.

For fun, I wrote this program, fib.py, to compute the sequence in Python:

#!/usr/bin/env python3

import sys

def fib(n):
   return n if n < 2 else fib(n - 1) + fib(n - 2)

if __name__ == '__main__':
    print(fib(int(sys.argv[1])))

Computing the 40th Fibonacci number with ./fib.py 40 takes around 10 seconds to compute 102334155 on my computer. By contrast, when I wrote this as a formula in a spreadsheet (A3 = A1 + A2, then dragged down), it computed the first 200 numbers instantly. This should not surprise anyone, but it’s a reminder that we expect our software to be smart like this, and conventional programming languages are the abnormal ones.

make works the same way. If you modify a file, it will rebuild everything that depends on it, and it will not rebuild anything else, or build anything twice.

Here’s the equivalent program, written in a Makefile:

NUMBERS := $(shell seq 2 100)

fib-0.txt:
	echo 0 > $@

fib-1.txt:
	echo 1 > $@

define FIB
fib-$1.txt: fib-$(shell expr $1 - 1).txt fib-$(shell expr $1 - 2).txt
	dc $$^ <(echo '+ p') > $$@
endef

$(foreach number,$(NUMBERS),$(eval $(call FIB,$(number))))

You can make fib-40.txt, and it will write a file named fib-40.txt with the contents 102334155. Instantly.

Testing is formatting

I don’t know about you, but I like it when my code has unit tests.

We might write some tests for our Fibonacci sequence:

cell expectedFib 0 = 0
     expectedFib 1 = 1
     expectedFib 2 = 1
     expectedFib 6 = 8
     expectedFib 10 = 55
     expectedFib 20 = 6765
     expectedFib 40 = 102334155

In a spreadsheet, we would add these in another column, and add some conditional formatting: green if the test column matches up with the actual values, and red if it does not.

In our programming language, this might look something like this:

cell testFibs =
    forall n. (exists? (expectedFib n))
        fib n == expectedFib n

We’d render the resulting boolean value green, or red, as appropriate.

This language does not exist

But maybe something like it will, if I get a minute. Or if you do.

I hope to try and implement something like this in a Scheme (such as Guile or Racket), but I make no promises.

This idea is free as in birds

If you like this idea, it’s yours. I’d be happy to discuss it with you (and please get in touch!).

You can read more ridiculous ideas by browsing the series:

  1. Functional Reactive Serverless Architecture
  2. Starting from scratch
  3. Structured archival, and the web as it once was
  4. Search is broken
  5. Prompts already won
  6. Build systems are spreadsheets
  1. Words doing a lot of heavy lifting in this sentence: “systems”, “just”, “spreadsheets”. ↩