Generator Functions

If you’ve come from python then you’re probably very used to generator functions but you don’t see them used as commonly in Javascript. When I built my canvas based implementation of Conway’s Game of Life, I thought a generator function was a perfect match for the potentially infinite generations that could be returned as the game is played out.

Before I get ahead of myself I’ll give a quick overview of what a generator function is and then go into how they can be useful in simulations/generators like Game of Life.

What is a generator function?

Even if you haven’t heard of them you might have come across this syntax before, note the * after the function keyword.

function* genFun() {
  yield "Hello World!";
}

It looks like a normal function but calling it only returns an instance of an Iterator which is where things start to get interesting. If we create an instance of the function above and log the result of gen.next() you can get an idea of how they work.

const gen = genFun();

console.log(gen.next());
console.log(gen.next());
// ▶︎ Object { value: "Hello World!", done: false }
// ▶︎ Object { value: undefined, done: true }

The first time we called .next() we got the value from yield and the second time we got undefined with done:true letting us know that the Iterator is finished. Lets see what happens when we add a couple more yields.

function* genFun() {
  yield "Hello World!";
  yield "Why am I here? What’s my purpose in life?";
  yield "Hey! What’s this thing suddenly coming towards me very fast?";
}

const gen = genFun();

console.log(gen.next());
console.log(gen.next());
console.log(gen.next());
console.log(gen.next());

// ▶︎ Object { value: "Hello World!", done: false }
// ▶︎ Object { value: "Why am I here? What’s my purpose in life?", done: false }
// ▶︎ Object { value: "Hey! What’s this thing suddenly coming towards me very fast?", done: false }
// ▶︎ Object { value: undefined, done: true }

Interesting, we can see it returns the yields in order until there aren’t any left. Now lets see what happens when we make an infinite loop that would normally make a page hang.

function* genFun() {
  let loops = 0;
  while (true) {
    yield loops++;
  }
}

const gen = genFun();

console.log(gen.next());
console.log(gen.next());
console.log(gen.next());

// ▶︎ Object { value: 0, done: false }
// ▶︎ Object { value: 1, done: false }
// ▶︎ Object { value: 2 done: false }

We can keep calling next() as many times as we want and the numbers will keep going up. Our tab doesn’t crash or run out of memory and the next loop is only started once we call next(). This is a simple example but you can probably see how we can do some interesting things with them.

As we are working with an iterator we could also use it in loops like any other iterator. Though if we do that we could have a loop inside and outside of the function and cause an infinite loop.

function* genFun() {
  let i = 1;
  while (i < 4) {
    yield i++;
  }
}

const gen = genFun();

for (const value of gen) {
  console.log(value);
}

// ▶︎ 1
// ▶︎ 2
// ▶︎ 3

Hopefully that gives a basic overview of a generator function, I’d do some more reading 🔗 about them if you’re interested as there’s some quirks and a lot more you can do like yielding a generator from inside another generator or passing values in as arguments to the next() method.

Using Generator functions for cellular automata

Cellular Automata is very similar to functional programming in that each generation is the result of a pure function (the rules) that is given the previous generation as input. 1D Cellular Automata are normally drawn with each generation in a horizontal line, these are then stacked vertically to make a still 2D image.

2D Cellular Automata are normally also drawn as a 2D image except that each generation is drawn as a frame in time which you view like an animation.

Either way both 1D and 2D need a repeating function that runs for an indeterminate time and returns a new state after each call. This sounds a lot like the loop we used above.

I won’t go into the Wolfram rules in this post and this is a simplified example, the most important thing to know about the advanceGeneration function we will be using is that it takes the current generation and returns the next generation based on the rule. We’re just using this as an example this could be any function that calculates the next state of a system based on the previous state.

function advanceGeneration(previousRow, rule) {
  const binaryRule = `00000000${Number(rule).toString(2)}`.slice(-8);
  const wolframRuleMap = { "111": 0, "110": 1, "101": 2, "100": 3, "011": 4, "010": 5, "001": 6, "000": 7 };
  const [leftPad, rightPad] = [previousRow[previousRow.length - 1], previousRow[0]];
  const nextRow = [];
  for (let column = 0; column < previousRow.length; column++) {
    const neighbourhood = [leftPad, ...previousRow, rightPad].slice(column, column + 3);
    neighbourhood[1] = previousRow[column];
    nextRow[column] = Number(binaryRule[wolframRuleMap[neighbourhood.join("")]]);
  }
  return nextRow;
}

Our generator becomes very simple. We can loop over and advance a generation each time and all the state is kept within the generator.

function* WolframGenerator(settings) {
  const { rule = 30, startingRow } = settings;
  let currentGeneration = startingRow;

  while (true) {
    yield currentGeneration;
    currentGeneration = advanceGeneration(currentGeneration, rule);
  }
}

We make a new instance with a starting row and then add as many rows as we want to the pattern.

const startingRow = [0,0,0,0,0,0,0,1,0,0,0,0,0,0,0];
const gen = WolframGenerator({ startingRow });

let patternHeight = 10;
let pattern = [];

for (let i = 0; i < patternHeight; i++) {
  pattern.push(gen.next().value);
}

When you actually draw the pattern using what ever method you are using it will look a lot better than the ones and zeros but to make the pattern a bit more readable on the console we can convert it to black and white squares before logging it.


const renderLine = (l) => l.join("")
                           .replaceAll("0", "⬛︎")
                           .replaceAll("1", "⬜︎");

console.table(pattern.map(renderLine));

//  ▶︎  ⬛︎⬛︎⬛︎⬛︎⬛︎⬛︎⬛︎⬜︎⬛︎⬛︎⬛︎⬛︎⬛︎⬛︎⬛︎
//  ▶︎  ⬛︎⬛︎⬛︎⬛︎⬛︎⬛︎⬜︎⬜︎⬜︎⬛︎⬛︎⬛︎⬛︎⬛︎⬛︎
//  ▶︎  ⬛︎⬛︎⬛︎⬛︎⬛︎⬜︎⬜︎⬛︎⬛︎⬜︎⬛︎⬛︎⬛︎⬛︎⬛︎
//  ▶︎  ⬛︎⬛︎⬛︎⬛︎⬜︎⬜︎⬛︎⬜︎⬜︎⬜︎⬜︎⬛︎⬛︎⬛︎⬛︎
//  ▶︎  ⬛︎⬛︎⬛︎⬜︎⬜︎⬛︎⬛︎⬜︎⬛︎⬛︎⬛︎⬜︎⬛︎⬛︎⬛︎
//  ▶︎  ⬛︎⬛︎⬜︎⬜︎⬛︎⬜︎⬜︎⬜︎⬜︎⬛︎⬜︎⬜︎⬜︎⬛︎⬛︎
//  ▶︎  ⬛︎⬜︎⬜︎⬛︎⬛︎⬜︎⬛︎⬛︎⬛︎⬛︎⬜︎⬛︎⬛︎⬜︎⬛︎
//  ▶︎  ⬜︎⬜︎⬛︎⬜︎⬜︎⬜︎⬜︎⬛︎⬛︎⬜︎⬜︎⬜︎⬜︎⬜︎⬜︎
//  ▶︎  ⬛︎⬛︎⬛︎⬜︎⬛︎⬛︎⬛︎⬜︎⬜︎⬜︎⬛︎⬛︎⬛︎⬛︎⬛︎
//  ▶︎  ⬛︎⬛︎⬜︎⬜︎⬜︎⬛︎⬜︎⬜︎⬛︎⬛︎⬜︎⬛︎⬛︎⬛︎⬛︎

I can imagine this running on a knitting machine making the worlds longest scarf one line at time without having to pre-calculate the entire thing.

For the 2D cellular automata we can do a similar thing except we can call gen.next() in requestAnimationFrame to get a new generation for each frame of the animation. In the Game Of Life implementation I ended up having to set a framerate limit as it was running far too quickly.

Thanks for reading I hope you found something interesting and if you didn’t know about them already I hope you are inspired to go and learn more about generator functions 🔗.

If you have any comments, suggestions or feedback about this post please do send them to me. Mention the title of the post or include a link so I know what you're talking in relation to.