A small breakdown of various approaches to implementing Finite State Machines

Erik Huizinga
3 min readJun 5, 2020

The issue with an FSM like this is that you can call any transition function on the machine, even though that transition is not defined for the current state! For example, fetch can only be called in the NotStarted state! Calling it in any other state is nonsensical, so how do you define what should happen then? In your implementations you just return to do nothing. This might work for functions that return Unit, but what would you return if your transition functions for some reason do return a value? (Probably uncommon, or even bad practice, but I’m sure there are use cases.)

An alternative approach is to use a great Kotlin feature to limit the transitions possible per state: extension functions. By defining the transitions as extensions on the required state type, you would e.g. define fun NotStarted.fetch(/*..*/). You use the type system and the compiler to enforce that transitions are only defined for the correct states! 🎉

There are issues with this approach though, for example:

  • What if fetch is defined for more than one state? You’d have to define the extension function multiple times, or resort to state class hierarchies to group states (which can become a true mess). Union types would solve this issue, but Kotlin doesn’t have those (find a discussion here). 😐
  • The caller of state machine functions (transitions) needs to call the functions on the correct state and therefore it must keep hold a reference to the required state. This is easy to get wrong, because ultimately an event that triggers a transition must happen while the state machine is in the correct state; only the state machine know the current state, no other entity has it. 👎

As you can see, neither approach is ideal. The easiest approach is yours: just ignore transition calls (do nothing, no-op) on an incorrect state. Do you know alternative approaches that truly model the state machine without transitions for states that do not have them?

The fundamental issue is that the events that trigger transitions can happen at random times. However, the state machine might have moved to a state for which handling such events is undefined. In other words: the event shouldn’t be handled in the first place given this state. Again, the easiest way to implement this is by doing nothing in the FSM given an incorrect state in the transition function. But maybe the event should not have been possible in the first place, given that the FSM cannot handle the transition triggered by the event.

Of course, events can happen simultaneously due to concurrency. One event may change state just before another event, that already happened, calls the transition function. Then, again, maybe it’s best to ignore the transition request. But still, that is not what a user might expect.

The state is a mutable shared resource. In Kotlin, an approach would be to encapsulate access to it through channels, to manage concurrent access. Maybe it would be possible to have state-dependent event generators (e.g. UI or I/O processes where events can emerge slowly/randomly) depend on this single source of state in such a way that they cannot produce illegal events? For example, a network call that can generate an error should be cancelled so that it will never trigger a refresh if the state is no longer Loading. Would that always be possible?

--

--

No responses yet