Hacker News new | past | comments | ask | show | jobs | submit login
Why Developers Never Use State Machines (2011) (skorks.com)
183 points by severine on Feb 27, 2018 | hide | past | favorite | 161 comments



Traditional programming languages and imperative thinking don't lend themselves to writing state machines, so its not really that surprising that they're under utilized.

Its kind of frustrating constantly reading posts where people shit on functional programm(ers|ing) for being too ivory tower-y or whatever, when its kind of hard not to be when a lot of the older FP guys have spent the last like 20-30 years going "yo this is dope you guys should really be doing this" and getting largely ignored. State machines are super commonly used in languages where you can (easily) construct ADTs and pattern match on them.


>the older FP guys have spent the last like 20-30 years going "yo this is dope you guys should really be doing this" and getting largely ignored

I've not been ignoring them. I tried at least three different Haskell tutorials and articles explaining how cool it is. The problem was the author would show some code sand say 'see how easy it is to do this, and this, and this!', but I'd look at the code and have no idea whatever what it was doing or how it was doing it, and the guide/tutorial wouldn't tell me. After a few tries, I just gave up.

Maybe there's a canonical newbie's tutorial that takes you through the shift from imperative/procedural thinking?


Heh, very often their problem domain is also an issue.

"We're going to write a compiler in our shiny FP language".

"We're going to implement a Fourier transform".

Nah, thanks, could you show me instead how I can get the Employee record from an XML file, stick it in a PostgreSQL database and also send it down the wire through a REST API as JSON?


Haskell code for that looks quite nice as well - however, it's less about a language per se but rather about availability of libraries and how mature / well-built their API is.

For quite a few FP languages the ecosystem is there and you can do all these problem domains nicely, they just tend to be skipped in tutorials as they rely on non-core third party libraries (sometimes with multiple nice but very different alternatives). Perhaps a "batteries-included" stance of Ruby(+Rails) and Python would simplify that, but it's more of a political issue.


> It's less about a language per se but rather about availability of libraries and how mature / well-built their API is.

In the real world, those matter just as much as the language. They usually matter even more :)

Look at what Rust is doing - I think at least half of their effort is spent towards building up a large collection of high quality, well maintained (i.e. feature requests implemented, bugs fixed quickly) and well documented libraries.


I agree. For me what matters most is language implementation (compiler/VM quality, GC quality, etc) and important libraries (and whether they are good). Language itself is not very important. I can write boilerplate, but I can't write GC implementation. So while I don't like Go language, for example, its implementation is very good and I can live with it.


> Haskell code for that looks quite nice as well - however, it's less about a language per se but rather about availability of libraries and how mature / well-built their API is.

That might be the case, but you simply never see tutorials for writing a CRUD-like web application in Haskell. Maybe they exist, but they don't seem to show up very often here or on reddit or whatever.



you forgot the "oh and requirements will change randomly as you're getting employee record from xml file to etc etc


In my experience, that's really not an argument against Haskell. I find it easier to make large changes in my representation of the world in Haskell than in any other language I've worked in.


As much as I love haskell and want to say I've had those same experiences, that hasn't been my experience. Haskell forces me to write better code so often that it's painful when bad code will do just as fine.

I'm always surprised how often bad code is the solution too.. And when I say the solution, I mean to all the points along the path that I may take to get to the final destination. And I'm not saying this because you can't write this type of code in haskell but because haskell makes it more difficult and that's not always a good thing


Well, despite what we tell ourselves, I think that most code we write is actually short lived. As in, it is only used for a short time. It might be in a zombie state for many years and even be revived, if it's an Open Source project, but that's not the common scenario.

As a result, if most of the code written by most programmers is short lived and during that short life changes drastically, super-strict languages actually slow you down a lot.

The real problem is: sometimes certain parts of our code live very long and intense lives. But we never know which parts and at which moments. If we knew, we'd just write those parts in Rust/Haskell and the rest in Javascript or Visual Basic and live merrily ever after.

To paraphrase P.T. Barnum: I know that 10% of my code needs to be extremely fast, easy to refactor and readable, I just don't know which 10% :D


I agree with most of that, but I find that when my code is changing drastically is when I get the most help from my type checking, and find it most conspicuously absent when I use "less strict" languages.

I've no doubt this depends on context, on habits and approach, on skill with the languages in question, and on skill with effectively using static typing in general, so I'm not surprised that others' experiences differ.


well said thanks! probably what i might have said if i had more eloquent wording :D


> The problem was the author would show some code sand say 'see how easy it is to do this, and this, and this!', but I'd look at the code and have no idea whatever what it was doing or how it was doing it, and the guide/tutorial wouldn't tell me. After a few tries, I just gave up.

You're better off learning OCaml or F# for a small project or two, honestly. The jump from pure procedural to pure functional is big enough without also dragging in laziness, type classes, and monads.


+1 F#, really nice language


FWIW, I came to Haskell by way of OCaml, and it seems to have gone well.


What's worked for me is using Scala on real production projects and just taking it slow. First year or two I was writing Scala that looked like Java, last year or two I've been writing Scala that looks like Haskell, but because the language supports both styles I was able to learn one piece at a time and stay productive the whole time.


I don't think 'three haskell tutorials' is ever enough to teach you a completely new programming language/paradigm. You wouldn't learn C in 'three C tutorials' either, would you?

Perhaps a Haskell book with a project or two would be a better alternative?


No, but if well done, it could be enough for me to say, "I get why this is worth pursuing further." I've had the same experience as the person above: Haskell tutorials seem to say, "Look what I can do!" and not, "See how you can do this?"


Well, after a few tutorials you should have noticed that it's a high-level language with clean syntax and a compiler that produces relatively fast binaries. That's a start. What else were you expecting?


Something that shows me how this is worth spending the time learning, when I could just as easily use another high-level language with clean syntax and a compiler that produces relatively fast binaries - but that are not functional - minus the learning curve.


Well shit. I guess teaching languages designed by academicians aren't for you. Same goes for blog posts written by random amateurs on the internet. A whole three tutorials, too! You should ask for a refund.


I didn’t expect to ‘learn Haskell’, but learning anything at all about how the actual code being presented worked would have been nice.


I find OCaml is a much more accessible language for getting your head around functional paradigms.


I'm following along with Learn You A Haskell[0] and it's the only Haskell tutorial I've found that's enjoyable to read and is super easy to follow and understand. I definitely recommend it.

[0]: http://learnyouahaskell.com/


Yes, agreed.

There's the shift to functional thinking, which should not be underestimated. Next up is learning the target language syntax. Then there is the issue of gaining familiarity with the tools & libraries available.


Currently just started myself on http://haskellbook.com/, their philosophy seems to be just something for you, teaching functional programming with haskell from well defined fundamentals to make sure everything clicks. If they deliver on the promise I am yet to find out!


The erlang tutorial is pretty effective at that, IMO. The biggest problem with it is that erlang syntax is so foreign to most programmers you're going to spend enough mental effort understanding that to detract from the brainpower available for understanding the functional paradigms.


I found the opposite to be true. It's so 'foreign' that it avoids me falling into "I already know what this does" traps. Except for 'if'. Avoid 'if'.


Isn't that why Elixir came about, to be Erlang with a less esoteric interface?


Sure, but does Elixir have the exact same tutorial?


> Traditional programming languages and imperative thinking don't lend themselves to writing state machines

That's probably not the problem - imperative development can start off from a level of complexity where state machines don't look relevant.

Six weeks down the line, the whole codebase has overtaken the complexity of a state machine, though each of the development bugs were simple fixes to the original branching code.

The standard web-app wizard is where I've run into this again and again.

Every web-app is stateful enough to maintain DB state sync'd & ticking over from a user-click, which makes it really easy to work out whether all clicks go somewhere useful.

However this makes sense only when you have around 7+ states and transitions between them (particularly the "go back" one).

Until then, the regular if/else branch scenarios work out just fine ... but then when you're near the 20-30 state range, it all falls apart.


> (particularly the "go back" one).

Holy shit, once I built a web app for some biologists that wanted a "designer wizard." First I implemented a really shitty buggy wizard. Then I refactored it all into a state machine...........


That's also why state machines are common in GUI frameworks, as noted by a sibling comment above.


> State machines are super commonly used in languages where you can (easily) construct ADTs and pattern match on them.

I had the opportunity to use F# for the first time last month, and I experienced this first hand -- it was far easier to write a procedure as a sum type with transition functions than it would have been to write it procedurally. And with all the advantages of having the states of the system and their transitions laid out explicitly, why fight it?


Github?


Unfortunately no -- NDA / work-for-hire. Customer requested .NET but didn't care which language. We usually write Python, F# felt friendlier than C#. State classes looked like:

    type InitialState = {a : int; b : string}
    type IntermediateState = {a : int; b: string; c: string list}
    type FinishedState = {a: int; b: string}
    type ErrorState = {a: int; b: string; message: string}
    type State =
        | UninitializedState  
        | InitialState of InitialState
        | IntermediateState of IntermediateState
        | FinishedState of FinishedState
        | ErrorState of ErrorState
        | FinalState
And transition functions:

    let initialize (args : string list) (s:UninitializedState) : State =
        // create an initial state from args, not actually like this
        InitialState {a= 5; b="hello"}
I probably put too many types into my function signatures, but I'm new to this style.


Plenty of imperative domain spaces use state machines, gamedev and the like come to mind in particular.


Similarly with embedded development. We use them for just about everything from communications to scheduling. I think the subtext here is that state machines are just rarely used for web/mobile...


Yup, I'm in the middle of writing a packet radio protocol and it's mostly just nested state machines.


One simplistic state machine in web/mobile is mouse/swipe motion handling – mousedown transitions into "in motion" state, mousemove tracks that state, mouseup transitions into "finished motion" state, runs event handlers, and transitions again into "idle" state.


Definitely true for gamedev in my experience. I don't think I've worked in a game code base of nontrivial size without seeing some implementation of state machines.


The "some implementation" probably is part of the problem - if there was a good generic implementation included in the standard library so that it could and would be used in language tutorials (especially in languages where rolling nice state machine code is tricky), then developers would use them much more.


The problem with that is each system has slightly different requirements. Scripting state machines vs animation state machines are going to have very different constraints and end-goals.

[edit]

For example here is UE3's anim tree[1] editor vs Kismet[2].

[1] https://docs.unrealengine.com/udk/Three/AnimTreeEditorUserGu...

[2] https://docs.unrealengine.com/udk/Three/KismetUserGuide.html


What? No, that isn’t true at all. State machines come before the FP/IP divide even existed.

State machines are also intrinsically stateful, that should be obvious.


The argument is not that state machines are the child of FP, just that a common idiom in FP, Pattern Matching over ADTs, makes them easy to implement.


This might be, but state machines are so common in imperative code, you could say they are common idiom there also. Pattern matching over ADTs only drives transitions; different representation of transitions can lead to different constructs being more convenient (e.g. if your transitions are just based on a finite set of values, you can use just dictionaries).


That could very well be. My brain tends to map problems to ADTs with very low friction.


> State machines are also intrinsically stateful, that should be obvious.

So are lots of data types, but those are still useful in FP.


Yes, that should be obvious also. State machines have no strong relationship with FP. I'm not sure where this claim even comes from!


This comment reminds me of something I read recently on Yaron Minsky's twitter:

"An odd habit of functional programmers: when confronted with a nice, but clearly imperative way of structuring a program, they will often declare that this technique is in fact functional." (https://twitter.com/yminsky/status/950883335324225541)

and then

"Case in point: structuring a program as an imperative, deterministic state machine, where the state is determined fully by the state machine logic plus the sequence of transactions." (https://twitter.com/yminsky/status/950883598189686784)


In 's4vi0r’s defense, “FP” usually means ML-style FP, which does happen to be pretty biased towards creating stack-based FSMs since it has literal syntax for stacks (list literals), states (discriminated unions + tuples), and transitions (pattern matching).


>“FP” usually means ML-style FP

Only after sometime around 2010. Before that it was mostly Lisp and Scheme. Sometime around then, the trend around Haskell, purity, and co overtook them as what people mean when they talk about FP.


Traditional programming languages and imperative thinking don't lend themselves to writing state machines.

Linden Scripting Language, used for Second Life, has explicit state machines as a first-class programming construct. Programs are divided into state sections, using a keyword "state". Each state has its own local functions.


State machines are still and by far most commonly used in C, where, lo and behold, most low-level and embedded programming is still done.

And function pointers make up for pretty much the only fundamentally useful aspect of FPs regarding FSMs which is functions as first-class objects.

Everything else pretty much just substitutes one convenience for another. A pretty good case can be made about how the State Pattern makes traditional OOP indispensable in FSM design, and I'm sure that in OO languages with functions as first class objects (like Python, Lua or Javascript) people can showcase elegant designs that to them far surpass both classically OO or classically FP designs etc. etc.


> And function pointers make up for pretty much the only fundamentally useful aspect of FPs regarding FSMs which is functions as first-class objects.

I'd argue sums and pattern matching are almost as important given the simplicity of expression.


State machines are one of the most traditional programming constructs there is, and most people I've seen champion or use them are the types who would rather program in assembly than Haskell. Heck, some "imperative thinking" programmers consider state machines only slightly more exotic than a for loop, and would be similarly dismayed if a colleague didn't know how to write one.


I don't think there is a conflict between imperative thinking and state machines, but I do agree that lots of programming languages neither provide built-in features for expressing state machines nor support expressing them particularly elegantly, and that the superior support for general abstraction in certain functional languages reduce the friction of use.

It's pretty easy to imagine, though, a class based OOP language where each class was also an HSM, with methods defined per state and identifying transitions (as well as states and their entry and exit behavior being explicitly defined.)


> Traditional programming languages and imperative thinking don't lend themselves to writing state machines,

pretty sure most C introduction books cover various implementations of state machines. In "traditional" desktop WIMP GUI software it's a fairly common paradgim ; a bunch of frameworks such as StateCharts with SCXML even allow you to design the state machine graphically and have it compiled to C++ or Java code.


> a lot of the older FP guys have spent the last like 20-30 years going "yo this is dope you guys should really be doing this"

many of "the older FP guys" actually think "it's cool, it's mathematical. But it's not practical."


As a whole, a database-backed CGI server can be considered a state machine.


I would hazard a guess that there are decent state machine libraries for most languages that you can use

I think this is already part of the problem of complexity --- when I think "state machine", what comes to mind is a loop and a switch with some gotos in the cases. Definitely not a library. If you think "I need to use a state machine" and your next thought is "I need to find a library for that", then IMHO you're definitely doing it wrong.


Agreed. When I worked on last years Advent of Code problems (amazingly good problems btw) I ended up writing many solutions as small FSMs.

Typically you read a token, moved to a new position and then acted based on the current state and the token, position. You don't need a library since the code is just a few switch statements.

https://adventofcode.com/2017


And then everything ends up in Error state with no history. At least function stack has stack traces...


FSM's usually don't have error states, you tend to crash them first.

Though that is preventable, mostly because the code you run is inbetween transitions and that will return very nice stack traces.

Otherwise, you don't need a trace of a state machine, the state you're in and the current transition should tell you exactly where it's breaking.

If it doesn't, chances are you're breaking state machine definitions by storing wildly complex state outside the machine.


I really don't like state machines because if you need to add a new event, each of the states need to be updated to handle that event. If you add a new state, you have to figure out how to handle each of the transitions from other states. So as your states grow, the maintenance on the developer's side grows faster than linear.

Additionally, I find that I cannot understand how the program works without actually drawing out the state machine diagram if I come across it the first time, so there is a bit of a learning curve. It's also a nightmare to test because of all of the states that need to be tested.

So in summary, not a fan. Like recursion... if it feels natural then use it, but I don't go and try and turn stuff that isn't a SM into one or turn something that can be done as loops into recursive function for fun.


>I really don't like state machines because if you need to add a new event, each of the states need to be updated to handle that event. If you add a new state, you have to figure out how to handle each of the transitions from other states. So as your states grow, the maintenance on the developer's side grows faster than linear.

State machines have been designed to neatly capture and componentize exactly that.

Anything else is less explicit, and even more error prone.

That new even and those new transitions you've mentioned? You still need to handle them anyway -- only you do it in an informal manner without a SM.


For the life of me, I can't read your complaints as actual complaints, but endorsements of the idea.

I haven't done it, yet, but I am tempted to make everyone I work with draft out the state machine of everything we are working with in our systems. I'm half convinced the worst bugs we have, are when folks didn't realize that the change they were doing required modifications because of how far reaching they were in the state of the system.

I take a simple vending machine as a good thought exercise. If you are just changing the system that recognizes coins, you can easily localize your changes. If you are changing the system that accepts coins...


This.

How much of the anti-formalism attitude is about real difficulties of the method, and how much is about "I don't know this method, so it must be bad".

Code, regardless of what it does, is a state machine. That does not change whether you do it intentionally or not. And if you don't use state machine design tools to design them ... then your state machine becomes a huge mess with transitions going from everywhere to everywhere and very surprising connections in many places (and most/all of those will be bugs, bugs that no unit test ever is going to find).

Which seems to be acceptable for a lot of people.


Both of your concerns seem to be nicely addressed by statecharts (nesting to constrain relations + visualization): https://medium.freecodecamp.org/how-to-model-the-behavior-of...

(haven't used this yet)


> if you need to add a new event, each of the states need to be updated to handle that event

Depends on your formalism. I never use state machines of that form for exactly the reason you say. Rather, each state defines the conditions which cause a transition from it. Receiving an event in a state in which it is not expected (say, an I/O completion in a state which should not have outstanding I/O) is a straight-up hard error.


This idea of defining a hard error for a transition which makes no sense is a nice way to deal with the OPs problem. You must still explicitly handle the situation, but it minimizes the "unwind/undo" code.


I don't understand the issue very well... If you have a new event, you either have to handle it in the state machine or some other way. Any place where the event doesn't apply should cause an appropriate failure. Same with new states - you have to write those transitions in some way. You can use macros/abstractions/whatever for simplifying many cases. But none of that code really disappears when you don't use SM.


I think the concern is that the update is across the code -dispersed in the code base. But this only applies to legal events in all states else the default behavior should be invoked. If a new event that is legal in many states is created then you have to handle it everywhere anyway, and my experience that if then structures fill with bugs fast in this case


That faster than linear maintenance growth is exactly why you need SMs.

You don't need to test every scenario - only the critical positive/negative ones.

If you abstract out all the generic SM logic you can have a neat source file per SM that contains only the allowed transition mappings or do something like https://github.com/pluginaweek/state_machine where the transitions are contained behind methods.

My experience with recursion has been the reverse - the more recursion in use the less predictable the code behaves (at least initially) - but even just a little bit of SM usage can increase stability from the start and also forces you to think about all the states required.


What's wrong with drawing the state machine diagram? And generally, what's wrong with drawing your code structure?

You may be able to keep it all in your head when you write it the first time... try again when you're doing maintenance after 6 months of not touching it though.


> I really don't like state machines because if you need to add a new event, each of the states need to be updated to handle that event. If you add a new state, you have to figure out how to handle each of the transitions from other states

You still have to do all this without state machines, but likely in a less organized and harder to maintain way.


>I really don't like state machines because if you need to add a new event, each of the states need to be updated to handle that event

Depends but usually no. An event in a state which has no transition from it would be an error. The state becomes undefined and you produce a crash.

>If you add a new state, you have to figure out how to handle each of the transitions from other states.

Yes but only states that transition to this new state which is easily formalized.

>Additionally, I find that I cannot understand how the program works without actually drawing out the state machine diagram if I come across it the first time, so there is a bit of a learning curve

SM diagrams are fairly easy, they were mandatory course material in my second semester at university.

>It's also a nightmare to test because of all of the states that need to be tested.

In fact, the opposite is usually true. You can mathematically verify that your state machine will always behave exactly as expected or crash. The coffee machine won't dispense coffee and return the cash put in; the state machine in it does not allow it, even better, such a series of events becomes utterly impossible. Once the coffee has been dispensed, the machines has only one way forward: initial state.

Additionally, SMs allow you to verify that your specific implementation is the most optimal one. And if it isn't, you can easily derive it. And you can test if two independent SM implementations are equivalent to eachother with 100% certainty.

Sadly, since their state is very limited, they are usually not very useful once you want to do something that can't be expressed in a finite state machine (basically anything with threads, ever, to start with).


If you've ever do any game programming you'll be drowning in state machines.


Or control systems (which is remarkably similar to game programming, really). Basically anything that needs to model on control a system which changes state over time. Sometimes it gets a bit too much, though, and people start seeing every task as a nail for a state-machine hammer.


Any such state model is not the actual thing it models (which is almost always stateless continuous time) therefore extremely annoying to work with. Also a source of bugs and concurrency issues.


network protocol as well, hell, any non-toy network application ends up containing a state machine either implicitly or explicitly


VHDL/Verilog as well, but that's completely obvious.

I've been using state machines for Javascript UI elements.. really simplifies things.


Gesture detection/interaction here. All state machines all the time.


Life cycle management, process synchronization, lockfree data structure, user-space networking... I can't remember the last time I didn't use a state machine.


As someone who troubleshot networking issues for years, god bless you.


This. When I was learning to code by making games I was making state machines without even knowing the definition.


State machines are not a first-class concept in most currently popular high-level programming languages, so when you implement one, you're merely adhering to a design pattern.

While a design pattern is a perfectly legitimate strategy to organize code, it's a manual exercise with no help from the compiler: it's a workaround for the language of choice not being high-level enough for the concepts you're coding.

This is why frameworks useful: they intentionally constrain the problem space, so one can focus on just unique behavior. When one reaches for a framework, they're often using someone else's state machine. Game loops, GUI event loops, or network protocol states are applications of this same pattern.

In some greenfield development, frameworks have a poor reputation for removing developer control. This stems from a misunderstanding: that having a large amount of choices in organizing code is a desirable goal. It very rarely is.


In between design patterns and frameworks are the perfectly legitimate solution of libraries, which is the most common approach I've seen.


"Why Developers Should Be Force-Fed State Machines"

https://shopifyengineering.myshopify.com/blogs/engineering/1...

I always thought all computer science students had to take a compilers course, or some course on theory of computation that required some level of competency in lexing/scanning/tokenizing and parsing. This blog post appears to be directed at "web application developers" who lack "awareness about state machines".


Quite a lot of software developers were never CS students. In fact in some industries engineers are very much desired as programmers. In others, math and physics majors are desirable. And even when we exhaust all of those, I don't think there is any need to look down our noses at those who took an apprenticeship style approach to the industry. In fact, in my career I have met many CS graduates who dismissed grammars and automata as irrelevant and insisted on writing that parser for a context sensitive language using regular expressions and about 400 global variables (and they still can't understand why it doesn't always work). Besides some of us "web application developers" actually have CS degrees and have worked in half a dozen other industries besides "web application development". Programming is programming and a programmer is a programmer. There's a lot to learn and you have to stay humble your whole career. Not everybody manages it easily.


Very nicely put.

I've worked with lots of programmers over the years and a CS degree has been a very poor indicator of their ability to program in the large.


The ECMAScript standard actually has a section on "Context- Free Grammars". In that section and subsequent sections there is discussion of tokens, terminals, nonterminals and productions. If a web application developer using Javascript read these sections of the language specification and became curious enough to do a little outside research on the terminology, then perhaps it is forseeable she might encounter the notion of a "state machine".


Developers use state machines a lot more than they would admit to. Often code would be more readable if the state machine was succinctly defined.


There's a lot of truth to this statement in my experience. I just finished rewriting my homework for my operating systems course and the first version looks awful compared to when I rewrote a chunk of it as a state machine


The biggest source of resistance that I've run into is not with the concept of state machines but with common implementations. Two examples:

* A quarter-century ago I implemented a cluster membership protocol and coordination system as a state machine. This provided great benefits in terms of verifiability, extensibility, etc. but some developers complained while the current state was explicit the history of how we got there was not. Adding a history mechanism helped, but frankly it still wasn't as good as the stack traces we'd had before. While the benefits far outweighed the drawbacks, this drawback was quite real.

* On my current project, a critical component (not dissimilar to the the one in the previous example) was implemented by someone else as a state machine. Besides the fact that the semantics of that state machine are unusual and undocumented, and that this state machine also lacks a history mechanism, developers over the years have been wildly inconsistent about which actions occur on which state transitions and how other information is saved/restored "off to the side" between transitions. The result is worse than spaghetti. It's like a chunky goulash that nobody can digest, which is why we're rewriting that component from scratch.

Based on these experiences, I suggest a few rules for implementing a successful state machine.

* Make sure the state machine is properly documented - what the states mean, what the events mean, how a transition is selected when multiple might apply, etc.

* Make the control flow discoverable by providing a history mechanism.

* Make the data discoverable by capturing all relevant flags and secondary state in a structure that's passed to the code implementing transitions.

* Verify the heck out of your state machine every time it changes. Make sure that every path terminates, using timeout events as needed, and that termination includes proper resource cleanup. If appropriate, verify that multiple concurrent invocations can't race with one another and violate the invariants you've checked within each. This verification is much easier to do with state machines than with other approaches, and you should take advantage of that.


I find the biggest resistance is the obvious fact that implementing a state machine requires planning: mapping out the goals, identify which are your formal states, map out the transitions, evaluate the overall logic requirement and then design the state machine's data structure and classes... All the "planning" is the opposite of what most "code slinging macho programmer personalities" want to do; they want to jump into coding an just wing it. That is the real issue here: Undisciplined development.


One thing formal state machines make difficult is component reuse.

Let's say you have a music playback control component you wish to reuse across a feature rich player, as well as a mini playback controller.

You COULD just use the full state machine to power the mini player, but most of the transitions would be unused. It would be unclear to the next maintainer which aspects were needed for each use case, thus making the state machine brittle.

Instead, you can write an independent playback progress/state class, and reuse it in both players via composition and proper layering of functionality.

By hiding the playback state from the rest of the player and exposing control of it's state via an interface, you've gained functional reuse.

Reapply the same pattern for all components of your players and you'll find that your product is safer to change and easier to understand than if you had built a complicated formal state machine covering all possible music player use cases.


Ah... your "independent playback progress/state class" is another state machine, just better with an architecture that suits your use case. "Full state machine" is a misnomer and common misunderstanding of the power of state machines. Any state machine bordering on complexity that is hard to manage can be broken into multiple smaller, easier to grasp and manage state machines. And state machines transparently integrate with functional programming, so any ideas contrary to that is yet another misunderstanding of them.


I wouldn't consider this class a state machine in the sense described in the paper.

Any class managing state exclusively is an implicit state machine, no?


This was a nice thing in the example from Raganwald's blog post. The `transitionsTo` decorator:

  function transitionsTo (stateName, fn) {
    return function (...args) {
      const returnValue = fn.apply(this, args);
      this[STATE] = this[STATES][stateName];
      return returnValue;
    };
  }
By separating the state transitions from the state actions in this way it allows the functional components to be reused much more easily. You can construct the miniplayer using only the states you need with only the transitions you need (rather than obscured/hidden capabilities).


This is a nice approach, thank you for pointing it out.

I'd argue that you are building a separate state machine for the two players, but there is some reuse possible in this approach.


Reuse is important, but realistically, how much code is actually re-used? Sure, you have a lot of meaningful subcomponents or libraries that should be correctly isolated for re-use (and really, for testing purposes) but moving a state machine into the core of an application and away from segmented components and libraries hardly harms whatever reuse is likely to happen.


As a developer for backoffice apps (not shown in front of the clients, only our co-workers use our tools), since our team found a state-machine behavior for our framework (means easy to re-use on different project, just change the diagramm/steps conf), we cover more than 95% of the needs that was asked as 'workflow' needs.


I'll echo the observation: datacomms protocol parsing and generation are nothing but state machines.I started in that domain back in 1973 or so, and some configurations - statistical multiplexors for example - may be running upwards of 34 FSM's concurrently and independently. And that with a mix of multiple instances of one protocol. So I developed a cooperative multitasking pattern that ran on a virtual OS scheduler. Indeed in some systems it was the real OS. State variable is a function pointer to a function that returns its own protoype function pointer. Arguments include event, plus data plus state context. Supports submachines too. Still using it. No switching complex stack contexts, runs fast.


There was a reference to a Shopify blog post that 404s, the new url is https://shopifyengineering.myshopify.com/blogs/engineering/1...


State machine is useful when it is useful. I can see for certain string matching problem, internal management of certain data structure, it is going to be a handy improvement.

However, specifically in web development, it is discouraged because the nature of the application itself. Think hard how states are managed in a typical service, the real states live in a DB. Which makes state machine less useful because the ground truth is stored elsewhere, and should be updated by DB's own primitives. And in a distributed settings, failure is something you have to put into consideration. Making your service stateful is a red herring that brings all kinds of operational troubles. That is why the dominant approach people seems to agree upon is to keep application stateless while delegate all dirty work to DB.

So realistically I think state machine is useful to write certain helper routines in a non-persistent, single-thread scenario, for the ease of reasoning, but really should be limited within the lifecycle of a single API call.


Would like to read more on this subject if anyone knows any resources.


Isn't one component of this that so many people develop for web now? And that HTTP is inherently stateless.

That means we have to resort to hacks to store state (databases, cookies) - and like most hacks, those are fragile (such as assuming a single instance of state and then breaking when the user opens another copy of the site in a tab).

With the rise of single page apps, there's a more coherent concept of state on the front end, but that doesn't fix the fragility of the server side portions.


> That means we have to resort to hacks to store state (databases, cookies) - and like most hacks, those are fragile (such as assuming a single instance of state and then breaking when the user opens another copy of the site in a tab).

In principle, there's nothing fragile about the types of state you describe, but most web frameworks don't impose enough restrictions to ensure this state is used correctly [1].

[1] something like this would work, but it's not the only option: http://cs.brown.edu/~sk/Publications/Papers/Published/mk-int...


You could even go as far as saying that a class in some forms is a state machine. Networking is a huge application of state machines. Microcontrollers usually have a small state and step through a switch statement in an infinite loop. OpenGL is a state machine. If you write a state machine, it's easier to implement it "perfectly" than other methods, and other parts of your program are able to interact with it in more straightforward ways.


I rarely (if ever) use state machines explicitly, but I do use them relatively frequently to reason about the behaviour of a system. A paper tool rather than the way I actually write the code. I use flow charts and sequence diagrams in a similar way.


The state machine is very near at Design by contract pattern. There are states, preconditions and other stuff. The question now will be "Why developers never use design by contract pattern?" :)

https://github.com/nhatminhle/cofoja for example


Design by contact is a lot more general. You can have preconditions and postconditions in stateless code. And statelessness is the best way to handle concurrency. When stateful and concurrent, you get to handle outdated requests intelligently and with as little blocking as possible.


One of my go-to interview questions is "Can you tell me about a time when you've used an explicitly-modeled state machine in your programming?" Our work, heavy in embedded devices, network protocols, and parsing, is so full of state machines that I wouldn't want to hire someone who wasn't comfortable using them.


For equipment automation i am using end-state player which accelerate our development at least in 3 times. Why? There are three levels of hierarchy: conception level, detail, gui. All of these levels are independant to earch others, for example, arhitector can invented new conception with some fake elements which will be substituted in some future stages. Software developers who are making new elements (with parsers, new libraries and so on), regarding to conception above, are not intersected with conception and gui levels. In its turn gui developers use only state schema with all defined properties or data which are belong to every state.


I had to do it to implement a 16-instruction loop for the TI RM4's High-End Timers, which are programmable timers that you can offload arbitrary pulse-train generation to, among other things.

http://www.ti.com/general/docs/litabsmultiplefilelist.tsp?li...

Every instruction was a state and I had to model each of the transitions between instructions to understand the program flow.


Is it pretty hard to pick it up for an average programmer?


Nah, not at all.

It basically just means that you define states for your program to be in and you define from what state to what other state your program can go to under what conditions.

For example, let's say you have a program that takes user input and once everything is entered correctly, then you move on to the next thing.

That would mean you have a state "user_input" (or whatever you want to call it). Then you'd have a event "User clicks OK-button", with which you'd do a state transition. And then there's two possible state transitions, one which loops back onto user_input, for when the user enters something that's not correct, and one that points to the state of that next thing, with a condition of the user input being correct.

You could also directly specify here what "correct" user input looks like. That's your choice. It's a design tool, use it to whatever depth you need it.

There's a relatively intuitive notation standard, which you'll want to learn, as writing it all down is what gets you to actually think through all the states, events and conditions that there are.

Implementation-wise, you'll usually have a variable that holds your state and then methods to do state transitions. You can also implement a check into those methods to ensure you're in a state that's allowed to transition to the state that this method transitions to.


Nah, not at all.

It basically just means that you define states for your program to be in and you define from what state to what other state your program can go to under what conditions.

For example, let's say you have a program that takes user input and once everything is entered correctly, then you move on to the next thing.

That would mean you have a state "user_input" (or whatever you want to call it). Then you'd have a event "User clicks OK-button", with which you'd do a state transition. And then there's two possible state transitions, one which loops back onto user_input, for when the user enters something that's not correct, and one that points to the state of that next thing, with a condition of the user input being correct.

You could also directly specify here what "correct" user input looks like. That's your choice. It's a design tool, use it to whatever depth you need it.

There's a relatively intuitive notation standard, which you'll want to learn, as writing it all down is what gets you to actually think through all the states, events and conditions that there are.

Implementation-wise, you'll usually have a variable that holds your state and then methods to do state transitions. You can also implement a check into those methods to ensure you're in a state that's allowed to transition to the state that this method transitions to.

And even if you never end up using it, it is a nice mindset to get yourself into.


Compilers and games are two areas I've used them a few times in.


Order/bid management in markets is also a case where they shine.


Is that why orders tend to get stuck in "Delivery" state all the time?


Not sure what you're referring to?


Discussed at the time (over 90 comments):

https://news.ycombinator.com/item?id=2949543


Of course developers whose last experience with state machines was in a college course won’t use them. Just read some of the comments below the article on storks.com. Labview developers use state machines extensively, because the implementation is graphical, beautiful, and prevents loose ends.


I used to do VHDL for FPGAs, where you basically have to use state machines. LabView is that way too, being a dataflow language. But I don't know how much LabView I've seen that I would call beautiful. Maybe if one considers spaghetti beautiful. ;)


Looks like you got bit by autocorrect: s/storks/skorks/


Erlang OTP’s gen_fsm and gen_statem are great for implementing state machines.

Mind you however, the immutable nature of variables really helps.


Try consensus libraries widely used in modern distributed systems, whether it is paxos or raft based, just pick your favourite one, both are heavily based on the concept of state machine.



> I am the only kind of developer there is and so therefore I will speak with authority as to what all developers do.

I use state machines regularly when producing performance statistics where I need to individually isolate many history dependent outcomes in a large sample space. I tried doing it without them and ended up with unreadable and thus unmaintainable buggy code. Never again.


I use state machines all the damn time. Am I, then, no developer?

Maybe you meant to say that only competent developers use state machines. But that would place you outside their august company, leaving us little reason to credit your observation. In that world, the absence of state machines in one's code would reliably indicate incompetence.

But in fact, by definition, there is no computer program on God's green Earth that is not a state machine. Programming languages were created specifically to make the state machines they compile down to look more intuitive and less like, you know, state machines.

So the observation is really that our programs usually look the way they were intended to look, except when we abuse our language in pursuit of performance, elegance, or other consensual delusion.


We tend to use state machines for massively asynchronous operations like order management (with days between updates), as well as a home-built library for handling the task. It's incredibly fault tolerant and offers really easy abstraction and automation.


Using (explicit) state machines in Rails is so easy, thanks to the gem of the same name(1), that we use them very often, with good results. I think that ease of setting them up and integrating them in whatever framework/language you use is a very important factor in their adoption.

1 current version: https://github.com/pluginaweek/state_machine

1a original unmaintained version: https://github.com/pluginaweek/state_machine



Thanks for the correction, I pasted the same link twice - can't edit :(


I put the link when I saw the one hour mark ;)

I spent too much time on HN today


One of the most refreshing experience I have had recently is to map Websockets to some finite state machine implemented on an actor model (namely Akka FSM): straightforward and fitting together naturally.


Fast forward 8 years and everyone on the front end is using state machines with the rise of react/redux et al.


A reducer store is not a state machine since both the transitions and states are loosely defined. That said, at this point I think it's actually better for UI, despite the verbosity and lack of formal correctness.


It's also worth noting that you can absolutely _use_ state machines to implement your reducer logic :)


Saying that developers "don't use" state machines just because code isn't littered with things like instantiations of machine and state classes is an interesting point, but kind of glib. I use state machines all the time as an abstraction to reason about programs.


State machines are essential for a lot of programming tasks -- I wish they were more explicit more often, because there always is an implicit state machine lurking in there somewhere, and code would be cleaner with the state machine made explicit.

For example ...

I was brought on to work at a startup a few years ago by an old-time acquaintance that was CEO of a startup ... the company's proposed product sounded interesting, and I believed in her skills and background and had decent rapport with her. I was wary of the VP Engineering and a few others, but it was to be my first post-move-out-of-SF-Bay-Area remote startup job and I thought I could make it work.

I show up in Santa Clara, CA to make sure I really want to do this ... and after joining we spend the next few days going over architecture, implementation-so-far, etc. Part of the product's job is to discover and categorize servers and networking devices, the basic software deployed on them, etc. Apparently this aspect has been in design/implementation for several months, but it's not working very well, and the problem and approach aren't articulated very well. I have a chance to look at the code a bit.

I eat lunch with the CEO and she asks me how I'd approach the problem, and I advocate breaking everything down into state machines with well-defined transitions ... both for interacting with the environment, and for storing info in the back end -- there's ample opportunity for things to go wrong, to require retries, etc., and tracking these interactions with state machines makes it much easier to efficiently and quickly deal with the environment and back end in asynchronous fashion, and to separate different levels of discovery (network / IP; finding credentials that work; OS determination; software inventory; storage inventory; etc.) much easier.

In the next day's architecture discussion I then hear the VP Engineering use a few of my words and draw a few poorly formulated diagrams that don't do the task much justice ... it's apparent the CEO has discussed the problem with him and urged a new look. I had to start work on my piece so I didn't follow their developments much, but I heard later they actually purchased and licensed for significant money a "finite state machine package" of some kind. All they needed were ENUMs, (preferably async-result) functions to try something out, and then transitions between enums based on the results. That discovery code was always buggy, never did work right.

That was one of many fiascos. There were so many other fiascos, and they all cost me lots of sleep, some of my sanity, and some self-respect. I lasted almost three years there (some early players were pushed out but the product never really got better). I would have been happier quitting the first week, or better yet never joining.


Erlang stands out for me as having a state machine library in the standard library (gen_fsm).


now gen_statem


A major problem with any state machine is that they tend to fall apart or become major bottlenecks if concurrency is required...

It is still shared state, a thing to minimize. Usually message passing (current buzzword: reactive) approaches work much better.


> A major problem with any state machine is that they tend to fall apart or become major bottlenecks if concurrency is required...

One of the classic ways of architectural a concurrent system is as a collection of sequential processes implementing message processing loops as state machines.

If you mean that state machines can't deal with internal concurrency, that's not true either. Leaving out concerns about unmanaged state (mutable data not reflected in the state of the machine), a state machine provides a clear framework for managing concurrency:

(1) an unlimited number of events that produce self-transitions can be processed simultaneously;

(2) an event that would be processed the same in the start and end states of all currently running events and whose end state would not alter the processing of any currently running event can be processed concurrently,

(3) an event that would not be valid in the end state of any currently running event is invalid,

(4) any other event cannot be run concurrently, blocks new events entering processing, and must be evaluated for validity after each running event completes (it may become either runnable or invalid).

The last is a kind of bottleneck, but it's usually not introduced by the state machine so much as a feature of the domain.


Why would state machines become a bottleneck with concurrency? This is definitely counter to my experience so I'm curious to see examples demonstrating this.


But most developers do write regular expressions, which are also finite state machines.


may be some of developers don't know this technique and more evident that they never tried to work with end state machines. I am using state machine long time and made my own script player for SM, unlike to others SM i am using it in different implementation from web till equipment automation. https://github.com/series6147/ProcessPlayer-state-machine


I use a state machine for text

https://github.com/BurntSushi/fst


I don't think I've ever programmed anything more than a hundred lines without a state machine...


I use state machines in UI.


Can't read the article as site is down but: fear of the unknown, along with some of the unnecessarily complex language around the topic.

I got over both of these and now use state machines in production where they make sense as a model.



Not complaining about the down votes, but it is odd to see an honest self-critical opinion being downmodded.


lol, interesting read. I used to work on a graphics project with a state machine at its core, and user actions are defined to trigger state transitions


Ads that auto pay audio on a developer blog, really?


Disable JavaScript, problem solved.

Bonus: Pages written by developers who don't know/care about graceful degradation appear blank, so you don't have to waste time reading them.




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

Search: