Hacker News new | ask | show | jobs
by dgb23 1153 days ago
I'm an autodidact and a big fan of state machines (specifically mealy machines).

They can easily be expressed as plain data structures of three layers that map the name of a state to possible inputs/events to the appropriate name of the subsequent state. Then you only need code / functions for each transition (from state, to state) to generate effects. This data driven pattern is very straight forward to implement and easy to reason about.

I learned it from hobby game programming, especially its application and usefulness. It comes up in lectures/books, sure, but generally people tend to vastly underestimate its applicability and instead smear state control all over their code, regardless of their education.

1 comments

do you know any examples of code that showcase this pattern really well?

every example I've seen (probably toy examples from articles) felt like way too much abstraction had been applied over the problem.

This response is a bit late sorry. I fully agree. Most programming languages already give you enough tools to write very simple and powerful state machines.

This works best with languages that have first class support for data literals and maps (or equivalent), such as JS, Clojure, etc.

But if you care a ton about performance you can encode the same with say enums and switch statements or similar. It's just a bit more work and you can't change the machines on the fly or generate them from data, which is a very powerful pattern you can put on top of this. But in many cases that's not needed.

---

You first define a map of states, where each key is the name of the state and each value is a map from event names to state names.

Say you have an on off toggle (very simple example):

On => Toggle => Off

Off => Toggle => On

Where On/Off are state names and Toggle is an event name.

Then, you define a map from two states to a function. We call them transition functions. Like so:

[On Off] => func()...

[Off On] => func()...

Inside the functions you would put side-effects directly (such as activating a light or changing a color, sending an email etc.), or maybe just describe side effects so another part of your program can handle them (which can be useful with larger programs as it makes testing easier).

Lastly, you define a state machine function that runs your program like so:

func(state, event) => if event is handled: trigger transition function and return new state, else: return current state

In many cases you want to simply ignore events that aren't handled in the current state of your state machine (like described above).

Sometimes though, you want to buffer events that are currently not handled in a queue and handle them later; that's a niche use-case in order to manage async events in a specific order.