Hacker News new | ask | show | jobs
by RcouF1uZ4gsC 2273 days ago
In my experience, state machines are very nice in theory, but in practice, over time, they devolve into a mess of spaghetti code.

Because they are not in a single scope, loops become the equivalent of a bunch of gotos and managing lifetimes and locks becomes a problem because you can't use scope based mechanisms such as RAII.

In Rust, if you want a state machine, generators are probably the long term way to go.

https://doc.rust-lang.org/nightly/unstable-book/language-fea...

By using generators, the compiler will generate the state machine for you based on your code, and you can use structured loops and scope based cleanup.

5 comments

Resource managing is not a problem with state machines. Quite the opposite: you should acquire/free resources in well defined states of the machine (typically the different entry/exit points).

Generators are great for tasks that fit well but in the general case state machines are more powerful and easier to debug.

I agree that if your state machine starts evolving without control then it will be a mess (like any design).

Hand-coding state machines approximates hand-coding continuation passing style (CPS) transformation.

Generators are essentially similar to automatic CPS transformation.

> Because they are not in a single scope

That all depends on the tool. Despite being cross-language (or rather, because of it), Ragel's source code-level interfaces don't require indirection through callbacks or function pointers. Ragel has a rich set of operators for embedding code blocks at transition points, operators for embedding expressions to control transitions, and operators for controlling how Ragel saves/restores state. You can organize your code nearly as freely as when open-coding a solution--a million little functions, a single gigantic function, or something in between.

The problem with many tools that are tightly integrated with a language--such as in-language AST manipulation, or via lambadas or closures--is that they're constrained by the expressiveness of the host language. You can see this with Lisp macros--you can trivially hack the s-expression tree to implement incredible semantic changes, but if the problem isn't best described using simple function (and specifically s-expression) syntax, then good luck identifying the semantics on inspection or understanding the implementation.

State machines really shine in networking protocols where a state machine is part of the spec. I do agree though that when used in applications where it isn’t abundantly clear what the state machine is, or what that state machine is evolving over time that it isn’t the best abstraction.
I've had exactly the opposite experience -- I've found state machines to be "spaghetti code reducers" -- no longer checking a "bag of flags" to decide how to respond to the event.

For example, the "up" volume button on your phone could mean ringer adjust, MP3 playback volume adjust, take a photo, increase screen brightness, etc. In other words, it depends on what the phone is doing, i.e. its state.

Otherwise, code becomes "if (in_call == true) { adjust_volume(); } else if (camera_mode == true) { take_photo(); } else if ...

Just my 2 cents...