Hacker Newsnew | past | comments | ask | show | jobs | submitlogin
Why developers never use state machines (2011) (skorks.com)
93 points by greenie_beans on Jan 23, 2023 | hide | past | favorite | 117 comments


I've used state machines in the context of hardware design, particularly fpgas and asics. In hardware land, you use a state machine to implement something like software, where your circuit is able to remember what happened earlier so it can behave differently accordingly. It's very crude relative to their software counterparts.

They are not as common in software because software is one giant state machine already. Variables hold state, there is state within state (classes, composition etc). Sometimes entities in game design have explicit state machines for their behavior but that's to minimize complexity rather than due to absolute necessity


Speaking of CompSci people who cannot into state machines there was one time a contractor wrote some HDL that is a huge number of processes linked to each other with handshaking signals. There are zero state machines in their design. It was a pain to debug it and needless to say it was quite buggy.


I'm an EEE, and one of the strange things I've noticed is that, for whatever reason, people from a CompSci background is really, really have trouble building and understanding even simple state machines.

I suspect this unintuitiveness is the main reason why we don't see more state machines, as even in the case where they're an optimal solution many SWEs will shy away from them.

Locally-rendered UIs are probably one of the best examples of where state machines shine. State-machine based ImGuis lower complexity by orders of magnitude, yet, people still resist.


In software, it’s rare that two states can be transitioned between synchronously. Or at least, modeling a problem in terms of states that have synchronous transitions is hard. Waiting for user input, making a network request, disk IO, etc. are all asynchronous. This makes it annoying to design a state machine.

Just as an example, imagine you have two states you want to model, A and B. Let’s say the transition between A and B requires 3 async operations. You then need 8 total states: A, A_before_step_1, A_before_step_2, A_before_step3, B, B_before_step_1, B_before_step_2, and B_before_step_3. It just becomes totally unmanageable.


> This makes it annoying to design a state machine.

Annoying sure, but necessary right? With or without a state machine you have to handle all the states, and if you don't bother quantifying all possible states of your model and just winging it, it's not like those extra transition states magically disappear. Instead you just run into weird multithreading bugs and after banging your head against the wall for 5 hours you realize you forgot about a transitory state

And just to clarify, I don't mean handling every single state possible, because that's most likely infinite. But if you specify a finite number of states your app can be in, and then mark the invalid transitions to take you to an invalid state, then you can figure out when your app is in an invalid state immediately instead of having the weird bugs crop up randomly.


> Annoying sure, but necessary right?

No, not necessary. The key insight here is that the transitions are the hard part. One way to solve this is to write your transitions as a series of asynchronous steps that feed into each other. The end result is an asynchronous chain of operations.

E.g. if you have a video app, playing a video might look like:

1) Read the user metadata from disk.

2) Use the user metadata to make a network request for the video metadata.

3) Deserialize the video metadata on a background thread.

4) Present the video player with the video metadata on the main thread.

You compose each step into a chain of operations. Each step in the chain contains async work as well as logic that “cleans up” the work. So when you want to play a video, your code says “start the complicated video chain.” When you want to stop the video, your code says “stop the video chain.” The chains are a generic sequence of steps, so they can be stopped in the middle. Each step contains its own cleanup logic, so the cleanup of any pending work can happen automatically upon stopping the chain.

tldr: “state” the user is in at any given time is so complex that it’s impossible model with a state machine. It’s easier to think of cascading asynchronous steps instead. A good library for writing this kind of code is called Rx.


It's been a while since I wrote any Rx in earnest, but when I did, it was useful to look at it as almost exactly what you'd end up if you incorporated time (via ordered streams/publishers/observers/etc.) into a model that represents a state machine model.

That is to say, you can kinda both be right here. ;)


This is one that you can actually get a lot better mileage by introducing a new state machine, oddly. You have the states A and B, but you also have the state machine for the transition that is T1, T2, and T3. In this world, you have 5 things you need to be able to render/model. A, B, T1, T2, and T3. That the T things will be modeled/rendered inside either A or B can usually be irrelevant.

Even better, if you picked the right abstraction for the T state machine, that same set of modeling can be used elsewhere. (Note, no panacea. You can still pick the wrong abstractions here. Or one that just doesn't help you later, even if it isn't wrong.)

And this is exactly how it is in the physical world. The "payment acceptor" state machine of a vending machine is independent of the vending aspect of the vending machine. It is there solely to create the signal of "payment received" to the machine. Whether that comes from coins, credit cards, phone taps, whatever.


Those regexes that are ubiquitous are called regular expressions because they describe “regular languages” (qv), which are recognized by nondeterministic finite automata, which are converted to deterministic finite automata by fun with powersets, which are compiled into software state machines behind the abstraction of the regex libraries.

So they are not at all rare but not exposed to programmers explicitly.


Almost all "regexes" in programming languages are not actually regular expressions and do not have the properties you describe. (I'd argue that this is one of the clearest demonstrations that programmers do not put a lot of value of state machines)


Yes definitely a different thing when talking about ‘extended regex’ libraries, wrt to the regular languages (language class) and associates recognizers.

That said, even modern pcre uses state machines

https://github.com/rurban/pcre/blob/master/src/pcre2_dfa_mat...


Surely only if you need to name each state and define every transition. However a sparse representation would be useful here right?

If there are fundamentally transitions that are impossible (or errors) then just only define the transitions that make sense and have the rest map to a error condition if they ever come up.

Also, if the state space is factorisable (like in your example) then it is easy to represent. You never need to name every state separately if e.g. you use 2 enums (or more generally a GADT) instead of trying to fit it all into 1 variable for every possible state.


No, you need to represent every state. Like let’s say there are two async operations: a disk read, then a network request. When you transition, you need to know: did I start the disk read? If yes, cancel the disk read if it’s pending. Did the network request start? If it started but didn’t finish, then cancel it. So you can see that there is a huge amount of state you need to operate on that’s not directly captured by states A and B.


If you need recoverability from every state to A or B, then it’s not a 2-state (with intermediates) problem and you do need to go through the complexity of modelling all 6 states.

Also, the full state representation is not the problem, that’s easy enough to do with a reasonable type system. The problem is defining the transitions. But if you need the full transition matrix (or even any substantial subset), then you have a lot of things to do. But it’s things you’d have to do with a non-state machine based approach.

Realistically the only overhead with a sparse state machine representation is adding is the “catch-all” error branch you need to deal with any forbidden transitions. Everything else, you’d need to deal with anyway for normal operation. And you probably want the catch-all branch anyway, for debuggability when it breaks.


Yeah, I’ve wrestled with this, too. Usually I’ve compromised on having sub-states (Swift enums with associated types alleviate some of the pain), but still doesn’t feel quite right.


You should try Swift's Combine library. It works pretty nicely for modeling / cleaning up complex async flows.


now add concurrency, the system state is a superposition of concurrent transitions


Finite state machines are an ideal mechanism to with concurrency if you put a concurrent queue in front of them. Such a program is easily coded with Akka's FSM trait. It even adds the possibility to trigger transition based on timer. As soon as you are in the realm of time sensitive programs, i.e. because of network requests, this is a very powerful and easy to understand way to program complicated parts of programs.


for others:

https://doc.akka.io/docs/akka/current/typed/fsm.html

https://doc.akka.io/docs/akka/current/typed/persistence-fsm....

Is ^ demonstrating the equivalence between FSMs and event streams? or are they not quite identical just closely related?


I would just consider all those intermediary states as one “loading” state. It is pretty simple to have a “loading” and “error” state in addition to your other ones.


If you do that then you can't pipeline the response handling and you can't show loaded data until you fully transition to B. Any error would equate to every error.

You could have your loading state handle every little detail but then what was the point of the state machine anyway?


Can’t you still just model A and B, and have your transitioning logic compute and wait for whatever is necessary to make the full transition? If you keep the ephemeral stuff in aux memory, you can have keep your number of states more manageable.


No, because resources are limited. Like if a user indicates they want to go from A to B, and then half way thorough the transition decides they want to go back to A, then you need to cancel and clean up everything for the transition to B. Otherwise you’re wasting battery, network bandwidth, main thread cycles, etc. on useless work.


>> transition between A and B requires 3 async operations

Couldn't you do the equivalent of a join to transition to B?


They do not scale well. It is why Elm-style explicit message passing UIs are not popular. State machines work for state localised to a widget, but the moment you scale up to something moderately complex (spreadsheets, datagrids, forms etc.), your diagrams balloons in size. For non safety-critical applications, it is better to have implicit state and potential bugs than to have your engineering team waste time proving the correctness of a button when they could be building features instead.

SWEs (at least those with formal CS training) understand state-machines perfectly well. It is the kernel of regular expressions and parsing. Mapping it to a product UI just doesn't make sense all the time.


> but the moment you scale up to something moderately complex (spreadsheets, datagrids, forms etc.), your diagrams balloons in size

I'm no CS major. But isn't that what Hierarchical state machines are supposed handle? Or was that all hat no cattle and/or a lost knowledge when UML style stuff got tossed away?


Hierarchical state machine = object-oriented programming in the SWE world. You'd have one state machine in each object. It's just rare to see that because even the individual objects tend to have practically non-finite states, like counters and stuff, and just be too complex in general for FSMs.

Maybe there's some math-ey way of saying everything we do is an FSM, but I don't think of it that way.


Having worked on a large hierarchical state machine implementation (using statecharts), I can say that nested FSMs help a bit, but are still nowhere close to capturing the necessary complexity. 80% hat, only 20% cattle.


I believe the intuition of most programmers tends to stem from the development environments being designed around ease of mutable state. If you want to "add state" you just grab some variable and branch on it, done.

Thus all the instances where you want to enumerate your state come off as seeming unnecessarily formal, so the code just drives straight towards a ball of mud and the programmers, looking for a way out, look for abstract techniques to eliminate the state(pure functions, dataflow, constraints, etc.) or sweep it under the rug(objects and configuration mechanisms) instead of "cleaning their room" by drawing up a giant decision table that enumerates all possible transitions.

I've made the transition table. It works. It produces a painful "rip off the bandaid" moment in that it forces out more of the specification all at once instead of allowing to accumulate iteratively. I'm pretty sure this makes it politically undesirable in many orgs.


I was trained as an EE and occasionally use a state machine in my code. Every other time I've encountered a state machine it was also written by someone trained as an electrical engineer or similar.


I tend to use them a lot when dealing with hardware (I'm an embedded software dev, so that happens reasonably often). Most hardware devices have some sort of state machine you need to keep track of. So hardware drivers tend to model the state machine, to track what the hardware is doing.


This was my experience as well. I could've gone my entire CS education without even needing to think about state machines outside of a short discussion in a formal methods class and regex, but taking a digital logic class really opened my eyes to the power of state machines. They clarify so much of software design by making implicit and adhoc parts of the design explicit. It really is a shame that most CS degrees don't seem to give state machines the credit they deserve.


I've had the same experience ... The other fun thing that blows mind is to use Karnaugh Maps to minimize the logic needed in complex condition statements.


Usually your state is more complicated than just one enum. You'll have counters, buffers, etc.

The only time I've ever used a state machine in real code is when parsing text one token at a time or something like that. It's a useful skill to remember for those situations. And this kind of problem is very over-represented in SWE interviews.


Well yes, because state machines are identical to regular expressions... though regular expressions tend to be used quite a lot ?


Yeah, in most cases I won't even get into manual parsing and will use a regex instead. But sometimes I have to break out the FSM.


Here's a real-world example from my previous job. A process makes two simultaneous requests (we're on a tight dead line here). Here is the enumerated states we have to code for:

    send 1,2 -> receive 1, receive 2, process
    send 1,2 -> receive 2, receive 1, process
    send 1,2 -> receive 1, timeout 2, process, receive 2 (which is ignored)
    send 1,2 -> receive 1, timeout 2, process (2 is lost)
    send 1,2 -> receive 2, timeout 1, process, receive 1 (which is ignored)
    send 1,2 -> receive 2, timeout 1, process (1 is lost)
    send 1,2 -> timeout 1,2, process, receive 1,2 (which are ignored)
    send 1,2 -> timeout 1,2, process, receive 2,1 (which are ignored)
    send 1,2 -> timeout 1,2, process, receive 1 (which is ignored; 2 is lost)
    send 1,2 -> timeout 1,2, process, receive 2 (which is ignored; 1 is lost)
    send 1,2 -> timeout 1,2, process (1,2 are lost)
You might think that after the process step, we can ignore anything we receive, but we need to make sure we ignore the timed out replies, because this "state machine" is running a few dozen/hundred times a second, depending upon load.

As I was leaving, there was talk of making a third concurrent request. States start exploding here. Maybe if there were languages that made this easy to implement they would be used more often (the code in question was in a mixture of C89/C++98).


Wouldn’t be hard to make a combinator with function pointers in C.

I do it regularly. They compose well (and you can pass them state for context).


Any discussion of "state machines" is not complete without a mention of Statecharts (or Harel Statecharts) [1] [2] [3]. The hierarchy and triggered actions are a really excellent way of representing a state machine.

[1] https://en.wikipedia.org/wiki/State_diagram#Harel_statechart

[2] https://statecharts.dev/

[3] https://www.wisdom.weizmann.ac.il/~harel/papers/Statecharts....


Related:

Why Developers Never Use State Machines - https://news.ycombinator.com/item?id=20875583 - Sept 2019 (1 comment)

Why Developers Never Use State Machines (2011) - https://news.ycombinator.com/item?id=16470262 - Feb 2018 (161 comments)

Why Developers Never Use State Machines (2011) - https://news.ycombinator.com/item?id=12204038 - Aug 2016 (1 comment)

Why Developers Never Use State Machines - https://news.ycombinator.com/item?id=2949543 - Sept 2011 (92 comments)


I've used state machines twice, both times for GUIs.

It took more time to program than a bunch of messy if-else logic would, but it was well worth it. The knowledge that your GUI can't find itself in an invalid state is realy reasuring, and also it's preety easy to add new states/logic after you get over the initial hump.


Two times I can recall using them in real life were for parsers to identify MIME and CSV files. I thought it was pretty elegant, but zoom forward a few years, and I got to listen to the new guy tell everyone in scrum how he had to fix a bug in there and it was all, ugh, *weird*.


State machines are useful when the same input/event requires different handling based on the current state. There are not that many applications when this is true. Most of the time only two handlers in each state are needed, success and failure, which are much better modeled through a normal code than an explicit state machine.

At the framework level they might be pretty useful, but they rarely appear at the first version, but as a result of refactoring.


As a regular user of the state_machine Ruby gem, I wouldn't recommend it. If you don't believe me, just check out the "Class definition" section of the usage examples: https://github.com/state-machines/state_machines#usage

The problems are obvious. It's built on magic and indirection. This leads to difficult to debug state machine problems. For anything beyond simple state machines you quickly lose any idea of what your object is doing.


I’d strongly recommend Statesman instead: https://gocardless.com/blog/statesman/

I’m unaffiliated, just have used a lot of Ruby SM libraries.


Looks just as bad tbh, mixins and magic, I don't think you can really do this well in Ruby.


The answer is LabVIEW. State machines in a graphical language are a joy and very easy to work with. It’s clear what state you’re looking. At and how to trace the execution.

Most text language based state machines are a mess and difficult to reason about.

FWIW I maintain a state machine library for Ruby called wicked that (IMHO) isn’t too bad, but can still get gnarly if you’re not careful.


I think you are the first person I've seen call LabVIEW a joy ;-)


I worked at NI for two years out of college. Almost everyone in my cohort has never used it and after training most legit were convinced it was going to take over the world. Internally the juice is strong.

I ended up working as an “applications engineer” which is a glorified way to say “support”. And I saw some really gnarly code. Just literally spaghetti.

I think though the big problem with much of the bad code is that it wasn’t architected and modularized (at all). As the tool is sold as “it’s so intuitively obvious anyone can use it”

I don’t think many people train to use it the same way we were trained. That’s a big bit of the problem.

Hell is always someone else’s code, but if I every had to refactor some labview today, one of the first tools I would reach for would be state machines.


And yet they’re doing it.

This idea that modular code matters is bullshit.

Enterprise fizz buzz or spaghetti nightmare.

Learning things isn’t the point, talent is.

Speaking as someone who used to think this stuff mattered, then realized they’d just been wasting their life, instead of isomorphically rearranging thinking patterns on the deck of the trendtanic.


I'm a big proponent of state machines. They are an excellent tool for unambiguously communicating how a certain critical part of a system should work - to that end, they are not only a coding tool but also a social one to get everyone on the same page. I've wandered several times into a situation where the state is not being well-handled and while it's not often loved, getting the team to hammer out a state machine does wonders for system reliability.

It's true that they have limitations and are not always the right tool, but it's often valuable to realize that you implicitly have a state machine already whether you wish you did or not.


Same here. I am a big proponent of state machines. I have found that state machines bring sanity to an otherwise complex situation. For example, most inexperienced developers will check presence of certain fields to imply where someone is in the process whereas it would be better written using a state machine.


As someone who started in video game development...

...beyond trivial stuff like pong, the natural pattern is just a big collection of state machines (as entities) interacting with each other.


I always use state machines when writing a game. It's natural and simple. But I've never used a state machine for any other type of software.


I suppose applicability depends on the problem domain. In embedded systems that control some device I've found them very useful, particularly as the logic becomes complex. () Perhaps for other realms such as Web back ends that tend to be transactional, they are less useful.

() Many years ago I was tasked with coding a replacement for a device that operated from discrete logic involving a number of inputs, timers, decisions and actions based on all of these. The first thing I did was to model it as a state machine and everything went well except for one part where it was not clear what the next state should be based on the inputs. I reviewed this with an engineer familiar with the existing logic and he shared with me that the actual behavior of the device was indeterminate in that particular situation. That was easily fixed with the state machine.


My belief is that part of the reason state machines are not used often is:

- State machine libraries are good at expressing an existing state machine but often the workflow for upgrading or modifying it in production is lacking. If you want to remove a state for instance, it's still very hard and the tooling just makes it more obtuse.

- The reason class hierarchies as a way to model your problem are overused in most programming languages is not because it's good, but because it's a language feature that's taught by every book that covers that language. If state machines had language-level support/keywords, more people would use them.

(Interestingly, I think part of why class hierarchies can be a trap is similar to the first issue above with state machines: they are hard to change down the road as you learn more about your problem space.)


Yep, the teaching is the problem (and this generalizes). I just went and took a bit of a look at my alma mata's IT degree. It's still teaching a variety of data orientated approaches to system design. I.e. state without transitions.


I feel state machines might be better represented as validations happening behind the scenes, e.g. if my coffee machine's state would go from COFFEE_READY to CUP_PLACED then allow it, if it would go straight to COFFEE_POURING with no cup then throw an error. I'm not convinced you need explicit events, and think most of the value is in preventing nonsensical transitions between states.


That reminds me of traffic light controllers at intersections. There's often a main controller, and then also a safety monitor that simply watches for invalid combinations, such as original orthogonal traffic both getting greens.


Interesting.

I use state machines all the time, mostly in FPGA's yet also in software, embedded and beyond. Yes, you do have to develop a sense of when and how to use them.

One of my software techniques is to use a state machine to initially solve and understand a problem. Once done and working, you can start to chip away at the state machine, eliminate states and sometimes refactor the code into something faster or more efficient that completely eliminates the state machine.

The other aspect of this is that state machines can, in some cases, add a layer of safety and failure mitigation.

In the end, it really depends on the nature of the project.


Many moons ago I built a UI using a fairly basic UI toolkit (X/Motif) so I had to handle all the state of the UI menus and dialogs along with the status of the current file (new/saved/unsaved/etc). In the end when it was all working I thought to myself that I should have used a state machine.


Most people attempt to achieve Idempotence whenever possible, and there are several reasons this is desirable in HA systems. Fundamentally, the REST paradigm broke peoples brains, and set many projects on a doomed trajectory into the sun.

That being said, for some types of problems a high-latency finite state machine greatly simplifies the complexity of handling data stream consolidation efficiently. The trick is knowing when it should be avoided like in shared-state awareness problems... you know, the "lets pause the internet while I do this one op" guy we all work with sometimes... =)


> Most people attempt to achieve Idempotence whenever possible, and there are several reasons this is desirable in HA systems. Fundamentally, the REST paradigm broke peoples brains, and set many projects on a doomed trajectory into the sun.

Respectfully… wha?


If you have under 40k users, than you can get away with a lot of bad choices. =)


Sure, I’m just trying to understand the thing you previously said.


I would recommend David Schmitz's talk: https://www.youtube.com/watch?v=GWgRw5jiYy0

While I don't agree with many of his points (riffs on Erlang/Elixir are silly), there is still some good hints in the talk.

Hope it helps =)


Computers ARE state machines and the primary purpose of a programming language is to help humans manage all this state.


I’d argue the best way to do that is to write stateless code and let the compiler figure out how to make it fast


I've thought about this a bit too after coming over from a digital logic background--it would be neat to have a language that let you specify multiple concurrent blocks that need to be run for each "clock" of the program and then just let the compiler interlace them as necessary for performance. In other words, just a high level verilog with a synthesis tool that generates high performance (possibly threaded) machine code. I do think though that most programmers would hate writing code in this way since it's a very foreign way of thinking for most SWEs, but it does have promise (especially in a post transistor scaling world).


State machines sounds great in theory, but, and maybe I am unlucky, but often code that starts out as a nice state machine ends up degenerating into spaghetti code.


I agree, my experience is that state machines are great except the ergonomics of using them in programming languages tend to suck. In my experience you really want a great type system with really good inference to help you out as well. Otherwise I find they often descend into stringly typed messes despite the best intentions.


I recently designed a state machine serialization that is extremely powerful.

My problem with most state machines is that they are tied to your implementation language. And they are ad hoc.

An implementation programming language is itself a state machine - if you think of memory as being the states the machine can be in.

My state machine serialization supports and defines behaviour between concurrent threads and collections.

It defines a state machine between threads in async/await thread pool

Here is the serialization:

  next_free_thread = 2
  task(A) thread(1) assignment(A, 1) = running_on(A, 1) | paused(A, 1)

  running_on(A, 1)
  thread(1)
  assignment(A, 1)
  thread_free(next_free_thread) = 
    fork(A, B)
    |         send_task_to_thread(B,  next_free_thread)
    | running_on(B, 2)                         paused(B, 1)
                                    
    running_on(A, 1)
   | { yield(B, returnvalue) | paused(B, 2) }
      { await(A, B, returnvalue) | paused(A, 1) }
   | send_returnvalue(B, A, returnvalue)
This defined a progression of a task on a thread and that can fork to a background task and then synchronize with a yield later on


My early Swift UI app might not have worked as well as it did, had I not modeled the UI as hierarchical state machines.

But I think that a lot of the dialogue I see around state machines in programming is effectively strawperson. Much like the dialogue around DSLs in programming. People end up debating over weak examples, and then dismissing valuable ideas.


I definitely use state machines and quite like them. The benefits are real. And they're mostly quite simple.

However, there can also be complexity. Different customers inevitably want different sets of states and different behaviors on transitions. The pre-transition and post-transition logic can get complex. And it can sometimes be difficult in retrospect to figure out what happened if you didn't log and directly relate the transition event to other things (common if you're using a separate generic state machine library). And sometimes you can end up needing overrides to allow a transition from one state to another that isn't normally allowed, in order to fix bad data or mistakes.

But you can control some of that, and the state machine makes a lot of things easier to reason about.


Language tooling makes a big difference on whether state machines work well in your code or not.

Rust is awesome for state machine. The "enum" type can hold data, and modern Rust has a bunch of convenient ways for destructuring enum's.


Not sure what developers are they talking about. I was using state machines since the 90s. Even wrote business process middleware and management front end to it based on the concept.


Better question: Why do we endlessly produce so much state?


Huh? Computation can't happen without state. Everything you make has a state, otherwise it won't really do anything interesting.


Balance in all things

Edit: eh, that comment didn’t really say much. What I meant was that there are classes of algorithms that are stateless. Maybe the most useful are proofs.

Look, I’m a C++ dev. I live and breathe state. Weird, mostly-working gadgets are the real humans of the world.

But that doesn’t mean I don’t use linear types and write unit tests for my function composition. Sure, I’ll let closures capture their context, and I’ll allow pass-by-reference in my APIs, but that doesn’t mean I encourage it.


> What I meant was that there are classes of algorithms that are stateless.

Can you give me an example?


Practically speaking, any algorithm that does the same thing given the same input is said to be stateless.

This property becomes more useful when you’re trying to prove something about a function’s behavior over a bunch of well-defined types.

Languages like Haskell and Agda have these properties.

Rust also has some of these properties by default. It has an affine type system (the borrow checker) enforces some guardrails on ad-hoc state manipulation. C++ has linear-esque types in its pointers and higher-kinded types in the concepts and constraints features.


This is kind of nitpicky, but I'll say it anyway.

An algorithm that does the same thing given the same input is deterministic aka referentially transparent aka pure, not stateless.

Even this little snippet of Haskell is stateful:

    statefulFunction :: Int -> Int
    statefulFunction x =
        let y = x * x
        y + y
The binding 'y' is state. Even if it's implicit, as in ((x * x) + (x * x)), it's still state.

People seem to use "stateless" as a word for "doesn't mutate anything", which is kind of weird. Mutability has _nothing_ to do with having state.


Algorithms != computation


Then no algorithm holds state, because an algorithm is just a ruleset.


Sure. But the point is that functional languages are more declarative, holding very little control flow state. Procedural languages are the opposite.

The computation I’m talking about is whether you’re for more of a von neumann target or a lambda calc target.


Can you elaborate on your argument? Otherwise, it's a stupid question.


The argument is that we don’t compose enough functions to create value. Instead, we lean on (not always well defined) state, the result of operations on data


Nearly every app I've worked on has had some type of workflow, with an enumeration type and rules to go from one state to the next.


Woah — with all the declarative and functional languages, looks like someone has just discovered a (stateful) state machine. They’re trying to convince us that this is a useful thing in a hypothetical situation where people do ridiculous, unsafe things on an ad-hoc basis. Not sure I’m buying it.

Oh, wrong universe.

Edit:

I use state, too. I get it. I just dislike it. It doesn’t usually compose and is rarely safe.


It is not hard to argue that several FP abstractions are basically state machines themselves, their transition function is simply recursively defined.

For a trivial example, objects defining the Applicable trait.


Which abstractions?

I could see a monad being defined as a list of (typed) states with a functor. But they’re still stateless.

I could also see immutable data structures being implemented in a stateful way (they usually are in non-FP languages).


Does anyone have a recommendation for an article or similar about implementing state machines? I've only used the simple "switch on an enum" variant (switch(stateVar) case STATE1: doWork(); stateVar = STATE2; break;). I think it's pretty elegant, but it's limited to small and simple problems.


Practical Statecharts in C/C++: https://www.amazon.com/Practical-Statecharts-Quantum-Program...

Or more generically, look into Hierarchical State Machines.

There's a few insights that you can apply to your state machines that will give you most of the power you'll ever need:

1. Rather than storing an enum in your state variable, store a function pointer to state-functions.

2. Rather than have the states check outward for data to operate on, pass in a generic event type. There will still be some amount of looking outward, perfection is the enemy of good (a simple enum that the state function switch()-es on is enough to experiment with the idea).

3. Have a single entry point that takes an event and dispatches it to the states. That way you can trigger an arbitrary number of events to the state machine for any one external event (like after a transition, sending in "state exited" and "state entered" events).

4. Hierarchy: Add a mechanism by which a state-function can ignore the event, and the dispatching code sends the event to the state's parent state instead.

I think the rest that I gained from reading about and working with complex state machines is about creating good APIs for dealing with the ideas above, and to design the state diagram on paper before implementing. I rarely use the full complexity of the "QHsm" described in the book I linked, but the concepts aren't strangers and I'll often start with a switch(_state){} and sprinkle in features as needed.


Bader-Meinhoff phenomena for me here, as I read this post while hacking away at refactoring my spaghetti code in Unity into a FiniteStateMachine.

One thing I've found is that the end result can still be spaghetti with an FSM, but if you have tools to edit these things graphically, it becomes very manageable spaghetti!


I worked at a company on the front end team while a foundation team senior developer coerced the management into this whole state machine thing.

It was overly complex for what it produced.

.... We needed an API.. everything got rewritten 3 times before it was actually released because it was too complex and served zero purpose.


I use state machines, plenty of time. Some examples. Animations are natural with state machines. Iterator over trees or network graphs is easier with SM. Cursor for db result is easier with SM. Parsing is natural with SM. Games are full of SM. Workflow is a SM itself.


Where is the line between a state machines and object-oriented? One encapsulates some state and provides a set of messages to modify it. The other encapsulates some state and provides a set of transitions to change it.


The point of ""using a state machine"" is to make this explicit. Programs (and computers in general!) are already just ridiculously complex state machines. Issue is, however, that it is too complex to be reasoned about and so a simpler, explicit one on top is necessary


The last state machine I was involved with had to do with possible conditions when registering vehicles. What must be done if it was reported stolen (by NCIS) was coded in laws and regulations. What has to happen if the customer decides not to register it after all. Or they call up and go "hey! where's my title?" There were all sorts of edge cases that had to be unambiguously defined and documented. 99% of registrations followed the "happy path", but that last little bit caused all of the trouble. The printed version was an 11x17" flowchart with the "happy path" taking up less than 1/4 of it.


There is no line. An instance of an object is a state machine. Everybody is arguing over nomenclature.


I am a developer. I use state machines all the time.

I have even written code generators that auto generate hierarchical state machines. Because they are really useful for managing complex logic.


login & signup flows, batch management, request retry/backoff/error, data compliance. State machines are ridiculously common - we just don't need to import a library to do it.


Unrealscript has a notion of states built into the language.

https://wiki.beyondunreal.com/States


State machines are commonly used in game development.


I love state machines since my CS classes. I've wrote chatbot engine using this pattern; it was big success for my customer.


I love hierarchical state machines. I've used them on so many projects, they simplify the design and make it easier to modify.


I programed a State Machine app once in my career and it was much much more difficult to program than ordinary C# code development. It took a very long time to develop the app for MS sharepoint that required it many years ago. I remember calling MS support and even they could no really help me. I spoke to them for hours eventually and I got nothing done. I had to figure it out myself. I think it requires a different way of thinking than what developers are used to. It was not worth the time and effort.


Lack of first class support for them in the computer languages.


Aasm works quite well in Rails land


This is a strange take. Every last program ever written is a state machine.

The number of possible states a program represents is normally very, very large, and the transition rules are very complicated and often mixed up with, e.g., multiplier circuits. So we use familiar programming languages instead, which help to organize it all. A program emulating another state machine tends to raise the question why the (or anyway some) programming language is not being used to manage its complexity.

So in practice we emulate only the simplest of state machines explicitly. A programmer writing such a program is sometimes said to be in a state of sin.


Amusingly, I will push back that your take is equally strange. The point is clearly on a pattern of programming. Yes, programming is often creating tools that will have states. That is somewhat banal and doesn't help.

Now, I fully grant that there can easily be an explosion of states. I still find it odd to see so much pushback to trying to force a limit on the number of states that we want to support on a UI. We seem to want to allow any number of inner states to transition on a page at any time, and then we wonder at the complexity of the interfaces that we have built up.


Here is a kind of bridge from the articles POV to yours: As the OP points out, state machines are very hard to develop by stepwise refinement. A "general solution" to that problem is...the universal Turing machine--a state machine which can emulate any other state machine.

However, its really hard to program a Turing machine, so we invented assembly language, which is really hard to program, so we invented higher-level programming languages....

Basically, the programming languages we have are the best way we've figured out how to make state machines...


I was also confused then realized that Turing machines that are allowed to write on their tape are not (always equivalent to ?) state machines.

So I guess that making a state machine involves clearly separating the write-protected parts that make it from the rest of the program, and forbidding tampering ?

Also, all computing is not (directly) state machines or even Turing machines : quantum computing and generally analogic computing come to mind.

P.S.: Also, seems like (because of that restriction ?) you cannot have multithreading inside a state machine ?


Here are some comments from the perspective of computing science (i.e. not from the perspective of software development).

There is a conceptual thing called a "finite state machine", which has no memory except a set of finite states. It can, of course, "recognize" the elements of any finite set, but the question is, what infinite sets can it recognize? There is another conceptual thing called a "Turing machine". It has all the same capabilities that an FSM has, plus the ability to read from, and write to, an infinite long tape with a finite set of symbols writable to that tape. Again, the question, what infinite sets can this conceptual machine recognize? Another machine, a push down automaton, has memory capability greater than the FSM, but lesser than the TM. Again the same question.

So, yes, in this context, a TM can do anything an FSM can do, but the converse is very much not true.

That said, real life computers do not have infinite tape; they are actually very very powerful FSMs. So when we talk about the applicability of these ideas to the practical day-to-day writing of software, there are several layers of metaphor involved in why we care at all.


> Another machine, a push down automaton, has memory capability greater than the FSM, but lesser than the TM.

What you've written is correct, but someone who's not reading carefully might mistake "capability" for "capacity". Both pushdown automatons and Turing machines have infinite storage capacity, but the Turing machine can seek to any position in its infinite tape while the pushdown atomaton can only access the top of its infinite stack.


Yeah, as I understand it, this is actually more about emulating a (much more simple) FSM on a (finite, therefore pseudo-)TM ?

And so another thing that I thought would matter for software development is that you should be able to actually make the FSM write "outside of itself" without losing its FSM-ness, in the sense of not being able to later read it - which makes no sense talking generally, but does make sense in this context of emulation ?


2011 article is comparing against OOP, the FP perspective here is that we want to remove state from our systems




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: