A software engineer website

# TDD: optimizing for inputs vs for outputs

Gautier DI FOLCO November 22, 2022 [Code practice] #haskell #code kata #coding dojo

There is a famous code kata used during Global Days of Code Retreat called Conway's Game Of Life.

It has four rules:

1. Any live cell with fewer than two live neighbours dies, as if by underpopulation
2. Any live cell with two or three live neighbours lives on to the next generation
3. Any live cell with more than three live neighbours dies, as if by overpopulation
4. Any dead cell with exactly three live neighbours becomes a live cell, as if by reproduction

I have a love - hate relationship with this code kata.

On the positive side:

• It was one of the first code kata I have seriously worked on (especially during my first GDCR in 2012)
• It is a simple and yet challenging kata
• It shaped my TDD style (inside-out)

On the negative side:

• I have overdone it (more than 120 times)
• When I do it, I tend to be impatient and prescriptive

I'll use it to illustrate my point, which is: there are two ways to optimize a piece of code in TDD, for outputs or for inputs.

I always start by implementing the rule 4, focusing on the cell (not the grid):

``````spec :: Spec
spec = do
describe "Game of life" \$ do
it "Any dead cell with exactly three live neighbours becomes a live cell, as if by reproduction" \$

data Cell
= Alive
deriving stock (Eq, Show)

newtype Neighbours
= Neighbours { getNeighbours :: Int }
deriving newtype (Eq, Ord, Show, Num)

nextGen :: Cell -> Neighbours -> Cell
nextGen _ _ = Alive
``````

Then I implement rule 3:

``````-- ...
it "Any live cell with more than three live neighbours dies, as if by overpopulation" \$
-- ...

nextGen :: Cell -> Neighbours -> Cell
nextGen _ =
\case
3 -> Alive
``````
``````-- ...
it "Any live cell with two live neighbours lives on to the next generation" \$
nextGen Alive 2 `shouldBe` Alive
it "Any live cell with three live neighbours lives on to the next generation" \$
nextGen Alive 3 `shouldBe` Alive
-- ...

nextGen :: Cell -> Neighbours -> Cell
nextGen _ =
\case
2 -> Alive
3 -> Alive
``````

At this point, my implementation is incorrect as a dead cell with two neighbours or less should stay dead.

Moreover, in a pure TDD-style, I won't be able to add tests not passing to cover rule 1, so I add few "business" tests:

``````-- ...
it "Any Dead cell with fewer than three (2) live neighbours stays dead on to the next generation" \$
it "Any live cell with fewer than two (1) live neighbours dies, as if by underpopulation" \$
it "Any live cell with fewer than two (0) live neighbours dies, as if by underpopulation" \$
-- ...

nextGen :: Cell -> Neighbours -> Cell
nextGen x =
\case
2 -> x
3 -> Alive
``````

That's what I call optimizing for outputs: implementation does not matter, as long as you have the correct values, the production code can omit the business side (which are in the tests).

On another hand, there are what I call optimizing for inputs:

``````-- ...
describe "Reproduction (three live neighbours)" \$ do
it "Any dead cell with exactly three live neighbours becomes a live cell" \$
it "Any live cell with three live neighbours lives on to the next generation" \$
reproduction.next Alive `shouldBe` Alive
-- ...

newtype Neighbourhood
= Neighbourhood { next :: Cell -> Cell }

reproduction :: Neighbourhood
reproduction = Neighbourhood \$ const Alive
``````

Then rule 3:

``````-- ...
describe "Overpopulation (more than three live neighbours)" \$ do
it "Any live cell with more than three live neighbours dies" \$
-- ...

overpopulation :: Neighbourhood
overpopulation = Neighbourhood \$ const Dead
``````

Then rule 2:

``````-- ...
describe "Survive (two live neighbours)" \$ do
it "Any Dead cell with fewer than three live neighbours stays dead on to the next generation" \$
it "Any dead cell with exactly three live neighbours becomes a live cell" \$
survive.next Alive `shouldBe` Alive
-- ...

survive :: Neighbourhood
survive = Neighbourhood id
``````

And finally rule 1:

``````-- ...
describe "Underpopulation (zero or one live neighbours)" \$ do
it "Any Dead cell with fewer than three live neighbours stays dead on to the next generation" \$
it "Any live cell with fewer than two live neighbours dies" \$