I cook a lot of food from scratch and it occurred to me one day that the way I understand and assimilate recipes is very similar to how make works.

So, in case you don’t know, make is a tool used to build executable software from source code. Building software often involves several steps such as translation, compilation, linking and bundling. The process required for a particular project is describe in a Makefile but, here’s the thing, it isn’t described imperatively, it’s described declaratively.

Let’s look at an example:

1myprogram: code.c
2        gcc code.c -o myprogram

In the example myprogram is a target and code.c is a prerequisite. The target is what make is going to, well, make. The prerequisite is what is required before we can make it. In this case, the prerequisite is source code, so is always present and satisfied.

The second line tells make how to make the target from the prerequisite (OK, this bit is imperative, but we have to do something). In this case, we run a C compiler.

In this second example, more than one step is required to build the target:

1myprogram: module.o
2        gcc module.o -o myprogram
3
4module.o: module.c
5        gcc -c module.c -o module.o

Now we first require module.o and, in case it’s not already present, we show how to make module.o too which requires module.c, the source code.

Making food

So what does a food recipe look like in a Makefile? Here’s how to make a basic béchamel sauce:

1béchamel: milk roux
2        Gradually incorporate milk into roux, whisking continuously
3
4roux: butter flour
5        Combine equal parts melted butter and flour into smooth paste

We could easily extend this file to include higher-level targets, like a Mornay sauce:

1mornay: béchamel grated-cheese
2        Add grated cheese to warm sauce

More complex recipes are then natural:

1cauliflower-cheese: mornay boiled-cauliflower
2        Combine sauce and boiled cauliflower
3
4boilded-cauliflower: cauliflower
5        Separate cauliflower and boil in salted water

Implicit rules

A perhaps lesser-known feature of make is that it contains implicit rules. That is, make already knows how to make some things, mostly around C compilation. These aren’t as often used today, probably because C is no longer the only language in town.

1module: module.o

This is a valid Makefile. This works because make already knows how to make module.o from module.c and module from module.o.

It’s the same in the kitchen.

1salsa: chopped-tomato chopped-onion chopped-chilli

Some things don’t need to be written as rules. Even if you’ve never seen a chilli before, you already know to get chopped chilli from chilli: you chop it with a knife. And you already know that to make a sauce from chopped ingredients: you mix them.

Parallel execution

It’s possible to configure make to run jobs in parallel with the -j flag, for example -j4 says run up to four things at once, presumably because you have four CPU cores available. The following process can be sped up on two cores:

1myprogram: main.o module.o

This is because building main.o and module.o don’t depend on each other so can each be built immediately, as soon as a CPU core is available.

It’s the same in the kitchen. Instead of CPU cores you have burners, ovens, toasters etc. The following can be executed in parallel given one toaster and one hob:

1beans-on-toast: warm-beans toasted-bread

Cooking isn’t a script

Before I could cook myself, I used to marvel at the ability of experienced cooks to not only reproduce a dish from memory, but to seemingly make it up as they went along. To observe it would appear the script was different every time, but the result was always the same!

Now I realise how it’s possible: they weren’t memorising scripts. That would be far too hard. Imagine the hundreds of recipes and thousands of steps that would need to be remembered. Instead, human minds have a remarkable ability to organise this stuff, and I think it looks like one giant Makefile. We develop implicit rules far more comprehensive than those of make. Complex recipes are integrated by taking advantage of the redundancy in multiple layers of existing rules.

We probably even have a default target or, in English, a favourite comfort dish.

When reading recipes they are almost always written in imperative style. My approach is to read it once through and assimilate it into my global Makefile before executing it. I don’t think it’s a good idea to execute any recipe from top to bottom without reading it first. I’ll often scribble down a recipe into a pseudo-Makefile format and take that into the kitchen rather than the original text.

For the record, I don’t actually write down recipes in anything close to a strict Makefile format but, just for fun, here’s what I think a Margherita pizza recipe looks like:

 1margherita-pizza: cooked-margherita-pizza basil
 2        Add basil to top of pizza
 3
 4cooked-margherita-pizza: pizza-base tomato-sauce cheese
 5        Spread sauce on base and top with cheese, bake in oven
 6
 7pizza-base: pizza-dough
 8        Stretch dough into thin circular disc
 9
10pizza-dough: flour water salt yeast
11        Combine ingredients, knead, prove for many hours
12
13tomato-sauce: tomato
14        Peel and chop tomatoes