Functional State Machines
Finite state machines, sometimes simply automatons, are a mathematical model of computation. They are used to describe the behavior of systems, and are often used in computer science to model programs.
I spent a lot of times thinking about automatons because they have an inherent elegance that I admire. This article describes a functional approach to state machines in JavaScript, but you might also be interested in an imperative approach.
Finite-State Automatons
To begin with, letβs try to create a simple application that follows this automaton:
There are three states (1, 2 and 3), and three transitions (1 β 2, 2 β 3, 3 β 1).
The states can be represented as a set of functions, one for each state:
let color: string;
let label: string;
const gold = () => {
color = "gold";
label = "Magic β¨";
};
const red = () => {
color = "firebrick";
label = "Magic π";
};
const blue = () => {
color = "navy";
label = "Magic π";
};
// Set color and label to the initial state
gold();
For the transitions, we will take a declarative approach and use a data structure to describe them:
const automaton = {
// Initial state
state: "gold",
states: {
// Describe each state
gold: {
// Refer to the `gold` function defined above
enter: gold,
// The next state to transition to
next: "red",
},
red: {
enter: red,
next: "blue",
},
blue: {
enter: blue,
next: "gold",
},
},
};
How do we run this automaton? We need a function that takes an automaton and returns a new automaton. (Note that we are not mutating the automaton, but returning a new one, which is a functional approach.)
const next = (automaton) => {
// Get the current state
const state = automaton.states[automaton.state];
// Run the next state's `enter` function
const nextState = automaton.states[state.next];
nextState.enter();
// Return a new automaton with the next state
return {
state: state.next,
states: automaton.states,
};
};
And thatβs all we need for the automaton to run! We can now run our next
function in a click handler, and this is what we get:
More complex automatons
We started with a simple automaton model that only allows for a single transition between states. We will extends this model to allow for more complex transitions, based on user input.
Letβs build a simple Tamagotchi! Because I am not very aware of what Tamagotchis do, I will make up a simple model: the Tamagotchi can be happy, hungry, or doing an activity (sleeping, running or eating). The user has three buttons to have the Tamagotchi start an activity, but the Tamagotchi might refuse to do the activity if it is not in the right state.
This diagram is the automaton of our Tamagotchi: it features all the possible states, as well as the user actions that can trigger a transition. The activities automatically end after a certain amount of time.
To represent the several possible transitions from a single state, we need to replace next: "state"
with a slightly more complex structure:
const automaton = {
state: "happy",
states: {
happy: {
enter: happy,
// Enumerate all the possible next states
next: {
// transition: nextState
sleep: "sleeping",
run: "running",
},
},
// ...
},
};