Statecharts · Lesson 03
Orthogonality
Regions stop the diagram doubling. The harder skill is telling a real region from two facts that only look independent — and reading a box as a statement about where a feature applies.
Two facts, side by side
A parallel state is divided into regions by dashed lines. When the parallel state is active, every region is active, each in one of its own states. The configuration contains one leaf per region, not one leaf overall.
stateDiagram-v2
state car {
state doors {
[*] --> closed
closed --> opening : open
opening --> door_open : opened
door_open --> closing : close
closing --> closed : shut
}
--
state motion {
[*] --> idle
idle --> up : go_up
idle --> down : go_down
up --> idle : arrive
down --> idle : arrive
}
}
class Elevator(StateChart):
class car(State.Parallel):
class doors(State.Compound):
closed = State(initial=True)
opening = State()
...
class motion(State.Compound):
idle = State(initial=True)
up = State()
...
Four door states and three motion states. The flat equivalent has twelve, and every door arrow has to be drawn three times. That is the arithmetic, and it is the boring half of the argument.
Events are broadcast
The interesting half is what happens to an event. It is not routed. It is offered to
the whole chart, and every region independently either has a transition for it or does not. One
b can light the watch and start the stopwatch, because two regions both have a
b arrow and neither knows about the other.
This is what keeps regions from degenerating into mutual references. Harel counted broadcast as the third construct precisely because orthogonality without it would force every region to name the others.
The test for a real region
Splitting into regions is only sound if the regions are genuinely independent. The test is blunt:
Doors and motion pass, nearly. All twelve combinations are reachable; one of them — moving with the doors open — is merely forbidden. That distinction is the whole design decision:
The elevator's rule is a single condition on a single arrow:
go_up = idle.to(up, cond="doors_are_shut")
def doors_are_shut(self):
return self.closed.is_active
Harel writes this kind of guard as in(state) — a condition that
reads another region's configuration. It is the sanctioned way for regions to constrain each other,
and it is one line rather than a multiplied diagram.
self.car.doors.closed.is_active is always
False. Chained attribute access reaches the class attribute, which knows nothing
about any instance. Every state is also published on the instance under its own id, and only that
accessor is live: self.closed.is_active.
Scope: the box is the specification
Here is the part that does not show up in the arithmetic, and it is Harel's own
favourite point. His watch has a beeper test: press two buttons together and a healthy beeper beeps.
Where does that feature apply? Not in dead. Not while an alarm is already beeping.
On the real watch, it turns out to work in the time display, in the date display, and throughout the
update sequence — but not during a two-second wait. He remarks that the manufacturer's manual
describes the beeper test and the light in exactly the same way, “no indication of the scope is
provided, despite the major difference between the two”.
In a statechart the answer is not documentation. You draw a box around the states where the feature applies and attach the region there. The light's region sits at the top, so the light works everywhere. The beeper test's region sits around three displays, so it works in three displays. The scope of a feature is the box you drew it in — and it is checkable, because a reviewer can see the box.
stateDiagram-v2
state alive {
state main {
state displays {
state testable {
state shown {
[*] --> time
time --> date : d
}
--
state beeper {
[*] --> quiet
quiet --> beeping : b+d
}
}
testable --> stopwatch : a
stopwatch --> testable : a
}
}
--
state light {
[*] --> light_off
light_off --> light_on : b
}
}
Press b in the stopwatch: the light comes on, and the beeper test does
not fire, because beeper is not in the configuration. Nobody wrote a guard for that.
Exercise 1 — region, or not a region
For each pair of facts: two orthogonal regions, one compound state (because the combinations are impossible), or two regions plus a guard (because the combination is possible but forbidden)?
Exercise 2 — read the scope
Using the watch fragment above.
Exercise 3 — recall
Next
Something was lost when the alarm interrupted the display in lesson 01: the configuration no longer recorded what had been on screen. Regions do not help — the region was exited. What does help is history.
Sources: Harel 1987 §3–4 · statecharts.dev: parallel state · SCXML · RESOURCES.md