The Big Idea
I became interested in Clojure a while back, and like many things that excite me, I read a couple of books, wrote a couple of programs, and never got very far. Mostly because it was never really relevant to my work, and as much as Clojure kept intriguing me, it got replaced by other things that were more important to my day-to-day.
Lately, though, Gene Kim’s Love Letter to Clojure (Part 1) got me back on the Clojure horse.
Having a couple of days off work, I got interested in refactoring the famous FizzBuzz game from an imperative style to a functional style of programming.
The idea came to me as a mesh of two videos I saw lately:
- Solving Problems the Clojure Way which was referenced by Gene Kim in his Love Letter.
- Tom Scott’s FizzBuzz: One Simple Interview Question, which popped up on my YouTube suggestions.
Feeling confident I started with Tom’s FizzBuzz implementation.
for i in range(1, 101): output = '' if i % 3 == 0: output += 'Fizz' if i % 5 == 0: output += 'Buzz' if not output: output = i print(output)
I started dealing with the usual suspects, turning lists into tuples, for loops to recursions and removing side-effects (printing) from the actual game.
I had this whole idea of explaining that functional programming is about the flow of information. How the functional FizzBuzz game is an assembly line that takes a tuple of strings as the game state and that after building a tuple of 100 strings, it lets the last robot in the assembly line print the whole game.
Because I wasn’t sure in advance how the tests should look like for the functional FizzBuzz, I started writing unit tests only after I finished refactoring. This should have been a warning sign for me that there’s something I don’t quite understand yet.
Adding unit tests actually was helping me at first. It helped me realize that the function returning the next state, should receive both the state and the next move, where before I was computing the next move and creating the new state in one function.
def get_next_state(game_state): next_move = "" next_num = len(game_state) + 1 if next_num % 3 == 0: next_move += "Fizz" if next_num % 5 == 0: next_move += "Buzz" if not next_move: next_move = str(next_num) return game_state + (next_move,)
def get_next_move(game_state): output = "" next_num = len(game_state) + 1 if next_num % 3 == 0: output += "Fizz" if next_num % 5 == 0: output += "Buzz" if not output: output = str(next_num) return output def get_next_state(game_state, next_move): return game_state + (next_move,)
Then I started writing tests to see that when I get to 3, get_next_move() returns “Fizz”. I also added tests to see that get_next_move() returns “Buzz”, “FizzBuzz”, and “1”.
I was then uncertain about a few things…
“How do I know that given the 60th move, get_next_move(game_state) returns “FizzBuzz”.
“Can I always know that given the current state and the next move, get_next_state(game_state, next_move) returns a tuple of strings that is bigger by one?”
“Can I somehow prove these things by induction?”
I think I was slightly overthinking it by that point…
The aha moment
“The ability to observe without evaluating is the highest form of intelligence.“Jiddu Krishnamurti
Realizing I must be missing something here, I remembered a presentation by Ian Cooper where he talks about the pains and cure for TDD.
What Ian Cooper humbly and clearly said, is that unit tests should test the behavior, not the implementation. And if that’s the case, then what is the behavior that we are trying to test here?
I went to the post from 2007 that started the FizzBuzz craze and read the requirements again.
Write a program that prints the numbers from 1 to 100. But for multiples of three print “Fizz” instead of the number and for the multiples of five print “Buzz”. For numbers which are multiples of both three and five print “FizzBuzz”.
Needing to test only that, I realized that to decouple the tests from the implementation, all I really needed to test is that the string that I pass to print().
Having the imperative code already, I quickly printed the correct string and created the only test needed for this game.
output_after_100 = """1 2 Fizz 4 Buzz Fizz 7 8 Fizz Buzz 11 Fizz 13 14 FizzBuzz ... """ class TestRefactored(unittest.TestCase): def test_to_100(self): self.assertEqual(fizzbuzz_refactored.play(), output_after_100)
To make the imperative code pass the test, all I needed to do was to remove the side-effect of printing, which made the program functional enough to be easily tested.
Clojure is something I’m learning because it promises to make you write smaller and easier to reason about programs. I’m hoping to be fully versed in Clojure and functional programming alike.
And from now on, when writing in other programming languages, I will strive to make my programs functional enough to be both easy to reason about, and not look whack-a-doodle batshit crazy to people who are not yet into functional programming.
Separating the test from the implementation freed me to think of another solution, which reflects my love for Python comprehensions and is now my personal FizzBuzz favorite.
def apply_logic(num): output = '' if num % 3 == 0: output += 'Fizz' if num % 5 == 0: output += 'Buzz' if not output: output = str(num) return output def play(): moves = range(1, 101) game = (apply_logic(move) for move in moves) return '\n'.join(game)
You can check out all the FizzBuzz versions + tests on the GitHub repo