(The code of this blog post is available on GitHub)

Last year, I participated in the Global Day of Code Retreat. On this day, developers all over the world take implement Conway’s Game of Life under various constraints (such as “TDD as if you meant it”, pair programming without talking, can’t use functions that return values, etc) in order to practice the craft of software development.

The most fascinating constraint, to me, was to use no conditionals in the code. So no if statements, no while loops, no ternaries. But the code still has to be able to make decisions! The usual solution to this would be to use a lookup table. However, I wanted to try something a bit more ambitious: let’s implement the Game of Life using nothing but functions and function calls! In fact, let’s implement the GoL in the Lambda Calculus!

Because I don’t actually have a compiler for lambda expressions, I’m going to be using JavaScript as my implementation language.

Encoding the rules

The most important operation in the Game of Life is to determine the next cell state from the current one. The rules are as follows:

A cell lives in the next generation if either:

  • It is currently alive and has 2 or 3 live neighbours
  • It is currently dead and has 3 live neighbours

We need to implement some sort of counting mechanism to be able to make this distinction for cells. I’m going to use a stack of nested functions, that get progressively “peeled off” for every live neighbour, until the outermost function can be invoked to indicate whether the next state should be alive or dead.

Every function needs to be able to do two things: either return the next function in the chain (when we’re peeling) or return the current truth value that it represents (in a way similar to Church booleans). We do this by making each object a function, that will accept a function, which will be invoked with both the next function in the chain and the liveness state that the current function represents. The function we pass in will be used to select between the two cell operations.

We define the following functions:

var dies = function(next) { 
    return function(f, alive, dead) {
        return f(next, dead);

var lives = function(next) {
    return function(f, alive, dead) {
        return f(next, alive);

var nope = function(f, alive, dead) {
    return f(nope, dead);

dies indicates that the cell would die in the next generation, lives indicates that the cell would live in the generation, and nope also indicates that the cell would die in the next generation, while also indefinitely returning itself. nope will be used to terminate the table of possible living counts. Note that nope recursively refers to itself, which is not strictly speaking legal in the Lambda Calculus, but I’m going to be hand-wavy and say that’s ok. You could implement this using the U or Y combinator if you wanted to. However, JavaScripts supports the recursive call directly so why not use it: it’ll be easier to read. If this bothers you: fair warning, I’m going to be cheating a lot more down the line :).

We can now define the two cell states, alive and dead as follows:

//            # of live neighbours
//            0    1     2      3      4+
var alive = dies( dies( lives( lives( nope ))));
var dead  = dies( dies( dies ( lives( nope ))));

As you can see, the Game’s liveness rules are quite neatly encoded here. We still need a helper function to “peel off” the outer functions and a helper function to inspect the outermust function on our stack:

var advance = function(cell) {
    return cell(function(next, val) {
        return next;
    }, null, null);

var get = function(cell, alive, dead) {
    return cell(function(next, val) { 
        return val;
    }, alive, dead);

We can now use these functions to operate on cell states:

get(alive, 'lives', 'dies')                    // 0 neighbours: 'dies'
get(advance(advance(alive, 'lives', 'dies')))  // 2 neighbours: 'lives'
get(advance(advance(dead, 'lives', 'dies')))   // 2 neighbours: 'dies'

Determining the current cell state

A cell actually needs one more operation. We not only need to be able to determine the next cell state, we also need to know the current cell state, for two reasons:

  • To be able to invoke advance on a cell for every live neighbour, we need to be able to tell if the neighbour is alive.
  • To be able to draw the current state of the board on the screen, we need to know a cell’s liveness.

We actually already have a perfectly good way to do that: we can use the chainable Church Booleans that we already have for the “next state” calculation, and stick an additional one at the front of the chain to encode this extra piece of information!

We then establish the convention that the “head” function of a cell indicates its current state, and once we peel that off, what remains is the rule table for determining the next state based on the amount of live neighbours.

Although our operators are going to be the same, I’m going to define some aliases so their distinct purposes are made clear:

var yes   = lives;
var no    = dies;
var rules = advance;

And we redefine alive and dead with the new yes and no functions at the head:

//                   # of live neighbours
//                   0    1     2      3      4+
var alive = yes ( dies (dies (lives (lives (nope)))));
var dead  = no  ( dies (dies (dies  (lives (nope)))));

Now we can go on to implement the functions that, given a board composed of cells, will produce the next state of the board.

Calculating next states

Here, I’m starting to cheat a little more. I have no intention of reimplementing arrays and numerals in the lambda calculus, so I’m just going to borrow them from JavaScript. I think this is defensible, as the meat of the Game of Life logic is definitely implemented in nothing but the Lambda Calculus. And, most importantly: no conditionals!

First, we define a function to find the neighbours of a given cell. This is fairly straightforward, given that there are always 8 neighbours (with wrap-around at the edges). To keep the amount of JavaScript arrays used to a minimum, the list of neighbours will also be a nested set of functions, which when applied to a function will perform a fold (or reduce) operation using that function:

var L = function(y, next) {
    return function(x, f) {
        return next(f(y, x), f);

var neighbours = function(x, y, board, N) {
    var cell = function(x, y) { return board[y * N + x]; };

    var x0 = (x + N - 1) % N,
        x1 = x,
        x2 = (x + 1) % N,
        y0 = (y + N - 1) % N,
        y1 = y,
        y2 = (y + 1) % N;

    return L(cell(x0, y0),
        L(cell(x1, y0),
        L(cell(x2, y0),
        L(cell(x0, y1),
        L(cell(x2, y1),
        L(cell(x0, y2),
        L(cell(x1, y2),
        L(cell(x2, y2), 

We can now write the nextCell operation: given a cell and a list of neighbours, return the state of the cell in the next generation. Using the get operation to inspect the state of a cell, we retrieve either the advance function to advance in the rule table of the current cell, or the I (identity) function to keep our position in the rule table of the current cell. Finally, we use get to return either alive or dead based on our position in the rule table:

var I = function(x) { return x; }

var nextCell = function(cell, neighbours) {
    return get(neighbours(rules(cell), function(current, acc) {
        return get(current, advance, I)(acc);
    }), alive, dead);

Finally, the function to advance the state of an entire board is fairly straightforward and not very Lambda-y at all (since it mostly iterates over an array to call nextCell on it. To make it feel at least a little functional, I’m going to use Underscore.js’s map() function.

var nextBoard = function(board, N) {
    return _.map(board, function(cell, i) {
        var x = i % N;
        var y = Math.floor(i / N);

        var n = neighbours(x, y, board, N);
        return nextCell(board[i], n);

And that’s it! We implemented Conway’s Game of Life, with not an if condition in sight, using (almost) nothing but function defintions and function calls!

Check out the complete source code on GitHub or view the online demo.