FizzBuzz in GNU make

- hacks

When I couldn’t fall asleep last night 1 my sleep-deprived brain suddenly went “I bet you can’t write a Makefile for FizzBuzz.” While I obviously should have responded with Homer Simpson’s classic “Shut up, brain, or I’ll stab you with a Q-tip.”, it was too late, I had already nerd sniped myself. I fully expected to waste way too much time on this but it only (?) took a little over an hour to come up with a solution.

The Makefile

Without further ado, here’s the thing that should probably never have happened:

START := 1
END := 15
NUMS := $(shell seq $(START) $(END))

fizzbuzz: $(NUMS)

mod = $(shell echo $$(( $1 % $2 )))

define fb
$1:
ifeq "$(call mod, $1, 15)" '0'
	@echo FizzBuzz
else ifeq "$(call mod, $1, 3)" '0'
	@echo Fizz
else ifeq "$(call mod, $1, 5)" '0'
	@echo Buzz
else
	@echo $1
endif
endef

$(foreach _,${NUMS},$(eval $(call fb,$_)))

.PHONY: fizzbuzz $(NUMS)

Let’s see it in action:

$ make fizzbuzz
1
2
Fizz
4
Buzz
Fizz
7
8
Fizz
Buzz
11
Fizz
13
14
FizzBuzz

Since fizzbuzz is the first target we can leave it off. And we can also override START and END from the command line:

$ make START=19 END=30
19
Buzz
Fizz
22
23
Fizz
Buzz
26
Fizz
28
29
FizzBuzz

Walkthrough

So how does this work? We start by defining a bunch of variables for the start and end values of the number range we want to check:

START := 1
END := 15

Next we use the shell function to construct a sequence of numbers with seq, so NUMS will contain 1 2 3 [...] 14 15.

Now we can set up a fizzbuzz target and list all the numbers in NUM as prerequisites:

fizzbuzz: $(NUMS)

Next up, a little helper function for modulo, since make itself doesn’t support arithmetic operations. This uses the shell’s arithmetic expansion mechanism and does not rely on external programs like bc.

mod = $(shell echo $$(( $1 % $2 )))

Now for the fun part :-) First, we use define to, well, define a sequence of make expressions that we will later eval to turn them into targets:

define fb
$1:
ifeq "$(call mod, $1, 15)" '0'
	@echo FizzBuzz
else ifeq "$(call mod, $1, 3)" '0'
	@echo Fizz
else ifeq "$(call mod, $1, 5)" '0'
	@echo Buzz
else
	@echo $1
endif
endef

The code should be relatively straightforward: we refer to this block of statement as fb when calling it and $1 will receive the value of the first argument. Then we do a couple of ifeq checks 2

Now we can use foreach in combination with eval and call to define targets for all the individual numbers:

$(foreach _, ${NUMS}, $(eval $(call fb, $_)))

Last but not least, let’s declare all targets as phony to indicate that they don’t related to real files.

Summary

While I probably need better hobbies, this was a fun way to kill some time and I definitely learned a bit more about make today. When a friend (hi, Joe 👋) asked me why I did this, my answer was clear: “Because I could.” Hack on everyone and don’t forget to have fun with technology!

Updates


  1. I’ve dealt with insomnia pretty much since childhood. ↩︎

  2. I’d normally avoid the modulo 15 check but that gets awkward in make since there aren’t really any local variables. ↩︎

Feedback