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