FizzBuzz with Actions and guards

FizzBuzz is a programming puzzle, easily solvable using a simple for-loop. The puzzle has been described in terms of an event driven system, in order to allow us to explore some statechart concepts. See FizzBuzz for an explanatory introduction.

Here, we will show how actions can be used to let the statechart tell us what to do. We will use guards to tell the statechart which state it should be in. Note that this is not an endorsement to use statecharts to solve FizzBuzz!

Start with digits

Let’s start with a machine that prints out only the digits.

This simple statechart re-enters the digit state every increment event.Statechart with one state, digit with a self transition on the increment event

This machine remains in the digit state, constantly re-entering itself when it’s passed the increment event. A transition that goes from one state and back to itself is called a self transition. When it re-enters itself, it invokes the digit state’s entry action. Those actions are how we’re going to ask the statechart what needs to be done.

Here is the xstate equivalent:

This statechart represents the diagram above, in xstate syntax

const statechart = {
  initial: 'digit',
  states: {
    digit: {
      on: {
        increment: 'digit'
      },
      onEntry : 'print_digit'
    }
  }
}

The statechart definition needs to be passed to the xstate.Machine function. This machine provides us with the initial state, and a way to jog the state machine (transition()). The following code goes through a for loop, which increments ‘i’, and sends the machine an event called increment.

This loop “generates” an event every iteration.

const machine = Machine(statechart);

state = machine.initialState;
for (i = 1; i < 100; i++) {
  state = machine.transition(state, 'increment', i);
}

We’ll set up a little dictionary of actions, corresponding to the actions mentioned in the statechart. At the moment, all we need is print_digit.

Any side effects should be executed after every transition().

const actions = {
  print_digit: (i) => console.log(i)
}

for (i = 1; i < 100; i++) {
  state = machine.transition(state, 'increment', i)
  state.actions.forEach(item => actions[item](i));
}

This code should print out the digits 1 through 100. In the embedded codepens I’ve replaced the console.log with document.write('<li>' + i + '</li>')

See the Pen FizzBuzz with actions and guards 1: Digits only by Erik Mogensen (@mogsie) on CodePen.

Checkpoint

We have a simple machine that has a single state. We’ve shown how a self transition can be used to re-enter a state, solely to trigger entry actions every time an event is fired.

Adding Fizz

Now let’s change the behaviour so that it prints out Fizz when the counter is divisible by 3. Printing out Fizz is a different action than printing out the digit, so our goal is to ensure that the new print_fizz action only happens when it should, and that the print_digit action is not fired.

We will introduce a new state, fizz with the print_fizz action. When the increment event happens, we will alternate between the two states, and use the onEntry actions on each state to cause the desired side effect.

The statechart diagram we’re aiming for looks something like this:

Updated diagram with new fizz state.Statechart with two states, digit and fizz with increment events passing between them

Let’s introduce the new fizz state, with its entry action print_fizz:

The new fizz state, with the print_fizz action.

states: {
  digit: {
    ...
  },
  fizz: {
    onEntry : 'print_fizz'
  }
}

The new action print_fizz should be added to the action dictionary:

The print_fizz action now in the actions dictionary.

const actions = {
  print_digit: (i) => console.log(i),
  print_fizz: () => console.log('Fizz')
}

We want to transition from digit to fizz whenever the number is incremented, and divisible by 3. So we have to change the event definitions in the digit state:

The transitions defined in the digit state.

on: {
  increment: [
    { cond: (i) => i%3==0, target: 'fizz' },
    { target: 'digit' }
  ]
}

This (the on.increment[] array) is essentially a series of if-tests, which are evaluated when we’re in this state: It can be read as follows:

In the fizz state, we know that we’ll never be in the fizz state immediately after a fizz state, so the fizz state can simply transition to digit with no checks:

The transitions defined in the fizz state.

on: {
  increment: 'digit'
}

Here’s the final code for the “fizz only” solution:

See the Pen FizzBuzz with actions and guards 1: Fizz by Erik Mogensen (@mogsie) on CodePen.

This machine has two states, and every time it gets the ‘increment’ event, depending on which state it’s in, it might evaluate the condition, and transitions to fizz or digit appropriately.

Checkpoint

We’ve shown how to use a guard condition to make a transition only happen under certain circumstances. Perhaps more subtly, we’ve shown that the behaviour can be changed by making changes to the statechart definition itself.

Adding support for Buzz

The steps involved in adding support for printing “Buzz” is somewhat similar to addding “Fizz”, but there are some important differences, which will be explained.

First of all, we need a new action, print_buzz, which lives in the action dictionary:

The print_fizz action added to the actions dictionary.

const actions = {
  print_digit: (i) => console.log(i),
  print_fizz: () => console.log('Fizz'),
  print_buzz: () => console.log('Buzz')
}

We’ll also need the buzz state, with its entry action print_buzz:

The new buzz state, invoking the right action.

states: {
  digit: {
    ...
  },
  fizz: {
    ...
  }
  buzz: {
    onEntry : 'print_buzz'
  }
}

So far, so good! On to defining the transitions. We will use another guarded transition, this time, with the guard i % 5 == 0.

We can already see that there is some redundancy, it becomes clearer in the statechart diagram:

The statechart with digit, fizz, and buzz states. The number of transitions increases noticably.Statechart with three states, digit, fizz and buzz with a total of six increment events passing between them

There are a number of transitions that are identical, and this is by definition a maintenance burden. Imagine adding support for FizzBuzz; it would require another state, but six or more transitions, many of them duplicates.

We can use the hierarchical nature of statecharts to solve this particular problem, making it both easier to describe, and easier to maintain:

The diagram ends up looking like this:

The introduction of a compound state results in a much simpler diagram.Statechart with one state and three substates, digit, fizz and buzz, with one transition each, from the superstate to each state

This is clearly a simpler model, where the logic to choose which transition to pick has been moved to the superstate. In our xstate code, the statechart is defined as follows:

The xstate definition for the statechart diagram above.

{
  initial: 'digit_fizz_buzz',
  states: {
    digit_fizz_buzz: {
      initial: 'digit',
      on: {
        increment: [
          { cond: (i) => i%3==0, target: '.fizz' },
          { cond: (i) => i%5==0, target: '.buzz' },
          { target: '.digit' }
        ]
      },
      states: {
        digit: {
          onEntry : 'print_digit'
        },
        fizz: {
          onEntry : 'print_fizz'
        },
        buzz: {
          onEntry : 'print_buzz'
        }
      }
    }
  }
}

The machine now has a single root state, digit_fizz_buzz which has

When the state machine is running, it automatically enters the digit_fizz_buzz state, which means it will react to the events defined at that level, except if a substate also defines transitions for the same events. In our case, none of the substates digit, fizz, nor buzz have any transitions defined. When any of the transitions fires, it leaves the currently active state, whatever it is, and enters the one that is the target of that transition.

Here’s the current solution, with Fizz and Buzz, but no FizzBuzz just yet:

See the Pen FizzBuzz with actions and guards 2: Fizz Buzz by Erik Mogensen (@mogsie) on CodePen.

Checkpoint

We’ve identified complexity before it crept into the code, by extracting common behaviour to a superstate, by moving the transitions from the substates to the superstates.

Handling FizzBuzz

The design we’ve chosen makes it trivial to add support for FizzBuzz when something is a multiple of both 3 and 5, since it’s “just another guarded transition”.

The statechart complete with support for printing FizzBuzzStatechart with one state and four substates, digit, fizz, buzz, and fizzbuzz with one transition each, from the superstate to each state

The xstate representation is also very similar. We’ll need a new fizzbuzz state to call the new print_fizzbuzz action:

The new fizzbuzz state

fizzbuzz: {
  onEntry : 'print_fizzbuzz'
}

And the already guarded increment transition gets another entry:

The new guarded transition to the fizzbuzz state

on: {
  increment: [
    { cond: (i) => i%3==0 && i%5==0, target: '.fizzbuzz' },
    { cond: (i) => i%3==0, target: '.fizz' },
    { cond: (i) => i%5==0, target: '.buzz' },
    { target: '.digit' }
  ]
}

Note how the guard condition in this “solution” looks a lot like the if/else statements as in the first non-statechart example shown in the introduction:

The imperative solution to FizzBuzz from the introduction.

// After (example 1)
For i = 1; i < 100; i++ {
  If i%3 && i%5 == 0, print fizzbuzz
  Else If i%3 == 0, print fizz
  Else If i%5 == 0, print buzz
  Else print digit
}

The order of these if/else statements is just as important as the order of the guarded transitions. Both of these are relatively unmaintainable, mainly because of the long chain of if/else statements.

Here’s the complete solution:

See the Pen FizzBuzz with actions and guards 3: FizzBuzz by Erik Mogensen (@mogsie) on CodePen.

Checkpoint

We’ve seen how relying on guard conditions can lead to somewhat difficult-to-maintain code. One of the goals of using statecharts is to avoid a lot of if-tests. However, the guarded increment transition is starting to suffer from the same problems. For example, the guard condition i % 3 == 0 is repeated twice in the statechart, which in itself is the start of a maintenance problem. The more subtle problem is that the order of the guard conditions is highly relevant.

Another problem with relying on guarded transitions is that the guard conditions are evaluated a lot of times. For example, if i = 1 then the statechart ends up evaluating, i % 3 == 0, then i % 5 == 0, then i % 3 == 0 again, then i % 5 == 0 again, before taking the ‘digit’ transition. For our fizzbuzz problem this really isn’t a problem, but it’s worth noting.

Conclusion

We have shown the use of guards and how a single event can cause many possible transitions to be checked, and that the first guard that passes will be taken. We have shown how actions can be placed inside states, and how entering those states triggers those actions. We showed how to perform a simple refactoring of a statechart, namely extracting common behaviour to a compound state. On a higher level, we’ve shown how actions can (should?) be used to avoid the trap of depending on the structure of the states themselves, allowing us to perform the refactoring without changing anything except the statechart.

Take a look at the introduction to see different ways of solving the FizzBuzz problem.