Elixir Blog Post: Finitomata :: The Proper FSM for Elixir

Blogged about the motivation and reasoning behind my idea to create yet another FSM library. Long story short: I did it in a proper way :slight_smile:

Finitomata :: The Proper FSM for Elixir

11 Likes

very interesting. the example at the end could be a bit more ‘real world’ as i’ve only heard about FSM used in games. What do you use it for?

I wanted something, that is error-prone and easy to grasp.

You probably wanted to say “not error-prone”?

2 Likes

The real-world example would have not fit the page :slight_smile:

We in Fintech use FSMs for literally everything. Consider a long-lived “transaction” when the client puts an order and saves it, in ten days comes and tries to execute it. We are to contact a bank, receive a response and either trade or contact client for why it could not be traded etc. It’s like eventlog, but for the state of the object. We must prevent the client from the attempt to execute incomplete order, or to re-execute the order rejected by bank without introducing any changes, or like. One FSM obsoletes dozens of if conditional statements.

Of course, thanks, fixed.

2 Likes

I follow your blog already; the contents are always superb. May I suggest you to add the tags in your feed? You already have a nice tagging system; it’s a shame that this tagging system is not exposed through your feed. Since you are using Atom, the standard format shall be:

<category term="elixir" />
...
2 Likes

We use state machines all the time in Control Systems. If you want to run a piece of equipment or an entire plant, you need a well defined set of states to step through (or out of).

In the BEAM world, connections to things can be handled with gen_statem nicely too.

2 Likes

I wrote this library which seems similar:

https://hexdocs.pm/state_server/StateServer.html

3 Likes

Ouch, indeed. Great one! I googled and grepped hex.pm but somehow failed to nail it down. Two independent solutions so similar is a hint it’s a proper approach.

There is one significant difference, though. The definition made of PlantUML allows a no-cost visualization, after I’ve done Make embedded FSM description visible in VSCode · Issue #2 · am-kantox/finitomata · GitHub, the diagram would be transparently visible in VSCode, and the mix task to generate the diagram will also be available soon.

Also, by hiding all the OTP stuff under the hood and exposing callbacks only, I hope I made it more beginners-friendly.

1 Like

It’s too low-level and has its own drawbacks though. I find gen_statem somewhat alongside Agent in Elixir. Syntactic sugar does not actually bring any significant improvement over gen_server, but it must be only me.

Thanks for heads up! Done.

Turned out, I’ve already written about another real example: webhooks.

1 Like

I think there’s probably a smart way to use mermaid (which is easy to ship with exdoc now) instead. Plantuml is kind of tricky syntax and while it’s open source, it’s really hard to install and put into a pipeline :P. Though at work I forced the database tables to be plantuml documented by creating a compile-time check.

2 Likes

Wow. Thanks! Somehow I missed this possibility. Will surely support it alongside PlantUML. BTW, the compile-time check is already implemented in the library during definition parsing.

FWIW, I have implemented mermaid support. This is how the documentation for the generated module looks when mermaid syntax is used. Kudos and many thanks!

1 Like

This reminds me of the FSM framework I wrote for usage in my own project.

I did however not resort to parsing a description string, I used some macros instead.

defmacro __using__(_opts) do
  quote do
    Module.register_attribute(__MODULE__, :transitions, accumulate: true)

    import     unquote(__MODULE__)

    # ensure our additional functions are added only right before compilation
    # after all module attribute values have been fully determined
    @before_compile unquote(__MODULE__)
  end
end

defmacro __before_compile__(_env) do
  quote do
    def transitions, do: @transitions
  end
end

defmacro transition(list) do
 # [...] omitted some checks for required fields for example 

  quote do
    @transitions unquote(list)
  end
end

You can then add transitions like this:

  use # "<module name>"

  transition(state: :idle, in: :start,    next: :wait)
  transition(state: :wait, in: :continue, end:  :pass)
  transition(state: :end,  in: :finish)

I ended up supporting waiting for different incoming messages, a join, end states, and “guard functions” that can further refine if a transition is taken.

The resulting list of transitions then ends up in a module attribute I can read and process.

(The part of handler functions or callbacks is not shown as it is not specific to describing the transition table. I defaulted to handle/2 as a default function for incoming functions, and allowed a user-defined handler name if needed that could be defined differently for every single transition.)

1 Like

Great! But in my understanding, the textual representation is easier to grasp and it produces a diagram in docs for free.

these examples are great. this opens up a whole new section in my head. thanks!

1 Like