Statecharts: A Visual Formalism for Complex Systems (1987) [pdf]
inf.ed.ac.uk
inf.ed.ac.uk
If you are tired of littering your React/Angular/Vue/etc code with a litany of state flags (“isLoading”, “isError”, etc)[0] take a look at the de facto canonical state chart implementation for JavaScript, XState: https://xstate.js.org/docs/
It’s been a joy to work with and can replace other methods of managing state (i.e. redux) while constraining the behaviour of your UI to predictable states and interactions.
[0] https://dev.to/davidkpiano/no-disabling-a-button-is-not-app-...
I use XState to manage my login state. Not logged in, trying, logged in, log in failed, trying log out, logged out.
The thing I love is the confidence the machine gives me. Unless I’ve buggered up completely, it basically can’t be wrong. That’s incredibly reassuring.
Thanks for XState and all the work you put in to it. It’s an amazing thing.
I've used it for the exact same thing. I had a "userService" machine (embedded in a React UI) that handled all the legwork of an OAuth2 flow and ended with the user in an "authenticated" state with their details as part of the machine context (for use in any other part of the app via a custom hook).
You are absolutely right, it couldn't be wrong. It always just worked exactly the way it was supposed to.
I'm now working on a project that handles logins via a set of disconnected boolean flags flowing through redux and it is a mess.
IsConfirmed IsSent IsSentAndConfirmed IsDraft IsLoading IsInTheEther IsError IsNaN
I want to rewind the clock for a redo on that mega component but I suppose refactoring will have to do. Again, thank you for that great talk!
There's a lot of potential goodness that you lose when you drop down to "normal code" as opposed to a formalized structure.
Describing your application behaviour in data not only allows for all sorts of static analysis (as David mentioned above) but also the ability to change the behaviour without changing code. You could describe multiple different workflows in your application as different state machines, save them to a data store, and load them up dynamically depending on the situation/user.
I’m going off topic, but this approach goes beyond state machines. Focusing on a data driven approach allows you to do some pretty interesting stuff, like designing whole APIs and UIs using data only: https://youtu.be/BNkYYYyfF48
Factoring out complex control system logic into these (and related) formalisms dramatically enhances testability, substantially reduces code volume, and eliminates huge swaths of possible defects (not to mention seriously enhances formal verification and model checking). The "code" part for applications should basically be for data plumbing, and all decision-making and control logic should be factored into these.
Thanks for posting this!!
With the next request, reload the “current” state of the machine and transition to the next based on the incoming request.
Rinse/repeat.
https://www.edx.org/course/programming-for-everyone-an-intro...
Except the previous block of code does not exhibit the 'block' semantic at all.
> Try adding two X's in a row. It will not work. It's waiting for an O, and once that is triggered it waits for an X and so on.
If you try to make a move out of turn it seems like the illegal move is still "queued up" somehow. So as soon as you make a legal move the previously attempted illegal one occurs, as it's now legal.
In fact, this resulted in an interesting game where both sides won:
{ type: "X", payload: 0 }
{ type: "O", payload: 8 }
{ type: "X", payload: 1 }
{ type: "O", payload: 2 }
{ type: "X", payload: 3 }
{ type: "O", payload: 6 }
{ type: "X", payload: 7 }
{ type: "O", payload: 5 }
{ type: "X", payload: 4 }
{ type: "XWins" }
{ type: "OWins" }
I guess it needs a few more behaviours...The point was to show how by adding new bthreads the system gets smarter and less buggy.
Quote from the paper:
"... a complex system cannot be beneficially described in this naive fashion [state machines], because of the unmanageable, exponentially growing multitude of states, all of which have to be arranged in a ‘flat’ unstratified fashion, resulting in an unstructured, unrealistic, and chaotic state diagram. To be useful, a state/event approach must be modular, hierarchical and well-structured. It must also solve the exponential blow-up problem by somehow relaxing the requirement that all combinations of states have to be represented explicitly."
So regular state machines are usable up to only a few states and transitions. Implementing the whole set of Statecharts features is far from the quick exercise of building an ad-hoc, implicit state machine. Fully using Statecharts introduces some overhead. But the true power of Statecharts shows when using hierarchy, parallel states, history and so on, things that build on top of state machines and take a bit more investment to learn how to use.
--
Another interesting aspect is the "formal semantics". Quote:
"The statechart formalism turns out to be quite a challenge when it comes to providing formal semantics ... The main difficulty is not in the depth of states or the orthogonality constructs themselves; ... The more difficult problems arise with the introduction of events and conditions that are generated within the statechart itself, and are sensed in orthogonal components."
So the idea is that given the same Statechart diagrams, two different implementations should end up in the same states when subjected with the very same sequence of events.
Perhaps this is less of a problem to build GUIs and other applications that don't have super stringent requirements on safety and verifiability.
https://www.w3.org/TR/scxml/#AlgorithmforSCXMLInterpretation
It also provides a test suite that implementations can use to check compliance:
https://www.w3.org/Voice/2013/scxml-irp/
There are a number of conforming implementations in various programming languages.
The state expressed in Statechart could be turned into a single variable, and the chart could be refactored into a flat state transition diagram.
But that diagram would potentially be huge compared to the state chart description.
The state chart condenses that description by avoiding the duplication of some sub-state-machine for every change of some variable which is irrelevant to it.
E.g. suppose a device has two master modes of operation, A and B, in which it responds to inputs in totally different ways. Suppose that mode A and B retain their internal state when the mode switch takes place.
If we don't use a hierarchical state chart, we end up having to represent what happens when a certain input arrives in a state under mode B, for every possible state in mode A which is not actually in effect and irrelevant. Basically, we have to deal with the product state space, in which the explosion leads to numerous identical combinations.
So Statecharts adds a lot on top of them, but yeah, the foundation is FSMs.
I've also seen huge, multi-level, multiple-parallel-region monsters that were impenetrable.
Now, no formalism is immune to abuse: "just because you can, doesn't mean you should". But I've often found that the simpler state model formalism - without hierarchy - has led to cleaner design and better separation of logic.
More generally though I do think state machines are somewhat underrated tools in the programmer's toolbox.
I’m a huge fan of this approach when developing complex UIs, but you can definitely run into “state explosion”.
When you rigorously enumerate all the implicit states in your system you quickly realize that your system can have a lot of states.
It’s a balancing act. How much rigour do you need vs. how much code are you willing to deal with.
The implicit states will always be there whether you make them explicit via a state chart or not. But for simple things, it may not be worth the overhead.
Ages ago, I implemented an Illustrator style direct manipulation user interface. We had spent so much time analyzing, documenting, specifying in prep.
My initial implementation, per our "perfect" spec, really sucked.
Turns out there's a significant amount of fit and finish to a direct manipulation interface that I wasn't able to handle formally with charts and machines. Ended up with a bunch of special cases. Unanticipated knock on effects were unsatisfying negotiations with QA/Test (how to test) and tech writers (how to describe) our product.
I've always wondered about that gap. Was I doing it wrong? Are there better formalisms? How'd Illustrator (and others) do their direct manipulations?
With the rise of touch UI, I anticipate the problem space (complexity of the states) has gotten smaller. For instance, no modifier keys. But that's just a guess.
It's the thing that ended up working, and still works 4-5 years after the fact & numerous modifications / improvements / new features. Also Qt with GammaRay allows to visualize them "live" which was an immensely useful feature to debug them during development.
Of course I expect everyone will soon get annoyed at all these states we are tracking.
There are concepts like workflow engines and FLUX architecture which help in designing simpler SMs. For example, by limiting state transitions to events similar to how modern microservices communicate there will be less events. And actually many workflows themselves are implemented in (smaller) state machines.
- very clear explanation of statecharts, and why you'd want to use them (they're an elaboration of state machines)
- and why you wouldn't want to use them
very interesting, thanks to OP!
Redux and similar libraries are a bit hard to read when making these because representing sequential distinct states is implicit. I.e., have an enum for the "page" and some reducer cases to update the page.
With xstate, the order of things is often visually apparent. Not only are distinct states bread-and-butter for statecharts, the machine definition in the code visually jumps out at the reader and screams "these are my states, and here's how to move between them!"
There's also a good article by Hillel Wayne about using Statecharts and Alloy to formally specify UIs.
I finally tried using state machines in a hobby project and its much better than just writing a bunch of if then statements in a event loop.
I've also worked with code bases with a formally defined state machine object/concept, with pre-defined states, events, and state transitions driven by events.
Question for the crowd: what would you call the non-state machine state machine in the first case?
Ad hoc state-machines? I don't think there's a formal name for them.
I've retrofitted some near unmaintainable embedded systems, and one of the easiest ways to improve spaghetti code is refactoring some of the vars into state machines/statecharts.
Btw, state machines play well with event sourcing and clustering.
(Article references the above paper as citation #2).
Hence, every chart in this paper has a 2-D tree representation.
You can write that 2-D representation, and then without any type of transforming (keeping the shape of the graph in tree form untouched), you can turn it into a 3-D statechart, via the use of a Tree Language (https://jtree.treenotation.org/designer/)
So you get a 2-D text encoding of the statechart, editable in any dumb text editor or spreadsheet, isomorphic to the visual circles and lines diagrams they have, just as a 3-D rendering.
You could probably put that in a paper.
do you have a good reference for this encoding? i vaguely recall seeing something like this in a paper, but can't remember any specifics
It's not that traditional a style as I had no experience in academia until after I published it on arxiv, but I still stand by basically all of it.
For the encoding all you need is 3 things: a node delimiter (generally newlines), a cell delimiter (generally tab or space), and an edge delimiter for parent/child relationships (generally reuse the tab or space from above).
Think of a spreadsheet as a program, and that's the base encoding.
From there you just need to define Grammars to get higher level constructs. This page (https://jtree.treenotation.org/designer/) contains ~15 example grammars, including a grammar for grammars (similar to YACC or ANTLR). This is just the tip of the iceberg though, you can do really novel things with these languages that you don't see with all our traditional languages (like having N parse heads that start all over the place and move in all sorts of directions).
So if you take these ideas and combine them with this "Statecharts" paper of 1987, what you would do is create a "Statecharts" Tree Language defining all the elements they have in that paper, and then people could write "programs" in this Tree Language, and then you could use the Compiler Compiler I linked to above to generate a compiler that reads those programs and either 1) compiles them to SVGs like they have in their paper or 2) generates 3-D visualizations where the program shape in the focus is unchanged.
A --> B --> C
^ |
| |
+-----------+ A
B
C A
A
B
C
A
A
B
C.
A
B
C
AB
BC
AB
Verbose ones: ANode
BNode
CNode
Edge ANode
CAEdge
ANode
ABEdge
BNode
BCEdge
CNodere: TN stuff. don't take this the wrong way, but being able to serialize a graph into bunch of IDs+edges isn't very... remarkable. as in, you could also use JSON/YAML/XML/s-exprs or whatever format, they'd be kind of equivalent here (modulo punctuation). i mean, lightweight DSLs and homoiconicity are cool, but not really groundbreaking stuff
---
PS (additional bit of unsolicited advice). afaik you're still looking for TN's "killer app"; i'd consider making something geared towards the org-mode/markdown/plain-text-everything crowd, because TN's advantage is conciseness + ease of input/editing + aesthetics. maybe it could find its niche as a markdown++/org-mode-ish thing where you can freely mix text with structured bits (DSL stuff would be handy here)
So you're saying your notation can encode graphs that can't be represented as, e.g., JSON? The existence of such a graph would be the real (math-breaking) discovery here, no? I.e., you really should be able to give an example before offering such statements...
> connecting the software world to the physical world
I'm really not following you here.
Tree Notation/2D/3D languages are like a higher dimensional binary. Where you can traverse the program in multiple dimensions. Where physical layout matters.
Now all our current languages can be represented in this way. And that's what most of my stuff focuses on now.
But there are things that you can do with these languages and this style of 2D/3D architecture that you just cannot do with our current 1D register technology. (I mean maybe you could do them, but it would take billions of years to compute).
Wait, am I claiming that 2D/3D computers would be as big a development as Quantum computing?
No, much fucking bigger. Quantum computing is a fucking joke compared to this.
(pardon my french, I just find the occasional f-bomb as a good way to communicate my excitement about the OOM I am seeing)
- A
- B
- C
- A
to put it bluntly, i'm really not seeing what's so groundbreaking about using indentation to represent nesting – it's a nice concise syntax, but it's just that. if there's cool things you can do using that syntax (AIUI, Tree Languages and tools built around them, which reminds me of racket-lang and their whole "language-oriented programming" idea), that might be interesting; but failing that, people will dismiss your claims of being revolutionary as crankery, which will hurt your project's image.Turn off the computer and "write" a program by assembling legos. One token is one lego block. So an open quotation mark—"—is one lego block. If you can align all the lego blocks in a single line, and the meaning of the program does not change, you have a 1-D programming language. This is the case for all the languages you mentioned—"JSON/YAML/XML/s-exprs".
This is not the case for Tree Notation/2-D and 3-D languages. Changing the 2-D positioning of the tokens changes the meaning.
Now you might say that this is not the case because you could just have a lego block that equals "\n", and so the parsing would be the same. But the mistake their is to miss the general idea of 2-D languages. While Tree Notation first parses a program linearly from start to end, that's just because it's a limitation based on how our current computers are designed to work, and having multiple read/execute heads starting and moving in different directions across a memory cube is not technology we currently have.
I gave a talk on this putting it another way a few years ago: "https://www.youtube.com/watch?v=ldVtDlbOUMA".
To put it another way: you don't need syntax characters, all you need is space.
> but failing that, people will dismiss your claims of being revolutionary as crankery, which will hurt your project's image.
I figured it would take ~10 years of hard work and dedication to chisel out the core of this idea and make it "just work" for people. I'm about 4 years in, so the bulk of the work and resources to put in are still ahead. I do not want to put so much effort and resources into a novel idea that wouldn't have much of an impact if it turns out to be correct. I give this idea a low probability of being groundbreaking (<10%), but if it is it will fundamentally change our languages and probably make a significant impact across society in many unpredictable ways (probably many orders small than binary notation, but many magnitudes bigger than most things). So I'm happy to sacrifice my "image" for this idea. I want people to be critical, especially now, still in the early days, to figure out the flaws here. I'm still not seeing one. From all the data I've seen so far, these types of languages will be a 10x+ leap in technology. It's a really big idea.