This course has been updated! We now recommend you take the State Machines in JavaScript with XState, v2 course.
Transcript from the "Statecharts & Assignment Actions" Lesson
[00:00:00]
>> Finite state machines are fine. But you're going to quickly run into a scaling problem with them. Because you're going to have many states that have many connections between the states and you have no way of neatly grouping them or representing that more than one state can be active at the same time even though it is still one finite state.
[00:00:24] And so that's what statecharts solve. statecharts were invented by David Harel around 1985. He wrote a paper called Statecharts, a Visual Formalism for Complex Systems in 1987. David Harel is a mathematician and a professor and he's been working in a lot on this modeling stuff, especially with statecharts and behavioral programming, and things like that.
[00:00:51] And so I highly encourage you to read this paper, Statecharts, a Visual Formalism for Complex Systems, because this is the paper that basically describes how statecharts came to be. It describes the problem with traditional state machine notation. And it also includes a lot of great graphics on describing these statecharts.
[00:01:15] A lot of what is in this paper is already in use today. UML which stands for universal modeling language, has adopted many of the many of the syntaxes and the idiosyncrasies of statecharts as defined in this paper in the UML model. And so, a lot of this stuff will seem very familiar if you do have experience with UML.
[00:01:42] And so statecharts are more than just state machines. In the paper, we see that they are state machines or state diagrams with depth, which means that you can have states that are inside of states. I know this might sound complex, but just think of it as a nicer way of organizing your states.
[00:02:03] You could also have orthogonality, which represents a combinatorial explosion of states in a much more concise format, and also broadcast communication. That's something we're going to be talking at the end of this workshop, where we could have state machines that actually talk to other state machines, back and forth using message passing.
[00:02:24] So I highly encourage you to read this paper, there's a lot of really cool examples, like this citizen stopwatch where pressing each of the buttons does a different thing, and if you've had a digital watch before, you'll immediately recognize this and be like, okay, so this turns the screen on, this sets my alarm on or off, etc.
[00:02:44] And these could all be represented visually with these statecharts. Now, statecharts can be transformed to state machines. So, think of statecharts and state machines as equivalents in the sense that they could be transformed to each other. Statecharts don't introduce anything outside of what a state machine is fundamentally, it's just a better way of representing complex state machines that could have many, many, many states.
[00:03:18] So one of the most important concepts of statecharts are extended states. And so, let's take this light bulb machine for instance. Let's say that we're in the initial turned off states, and then we turn it on, and we might have a turn on action as well. And when we're in the turned on states, once we toggle that light bulb, we go to the turned off again.
[00:03:41] Now, this is great, but light bulbs don't really have an infinite lifespan, especially if you have one of the older ones instead of the LED ones. They eventually break, right? And so, we keep track, somehow, of how many times we actually toggle the light bulb. Now there's a difference between finite state and extended states.
[00:04:07] Finite states, you could think of as qualitative data. So that's data where it's either on or it's off. It describes the thing, and when we talked about this before, we said it describes the behavior. So a light bulb behaves differently when it's turned off versus when it's turned on.
[00:04:28] Now, extended state describes quantitative data. So this is data that's potentially infinite. And the combination of that qualitative and that quantitative data makes the overall states. And so your application, or any application, any software, any hardware, any electronic thing, isn't just a finite state and it shouldn't just be data.
[00:04:52] It is a combination of how it behaves at a certain period of time, which is its finite state, and any data related to that finite state, which is its extended state. So, in this case, counts could be potentially infinite. And so, that's why we have this in extended states.
[00:05:10] The next state, we call this context because it's considered contextual states. It's not called data because we use data for something else. But you could just think of this as contextual state that represents the potentially infinite state and any other data that occurs in your app. Now, what good is extended state if we can't actually change it?
[00:05:36] So that's where assignment comes in. Now, assignments to extended state, it is a side effect. So instantly you should be thinking that's an action. And in fact, the next state, we have an assigned action creator, which does just that. So we have a context with the counts of zero, and we're going to create an assign action over here, which adds one to that context.
[00:06:04] Now, ideally, assign actions are pure functions, or they're objects where each of the properties represents a slice of that context, which itself is a pure function. And the reason is because we want to keep track of the previous state and the current state without mutating the context of the previous states too.
[00:06:25] So just like Redux, NgRx, View X, you want your assignments to be pure. In this case, we're telling it that the counts, which is right over here, should be incremented. So whenever we go into the turned on state, this entry action will be executed. And this entry action is going to increment the counts by 1.
[00:06:49] So we'll see an example of that in a minute. So in the assignAction, there's two ways of writing it. This way is preferable, when you have a context which has potentially many properties, you could specify changes to just one or a few of those properties by passing an object to assign.
[00:07:11] Now there's two types of assignments, there's a static assignment, which says assign this static value to message. Could be a string, number, Boolean, array, objects, whatever, anything besides a function. Now when it's a function, that function is going to take the current context and the events that triggered that assign action.
[00:07:35] And in this case, we are basing the counts assignments on the current context.counts in the events.value. And so we'll see an example of that in a minute. And so this is the object syntax for defining an assign action. Now, in more advanced use cases, you could actually pass in a function.
[00:08:00] And this function is going to take the same two things, the context and the event, and it's going to return the part of context that you want to update. So in this case, we are setting the message to Hello, we're doing the same thing as before, and we're setting counts to the current context.counts and the events value.
[00:08:23] So, what's the difference between this function assignment, wholesale function assignment, and the object syntax? Well, we could take a look in the visualizer. So, I'm gonna reload the visualizer. And, we're going to see that over here, we're keeping track of retries over here. So when we get to the failure state, let's say we reject, we retry it again, and that's going to increment the retry number.
[00:08:59] Now notice over here that it says, assign retries. So it knows exactly which property is being assigned. However, if we instead pass in a function, So we could return retries to context.retries + 1. Tab that, expected token return, aha, forgot my curly braces. Cool, and this should probably be curly braces as well.
[00:09:38] All right, now it just says assign, and the reason it says assign is because it can't predict ahead of time what it's going to be assigning to. And so that's why in order to make your machine definitions a lot more clear, you want to specify it as an object syntax because you want to say these are the only properties of context that I'm going to update.
[00:10:01] So in general, prefer the objects syntax over the wholesale function syntax.