More State Machine Love: From Reflection to Statecharts

(raganwald.com)

131 points | by weinzierl 2228 days ago

12 comments

  • codetrotter 2228 days ago
    I recently built a state machine for a game I am working on. My state machine is inspired by reading https://statecharts.github.io/ and also by some of the dispatch stuff from SICP though it had been a while since I read SICP.

    The way I wrote my code is I model the state machine and the states as separate objects and I model the transitions between states as events with messages that the states inform the state machine of and then the state machine looks at what is the current state and what is the current event and it looks at what state if any it should transition to as a result of this.

    Here is the class for my state machine object: https://github.com/ctsrc/ITR-LotPT/blob/master/src/statechar...

    And here is where I create states and register said states and their transitions with an instance of my state machine: https://github.com/ctsrc/ITR-LotPT/blob/master/src/main.js

    Prior to reading about statecharts my state machines used to be messy and hard to reason about. The one I mentioned above feels very clean and I am very happy with it.

    As you can see this state machine I use for transitioning from resource loading to main menu and then to the main game, as well as going between the in-game and paused states. For the main game itself I do not use a separate state machine because currently I feel that if I added that stuff there I would be adding overhead without much benefit. The game is still in an early stage and far from finished. The game is in fact not even playable yet. But like I said I feel good about that state machine of mine.

  • V-2 2228 days ago
    Interesting in and of itself, and yes I'm familiar with the design pattern, but with so much code to accomplish the simplest of things, Spolsky's "architecture astronauts"* spring to my mind straight away. Perhaps it's JavaScript that's not the best choice of a language here?

    ____

    * https://www.joelonsoftware.com/2001/04/21/dont-let-architect...

    • deckard1 2228 days ago
      That's a great article that always deserves a read once or twice a year.

      > Your typical architecture astronaut will take a fact like “Napster is a peer-to-peer service for downloading music” and ignore everything but the architecture, thinking it’s interesting because it’s peer to peer, completely missing the point that it’s interesting because you can type the name of a song and listen to it right away.

      This sentence so succinctly summarizes the current "blockchain" craze that it's hard to believe he wrote it in 2001.

    • stephen 2228 days ago
      Yes, agreed, using chained prototypes to accomplish nested state charts is not something I'd want to inherit as a maintainer.

      It's cute, in a "look at how I can make the language do this"/novelty way, but I don't think it's JavaScript's fault per se, as this seems similar to Java architects solving problems with "just another factory", "just another annotation", "just another bytecode generator", etc.

  • sramam 2228 days ago
    I'm surprised that `javascript-state-machine`[1] hasn't come up yet as a suggestion. A simple implementation of the bank-account example [2] shows why the pattern is interesting when the implementation can be declarative.

    I haven't tried building a nested/hierarchical state-machine with it - mostly because I have been able to decompose my problems into units that didn't need them, but also because hierarchical state machines always give me a head-ache larger than deadline.

    [1] https://github.com/jakesgordon/javascript-state-machine [2] https://gist.github.com/sramam/fba55f5325a8c398870292e1f3cad...

    • chris_st 2228 days ago
      Indeed... or the Go library fsm[1] based on javascript-state-machine. I thought I'd use them both in a game I'm building, but I'm leaning towards the idea that the UI state is just the state that's already in the server's model.

      I'm happy that the ability to generate GraphViz drawings has already helped me find a bug, at design time!

      [1] https://github.com/looplab/fsm

  • michaelsbradley 2228 days ago
    An implementation of statecharts in Python, under active development:

    https://github.com/aleph2c/miros/

    https://aleph2c.github.io/miros/introduction.html

  • skybrian 2228 days ago
    This whole idea of having methods appear and disappear depending on the state seems error-prone: the caller needs to check whether the method exists before calling it, and nothing enforces this so you'll get runtime errors unless you're very careful.

    In a language like Elm, you'd pattern-match on the current state every time. Is there a better way?

    • sghiassy 2228 days ago
      I can’t speak to this particular implementation but events in a ststaechart are supposed to bubble up

      That said, a withdrawFunds action shouldn’t be available if the user is in the loggedOut branch of the statechart

      • braythwayt 2227 days ago
        As it happens, the "chain of prototype" implementation definitely ensures that method handlers "bubble up," for example the .close method bubbles up.
    • skybrian 2228 days ago
      I'm wondering if the method should just always exist? If you have a state machine that transitions in response to events, it usually is the case that events can arrive regardless of the current state. So the method should always exist and do something, perhaps report an error.

      You might also want to know which actions are valid for UI purposes, but validating an action is more complicated than just checking the current state. For example, a withdrawal action may or may not be valid depending on the amount.

      • nikofeyn 2228 days ago
        in my finite state machine framework that i built, this is what i do. a state machine is modeled by a class and the states are child classes. for state methods, which are defined by the parent class, i force them to be overridden. then if that method isn’t supported in a certain state, it just does nothing. there is support for sending a return message stating the method sent is not valid in the current state (this is within an actor framework), but i found for the most part doing nothing is fine for most use cases.
    • solidsnack9000 2228 days ago
      Subclassing and returning a new object instead of updating the existing one.
      • skybrian 2228 days ago
        Sometimes that works, but if there is more than one possible next state, you need to return a supertype and you're back where you started.
  • platz 2228 days ago
    great comments on statecharts previously here https://news.ycombinator.com/item?id=15835005

    the top few comments i find compellingly skeptical.

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

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

    • monocasa 2228 days ago
      The first comment seems to be confusing flow charts with state charts.

      The second is addressed with hierarchical state machines.

  • drewfish 2227 days ago
    In the article the author states:

      From this, we get that accounts should certainly behave like state machines.
      And from that, it’s reasonable that other pieces of code ought to be able to
      dynamically inspect an account’s current state, as well as its complete graph
      of states and transitions. That’s as much a part of its public interface as
      any particular method.
    
    I disagree with this a bit. I think that the state transitions are not part of the public interface -- they're an implementation detail of the SM. The public interface of a SM are its states and the events it responds too. It's up to the SM to decide when to do the transitions. (The _author_ of the SM would be very interested in it's transition graph/conditions of course.)

    For example, reviewing a bunch of the javascript SM libraries I see a few have the SM define a "transition table". This works for simple SM but makes it difficult to conditionally transition. Perhaps we want our bank account SM to automatically transition to the "hold" state if the balance goes below zero. With a fixed (i.e. as configuration) transition table we can't do this (or we have to fight against the SM library, or the SM library has to be more complicated).

    I guess I'm fairly influenced by this book: https://www.amazon.com/Practical-UML-Statecharts-Event-Drive... I found that approach worked very well when I used it to implement a fairly sophisticated UI on an embedded device. It was easy to rationalize about, easy to read in the code, and easy to maintain (add/move states). Seems like something similar in javascript would be nice, except doing things in a javascript way.

    • braythwayt 2227 days ago
      Author here.

      Conditional transitions, before- and after- actions, state entry conditions/validations, ... There are all sorts of places to go that have been proven fertile for developing robust software.

      But you know... In one essay (two if you count its predecessor)... You have to draw the line somewhere. The goal is not to present a unified theory of modelling domain objects with state, nor is it to introduce some code that should be copied and pasted into a production pull request.

      The simple goal is to provoke a conversation.

      And if the outcome of that conversation for some people is, "We need conditional transitions, so the exact thing articulated in the essay is NoDamnGoodForUs," then I am actually quite satisfied that I've done my job as a blogger.

      And if I can address your specific point--which I hope you understand that I agree with, even if it disagrees with that one paragraph--there are multiple approaches, and I think that if we really were modelling bank accounts as domain object with .deposit or .withdraw methods, we could do just as you suggest and bake a conditional transition to a Held state.

      But we could also ask if the bank account is responsible for making that decision? In some architectures, we might say that this is burdening it with too much responsibility. I actually mulled this over with the following use case, which I decided to cut from the essay:

      Consider a "suspicious deposit," or maybe a "suspicious transaction pattern," like a series of deposits and immediate withdrawals. PayPal is notorious for freezing accounts that violate some set of hidden rules.

      Should the .deposit and .withdraw methods know about this and sometimes transition to "held?" One could argue this is not the account's responsibility, especially if some aspects of what makes a transaction suspicious involve things outside of the balance, like how long the customer has been with the institution.

      Of course, there could be a line of code that calls a "Transaction Evaluator" thingummy from within the .deposit and .withdraw methods, or even a .validate method that always checks for this kind of thing after any action.

      Or... Maybe this is a lot more like an MVC architecture, and the responsibility for checking the transaction falls to the controller, not the account model. So the controller calls .deposit or .withdraw, and the controller checks for suspicious transactions (or for negative balances), and the controller calls .placeHold on the account if necessary (and possibly on ALL accounts for the customer).

      I'm not suggesting that this is superior to your suggestion, but it was something I thought about, and in some application architectures, I think what I just described is the way to do it. In others, what you described is the way to do it.

      JM2C. Thanks for your comment.

      • drewfish 2227 days ago
        Good points, I pretty much agree with everything you said. "NoDamnGoodForUs" is a little strong, more like "AlmostButThisOneUseCaseNeedsALittleMore" :)

        Interestingly, that embedded device I made wound up being MVC without explicitly intending so -- the statecharts were the controllers. The only conditional transitions had to do with code reuse, where a single implementation lead to slightly different transitions depending on how it was configured.

  • gregwebs 2228 days ago
    I created a simplified javascript statechart implementation several years ago, which I used to describe client-side application state: https://github.com/gregwebs/StateTree

    This approach worked quite well for me at the time and still seems to have more clarity for high-level state management than the modern redux approach.

  • sghiassy 2228 days ago
    Statecharts are a great data structure for front end applications. Especially when there’s complicated boot up a sync logic for an app
  • solidsnack9000 2228 days ago
    Web development might be very different -- both more organized and understandable, and less error prone -- if state machines had been adopted more widely than the active record pattern.

    State machines are a natural fit for audit logging and replay, both issues that come up whenever your "models" go into production and customers report errors.

  • clumsysmurf 2228 days ago
    The article mentions SCXML, and when I search for java libraries that implement this, the first one that comes up is Apache Commons SCXML - but the project seems dead: The last stable release 0.9 was in 2008. Are there any other viable Java implementations that work on Android?
    • netghost 2228 days ago
      Sometimes inactive just means complete. I think SCXML was specified a while ago, I can't vouch for any particular library, but it's possible the library does what it set out to do.
  • agumonkey 2228 days ago
    What's the relationship between carefully separated states and legal transition and linear types ?