What are use cases when Finite state machines should not be used?
MUST NOT USE in THESE CASES
Must not use because ______
MAY USE or MAY NOT use
Pro of using FSM are : (a) ____ (b) ______ (c) _____
Cons of using FSM are : (a) ____ (b) ______ (c) _____
MUST USE in THESE cases
because ____
This is a general discussion to gauge where are FSM’s useful. I am new to this concept and trying to understand if I should refractor some of existing code to use FSM’s.
You’ll get significantly better responses, IMO, if you ask about your specific use-case versus sifting through mountains of speculation. That said, here’s a small pile of speculation…
Must not use:
things where the space of states is infinite. You wouldn’t use an FSM for “current account balance”
Pros of using:
FSMs provide a shared vocabulary for talking about complicated code. Snazzier libraries will even let you run the documentation!
Cons of using:
using an FSM with the wrong “kind” of implementation can be painful. For instance, using something compile-time like :gen_statem or Finitomata (linked above) but with states/transitions defined at runtime
an FSM is good at enforcing business rules in software like “do X then Y”, but that can cause friction at a company where the real-world process is “do X then Y, unless it’s Tuesday then do Z first instead and then Y and then X”
“Must use”:
It depends on what you define as “an FSM”: is a “draft” boolean that starts out false and is deliberately set to true later an FSM? It has two states and a transition between them.
I think any time you have to model data with 3 more more exclusive states, you are writing a FSM. So I take this question to essentially be one of implementation, and specifically whether to use libraries or not.
I used FSM libraries heavily in Rails, the most recent legacy app I rewrote was just using timestamps to determine states which was very messy, difficult to reason about, and bug prone. Converting to proper/explicit state machines was a no brainer for me. I think there was briefly a trend against FSMs for whatever reason, I remember reading this article at some point. It skims the Rails history topic of observers which essentially was part of the perennial struggle with the type of callback hell that could affect Rails apps. But again, I don’t think there is any avoiding state machines in most apps of any significant complexity.
I feel like I end up writing a version of this on every other thread I reply on, but one of the things I deeply appreciate Phoenix and Ecto’s design in particular is that, in forcing you to be a bit more explicit in how you handle this kind of logic, and arming you with the context/schema separation off the bat, these kinds of issues I faced constantly as a Rails dev are much weaker threats. So far I have just been using context functions with pattern matching to control transition logic, as @sasajuric advises in the disclaimer of the top of his fsm library, which is I found when I first went looking for a way to handle this logic in Phoenix apps. So far that has been working well for me, although of course as others have mentioned above it can get a bit sticky when there is a lot of conditional logic around transitions. But in my experience libraries can only help so much with that kind of complexity since it is unique to each application. Basically I find the situation comparable to the factory pattern, where the small benefit in terms of reduced boilerplate and enforced consistency that you get from a 3rd party lib is not worth the simplicity of just sticking with POCFs (plain old context functions).
This is what I can’t understand (but I also don’t understand FSM fully). AFAIU, FSM is a definition for allowed states, each having allowed list of events to transition into another state (optionally with state transition callbacks) — thus helping to prevent unexpected state transitions and/or undefined behavior when unforeseen events happen to some states.
I don’t understand how regular functions with clause per different pattern match resemble a FSM. To me they’re regular functions, I don’t see a connection to FSM, and do not understand how such functions make state transitions as clear and robust as using a “proper” FSM
Currently I program a thing with few states and few rules to transition between them. I used regular code (functions with guards and pattern matching), and I use “functional core imperative shell” pattern. To drive the core I use genserver, core defines a proper general flow and it does not allow invalid operations by producing error responses. I guess regarding errors FSM would work similarly as it also would have no choice other than producing errors for invalid operations
But genserver also has important rules (timer related) as to when to call which core function (it is basically doing some work automatically), so not all logic is encoded in the core. If I code genserver correctly, then there should be no :error responses from the core during this automatic work (despite that I handled erroneous calls in the core), because correct operations would happen in the correct time. But I had irrational urge to match on :error core responses in the genserver and was puzzled as to what exactly should I do when they happen, because they should not happen and their occurrence has no meaning in overall system, considering I wrote the proper genserver logic — so I just returned unmodified genserver state on :error matches.
But I started to think how FSM maybe could prevent me from having this urge to match on “impossible” :error responses from the core in a genserver by somehow making such responses truly impossible, so I wouldn’t need to even try to handle them. They only can be impossible if I code genserver logic completely correct. And this is where I wondered if FSM can help (maybe also improving clarity). Maybe I was wrong on this, because pure functional FSMs are preferred, while this is impure genserver, or because I wanted to introduce FSM without the “real need”