This approach is a big boon to the development of complex UIs.
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.
It’s a big boon to the development of tiny simple UIs!
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.
I didn’t know a thing about state machines and lately would deprioritize your xstate frontendmasters course on my learning queue just out of ignorance. For whatever reason, I just watched your React Rally video and I can think of a bunch of projects where state machines would help get the UI in order! A “legacy” ui we have (much older than a week) has these state flags (and then some):
I developed my own statechart lib many years ago (before React, but I used it with Angular) and used it for the high-level state that drove the application. It worked really well. Redux, etc have always seemed like a step backwards for high-level application state.
https://github.com/gregwebs/statetree
I just tried to read that code, and perhaps I'm missing something, but wouldn't coroutines/generators be a much better approach of keeping state confined to a local process, and wouldn't that make the code for managing state look more like normal code?
Sure, but then you're missing out on the ability to visualize your states and transitions, much less statically analyze your state machines, generate tests for them, etc. etc.
There's a lot of potential goodness that you lose when you drop down to "normal code" as opposed to a formalized structure.
This is one of the things that is preached pretty often in the Clojure community: it’s all about the data
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
People who gloss over this do so at your own risk. This technique is applicable for so, so many applications - even when it doesn't appear obvious at first.
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.
Yes, but the heavy lifting required behind the scenes to make statecharts work seems quite elaborate, moreover it could be problematic to use blocking apis, like sockets etc with statecharts, perhaps?
On the contrary, in my experience such stateful APIs are a great use case for state machines. You just approach them differently. Instead of doing a blocking call, you introduce a "waiting for <something>" state. You leave the "blocking" state when the correct event comes, or after a timeout. And while you are waiting for the event, you don't just burn CPU cycles, but yield control quickly, so that other threads can execute in the meantime.
Additionally, you can use state machines server side to track stateful processes by simply serializing the “current” state of the machine and storing it somewhere.
With the next request, reload the “current” state of the machine and transition to the next based on the incoming request.
It’s easier when you write your application in Elixir or Erlang. Those are practically DSLs for state machines that also double as general purpose programming languages.
There is an edX course by David Harel (the author of this paper) and Michal Gordon called Programming for Everyone – An Introduction to Visual Programming Languages. I highly recommend it for anyone dealing with state, whether you’re a programmer, designer, engineer, or manager.
Harel's recent work (which evolve from Statecharts) is so called Scenario-based programming. If interested I follow his work very closely and showcase his techniques in this article about bthreads: https://lmatteis.github.io/react-behavioral/
Maybe I'm just super dumb but I don't get why such simple examples should be made so difficult. Take for instance the following: "Here were are introducing the 3 main critical pillars of Behavioral Programming; the request, wait and block semantics.".
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:
When I first saw Statecharts I failed to realize that Statecharts are _NOT_ state machines, state machines are the building block.
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.
Statecharts are in fact descriptions of state machines. They are just not ordinary state machine diagrams based on a single, scalar state variable.
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.
Regarding the "formal semantics", you are correct that different implementations of Statecharts have had different formal semantics, however some effort has been put toward standardization, the main one probably being the W3C SCXML standard, which specifies a pseudocode algorithm for execution of Statecharts:
I have mixed feeling about the hierarchical facility in statecharts. I've certainly found it helpful in some cases; for example, handling "cancel" events that apply to a group of states.
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.
To give another data point: https://ossia.io had three different tentatives for implementing its UI manipulation "manually" before reaching for (indeed complex) state machines which I spent at least 4-5 full-time days specifiying on paper for all their cases (following days of meetings trying to define informal specs).
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.
Good insight. I think it's a blessing and a curse. currently i think me and my team have trouble identifying all those states and thus mis-understand or under-understand the complexity of some features. I want to use state charts to force us to look at all the states.
Of course I expect everyone will soon get annoyed at all these states we are tracking.
I agree too. I assume that state machines (SMs) are underrated because they are used in a "wrong" way which makes building them impossible: I mean most authors focus on listing and naming all the states, but in fact, they should not do that (IMO) but rather focus on events, conditions, and actions, which cause state transitions. Secondly, they should use hierarchical grouping the reduce number of edges in the state graph.
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.
I've found xstate invaluable for multi-step flows on the front end.
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!"
I have no idea why in university we covered state machines so very little when they solve a host of problems and can replace fundamental ways in designing software. Not only that but it's also compatible with them all.
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.
Was also inspired by the Statecharts long time ago, and then saw an opportunity to go deeper into how server-side web applications could be implemented as state machines, and how it would ultimately lead into easier/faster programming where correct/complex patterns emerge from events, conditions, and actions. Wrote about it here: https://jarirajari.wordpress.com/2014/01/03/ecas-hierarchica...
In the SwiftUI app I wrote recently, in an act of desperation, I dusted off my Harel statecharts skills (I used to develop tools like this), and ended up with a huge comment block with an ASCII-art hierarchical state model. Then the code structurese were based on that, and the names generally matched up between code and diagram. The app UI has been solid, even though some of the states are in the canned NFC UI mode, and less in my control.
I've worked in the past with code bases that have what were called 'state machines' implemented with at best switch statements and at worse a lot of if's and else's and some state variables.
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?
You'd probably still call it a state machine if that is what it's doing. Maybe not well implemented but it also depends. State machines are tricky to get right.
State machines implemented in-line with code usually end up as a mess. It's best if they are factored out, and implemented as a DSL inside a true state machine engine running as a somewhat independent service or library.
Back in university, I had a course on reactive systems, a whole semester of state machines and statecharts. I can't stress enough how useful it was in my career.
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.
Every graph can be represented by a 2-dimensional tree.
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.
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.
ah sorry, i thought your OP was talking about a more graph-theory-ish thing, some fun bijection between trees and graphs or something, serialization isn't really what i had in mind
re: 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)
I hear what you are saying, but there is a big leap between TN and "JSON/YAML/XML/s-exprs". One is isomorphic to 2 and 3 dimensional structures, and the rest are not, connecting the software world to the physical world. This will turn out to be very important and groundbreaking.
> One is isomorphic to 2 and 3 dimensional structures, and the rest are not
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
The fundamental shape of all our current computer languages is one dimensional. You can take any current program/file, "encode it" to lego blocks, and it would be a one-dimensional string. This probably arises because our foundational layer is one dimensional binary.
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)
not really sure what you mean by 2D/3D. if you mean "whitespace sensitive" (≈ effectively laid out in 2D), what about YAML? pretty sure you could just add dashes and get valid YAML representing the same tree:
- 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.
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.
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-...