What I learned on my own I still remember
—Nassim Nicholas Taleb
Hacking to Learn#
In this example I ask a small question about a statechart. Then we will:
try to answer the question
build the chart in working code
test out our answer
learn something
A Picture and a Question#
Suppose we started the above chart in s11
, then we send a T
event to it,
when would each of the functions, a
, b
, c
, d
, e
, g
, and t
happen?
If you are unfamiliar with UML, this part of the diagram:
…contains a guard. Specifically, the code:
[g()]
…on the arrow is the guard.
It is shorthand for: if the logic between the square brackets is True then let
the event on the arrow pass through. The code that is after the guard on the
diagram, t()
, runs if the guard doesn’t block it. Now that we know how a
guard works, we can infer that:
…means:
When I see a T event, ask for permission from the guard to see if it can pass.
The g function is the guard
If g returns True, T can pass; it can run the t function then make the transition to state s2 [1].
If g returns False the T event is ignored by the chart.
A Partial Answer#
Now that we understand that, let’s re-ask the question:
“Suppose we started the above chart in s11
, then we send a T
event to it,
when would each of the functions, a
, b
, c
, d
, e
, g
, and t
happen?”
Well, if we start in s11
, then we receive a T
, s11
wouldn’t know
what to do with T
, so it would pass it out to s1
. s1
does know
what to do with T
, it would try to pass it to s2
.
Ok, now how do we get from s11
to s2
? We need to exit, s11
,
then we need to exit s1
then we need to enter s2
. Oh, and don’t forget
to run g
and t
on the T
event. Here is a working theory:
a
will run becauses11
needs to be exited.b
will run becauses1
needs to be exited.g
will run because it is the guard for the T event between thes1
ands2
states.t
will run (assuming thatg
returned true)c
will run becauses2
needs to be entered.
A Better Answer#
Now that we have a partial answer, let’s re-ask the question:
“Suppose we started the above chart in s11
, then we send a T
event to it,
when would each of the functions, a
, b
, c
, d
, e
, g
, and t
happen?”
If we look at the s2
state, we see that it has an initialization event
(black dot) with another function d
hanging on it . After s2
is
entered, we would expect the next thing to be a transition into the s21
state, but first we would run the d
function on the arrow tied to the black
dot, since it is outside of the s21
state and we haven’t entered it yet.
Let’s add this to our working theory:
a
will run becauses11
needs to be exited.b
will run becauses1
needs to be exited.g
will run because it is the guard for the T event between thes1
ands2
states.t
will run (assuming thatg
returned true)c
will run becauses2
needs to be entered.d
will run on the init transition event froms2
tos21
e
will run because thes21
needs to be entered.
Our First Working Hypothesis#
“Suppose we started the above chart in s11
, then we send a T
event to it,
when would each of the functions, a
, b
, c
, d
, e
, g
, and t
happen?”
Ok, our thinking is a bit clearer now but let’s tighten up our answer. There are actually two parts, because the guard can return True or False. We will start with the easy part of the answer, then explain the longer part of the answer:
If g() returns False, none of the functions are called.
If g() returns True, then… wait
g
had to be called first? Hmm.. whatever, here is my theory so far:a
,b
,g
,t
,c
,d
,e
. It is easy to answer this way because my eyes work that way when I’m looking at the picture.
But, there is something nagging at me now: g
had to be called first, otherwise
my answer doesn’t really make any sense. So, here is my answer:
If g() returns False, only
g
is calledIf g() returns True, then
g
,a
,b
,t
,c
,d
,e
.
We have our theory, but we are hackers not philosophers. We have more work to do. Hackers ruthlessly deploy the scientific method to seek understanding about things they care about. Hackers do four hard things over and over again:
Think
Move past technical boundaries
Destroy their own theories by seeking contrary evidence
Learn from their mistakes
Now that I think I understand how the statechart works, I have performed the first thing on the list. To try and disprove my theory, I will need to build up the statechart in the diagram and actually see what happens.
Code, Make a Picture#
To begin with I will draw the picture in the code, so that as I work I can see what I’m trying to build:
'''
+----------------------------- s -------------------------------+
| +-------- s1 ---------+ +-------- s2 -------+ |
| | exit / b() | | entry / c() | |
| | +--- s11 ----+ | | +---- s21 -----+ | |
| | | exit / a() | | | | entry / e() | | |
| | | | | | | | | |
| | | | +- T [g()] / t() -> | | | |
| | +------------+ | | +-----------/--+ | |
| | | | *-- / d() -+ | |
| +---------------------+ +-------------------+ |
+---------------------------------------------------------------+
'''
Code, Required Imports#
Now I’ll import the items I’ll need to run my experiment:
'''
+----------------------------- s -------------------------------+
| +-------- s1 ---------+ +-------- s2 -------+ |
| | exit / b() | | entry / c() | |
| | +--- s11 ----+ | | +---- s21 -----+ | |
| | | exit / a() | | | | entry / e() | | |
| | | | | | | | | |
| | | | +- T [g()] / t() -> | | | |
| | +------------+ | | +-----------/--+ | |
| | | | *-- / d() -+ | |
| +---------------------+ +-------------------+ |
+---------------------------------------------------------------+
'''
import time
from miros import spy_on, pp
from miros import ActiveObject
from miros import signals, Event, return_status
Code, Frame in the States#
Now I will frame in the state methods:
'''
+----------------------------- s -------------------------------+
| +-------- s1 ---------+ +-------- s2 -------+ |
| | exit / b() | | entry / c() | |
| | +--- s11 ----+ | | +---- s21 -----+ | |
| | | exit / a() | | | | entry / e() | | |
| | | | | | | | | |
| | | | +- T [g()] / t() -> | | | |
| | +------------+ | | +-----------/--+ | |
| | | | *-- / d() -+ | |
| +---------------------+ +-------------------+ |
+---------------------------------------------------------------+
'''
import time
from miros import spy_on, pp
from miros import ActiveObject
from miros import signals, Event, return_status
@spy_on
def s_state(chart, e)
pass
@spy_on
def s1_state(chart, e)
pass
@spy_on
def s11_state(chart, e)
pass
@spy_on
def s2_state(chart, e)
pass
@spy_on
def s21_state(chart, e)
pass
Code, Add Common Internal State Code#
Now I add the internal-event-handling code into each of the state methods:
'''
+----------------------------- s -------------------------------+
| +-------- s1 ---------+ +-------- s2 -------+ |
| | exit / b() | | entry / c() | |
| | +--- s11 ----+ | | +---- s21 -----+ | |
| | | exit / a() | | | | entry / e() | | |
| | | | | | | | | |
| | | | +- T [g()] / t() -> | | | |
| | +------------+ | | +-----------/--+ | |
| | | | *-- / d() -+ | |
| +---------------------+ +-------------------+ |
+---------------------------------------------------------------+
'''
import time
from miros import spy_on, pp
from miros import ActiveObject
from miros import signals, Event, return_status
@spy_on
def s_state(chart, e)
status = return_status.UNHANDLED
if(e.signal == signals.ENTRY_SIGNAL):
status = return_status.HANDLED
elif(e.signal == signals.EXIT_SIGNAL):
status = return_status.HANDLED
else:
status, chart.temp.fun = return_status.SUPER, chart.top
return status
@spy_on
def s1_state(chart, e)
status = return_status.UNHANDLED
if(e.signal == signals.ENTRY_SIGNAL):
status = return_status.HANDLED
elif(e.signal == signals.EXIT_SIGNAL):
status = return_status.HANDLED
else:
status, chart.temp.fun = return_status.SUPER, chart.top
return status
@spy_on
def s11_state(chart, e)
status = return_status.UNHANDLED
if(e.signal == signals.ENTRY_SIGNAL):
status = return_status.HANDLED
elif(e.signal == signals.EXIT_SIGNAL):
status = return_status.HANDLED
else:
status, chart.temp.fun = return_status.SUPER, chart.top
return status
@spy_on
def s2_state(chart, e)
status = return_status.UNHANDLED
if(e.signal == signals.ENTRY_SIGNAL):
status = return_status.HANDLED
elif(e.signal == signals.EXIT_SIGNAL):
status = return_status.HANDLED
else:
status, chart.temp.fun = return_status.SUPER, chart.top
return status
@spy_on
def s21_state(chart, e)
status = return_status.UNHANDLED
if(e.signal == signals.ENTRY_SIGNAL):
status = return_status.HANDLED
elif(e.signal == signals.EXIT_SIGNAL):
status = return_status.HANDLED
else:
status, chart.temp.fun = return_status.SUPER, chart.top
return status
Code, Add Hiearchy#
Then I add the hierarchy:
'''
+----------------------------- s -------------------------------+
| +-------- s1 ---------+ +-------- s2 -------+ |
| | exit / b() | | entry / c() | |
| | +--- s11 ----+ | | +---- s21 -----+ | |
| | | exit / a() | | | | entry / e() | | |
| | | | | | | | | |
| | | | +- T [g()] / t() -> | | | |
| | +------------+ | | +-----------/--+ | |
| | | | *-- / d() -+ | |
| +---------------------+ +-------------------+ |
+---------------------------------------------------------------+
'''
import time
from miros import spy_on, pp
from miros import ActiveObject
from miros import signals, Event, return_status
@spy_on
def s_state(chart, e):
status = return_status.UNHANDLED
if(e.signal == signals.ENTRY_SIGNAL):
status = return_status.HANDLED
elif(e.signal == signals.EXIT_SIGNAL):
status = return_status.HANDLED
else:
status, chart.temp.fun = return_status.SUPER, chart.top
return status
@spy_on
def s1_state(chart, e):
status = return_status.UNHANDLED
if(e.signal == signals.ENTRY_SIGNAL):
status = return_status.HANDLED
elif(e.signal == signals.EXIT_SIGNAL):
a(chart)
status = return_status.HANDLED
else:
status, chart.temp.fun = return_status.SUPER, s_state
return status
@spy_on
def s11_state(chart, e):
status = return_status.UNHANDLED
if(e.signal == signals.ENTRY_SIGNAL):
status = return_status.HANDLED
elif(e.signal == signals.EXIT_SIGNAL):
status = return_status.HANDLED
else:
status, chart.temp.fun = return_status.SUPER, s1_state
return status
@spy_on
def s2_state(chart, e):
status = return_status.UNHANDLED
if(e.signal == signals.ENTRY_SIGNAL):
status = return_status.HANDLED
elif(e.signal == signals.EXIT_SIGNAL):
status = return_status.HANDLED
else:
status, chart.temp.fun = return_status.SUPER, s_state
return status
@spy_on
def s21_state(chart, e):
status = return_status.UNHANDLED
if(e.signal == signals.ENTRY_SIGNAL):
status = return_status.HANDLED
elif(e.signal == signals.EXIT_SIGNAL):
status = return_status.HANDLED
else:
status, chart.temp.fun = return_status.SUPER, s2_state
return status
Code, Add the T and Init events#
Now I’ll add management for the T
event in state s1
event and the
init
event needed in s2
:
'''
+----------------------------- s -------------------------------+
| +-------- s1 ---------+ +-------- s2 -------+ |
| | exit / b() | | entry / c() | |
| | +--- s11 ----+ | | +---- s21 -----+ | |
| | | exit / a() | | | | entry / e() | | |
| | | | | | | | | |
| | | | +- T [g()] / t() -> | | | |
| | +------------+ | | +-----------/--+ | |
| | | | *-- / d() -+ | |
| +---------------------+ +-------------------+ |
+---------------------------------------------------------------+
'''
import time
from miros import spy_on, pp
from miros import ActiveObject
from miros import signals, Event, return_status
@spy_on
def s_state(chart, e):
status = return_status.UNHANDLED
if(e.signal == signals.ENTRY_SIGNAL):
status = return_status.HANDLED
elif(e.signal == signals.EXIT_SIGNAL):
status = return_status.HANDLED
else:
status, chart.temp.fun = return_status.SUPER, chart.top
return status
@spy_on
def s1_state(chart, e):
status = return_status.UNHANDLED
if(e.signal == signals.ENTRY_SIGNAL):
status = return_status.HANDLED
elif(e.signal == signals.EXIT_SIGNAL):
a(chart)
status = return_status.HANDLED
elif(e.signal == signals.T):
status = chart.trans(s2_state)
else:
status, chart.temp.fun = return_status.SUPER, s_state
return status
@spy_on
def s11_state(chart, e):
status = return_status.UNHANDLED
if(e.signal == signals.ENTRY_SIGNAL):
status = return_status.HANDLED
elif(e.signal == signals.EXIT_SIGNAL):
status = return_status.HANDLED
else:
status, chart.temp.fun = return_status.SUPER, s1_state
return status
@spy_on
def s2_state(chart, e):
status = return_status.UNHANDLED
if(e.signal == signals.ENTRY_SIGNAL):
status = return_status.HANDLED
elif(e.signal == signals.EXIT_SIGNAL):
status = return_status.HANDLED
elif(e.signal == signals.INIT_SIGNAL):
status = chart.trans(s21_state)
else:
status, chart.temp.fun = return_status.SUPER, s_state
return status
@spy_on
def s21_state(chart, e):
status = return_status.UNHANDLED
if(e.signal == signals.ENTRY_SIGNAL):
status = return_status.HANDLED
elif(e.signal == signals.EXIT_SIGNAL):
status = return_status.HANDLED
else:
status, chart.temp.fun = return_status.SUPER, s2_state
return status
Code, See if anything Runs#
Now it is time to turn on this hierarchy by giving it to an active object and seeing what happens:
1'''
2+----------------------------- s -------------------------------+
3| +-------- s1 ---------+ +-------- s2 -------+ |
4| | exit / b() | | entry / c() | |
5| | +--- s11 ----+ | | +---- s21 -----+ | |
6| | | exit / a() | | | | entry / e() | | |
7| | | | | | | | | |
8| | | | +- T [g()] / t() -> | | | |
9| | +------------+ | | +-----------/--+ | |
10| | | | *-- / d() -+ | |
11| +---------------------+ +-------------------+ |
12+---------------------------------------------------------------+
13
14'''
15
16import time
17from miros import spy_on, pp
18from miros import ActiveObject
19from miros import signals, Event, return_status
20
21@spy_on
22def s_state(chart, e):
23status = return_status.UNHANDLED
24
25if(e.signal == signals.ENTRY_SIGNAL):
26 status = return_status.HANDLED
27elif(e.signal == signals.EXIT_SIGNAL):
28 status = return_status.HANDLED
29else:
30 status, chart.temp.fun = return_status.SUPER, chart.top
31return status
32
33
34@spy_on
35def s1_state(chart, e):
36 status = return_status.UNHANDLED
37
38 if(e.signal == signals.ENTRY_SIGNAL):
39 status = return_status.HANDLED
40 elif(e.signal == signals.EXIT_SIGNAL):
41 a(chart)
42 status = return_status.HANDLED
43 elif(e.signal == signals.T):
44 status = chart.trans(s2_state)
45 else:
46 status, chart.temp.fun = return_status.SUPER, s_state
47 return status
48
49
50@spy_on
51def s11_state(chart, e):
52 status = return_status.UNHANDLED
53
54 if(e.signal == signals.ENTRY_SIGNAL):
55 status = return_status.HANDLED
56 elif(e.signal == signals.EXIT_SIGNAL):
57 status = return_status.HANDLED
58 else:
59 status, chart.temp.fun = return_status.SUPER, s1_state
60 return status
61
62
63@spy_on
64def s2_state(chart, e):
65 status = return_status.UNHANDLED
66
67 if(e.signal == signals.ENTRY_SIGNAL):
68 status = return_status.HANDLED
69 elif(e.signal == signals.EXIT_SIGNAL):
70 status = return_status.HANDLED
71 elif(e.signal == signals.INIT_SIGNAL):
72 status = chart.trans(s21_state)
73 else:
74 status, chart.temp.fun = return_status.SUPER, s_state
75 return status
76
77
78@spy_on
79def s21_state(chart, e):
80 status = return_status.UNHANDLED
81
82 if(e.signal == signals.ENTRY_SIGNAL):
83 status = return_status.HANDLED
84 elif(e.signal == signals.EXIT_SIGNAL):
85 status = return_status.HANDLED
86 else:
87 status, chart.temp.fun = return_status.SUPER, s2_state
88 return status
89
90
91if __name__ == "__main__":
92 ao = ActiveObject(name="T_question")
93 ao.start_at(s11_state)
94 time.sleep(0.1)
95 pp(ao.spy())
Notice, we sleep for a very short time to let the active object thread detect that it has received an instruction.
When we run this code it outputs:
['START',
'SEARCH_FOR_SUPER_SIGNAL:s11_state',
'SEARCH_FOR_SUPER_SIGNAL:s1_state',
'SEARCH_FOR_SUPER_SIGNAL:s_state',
'ENTRY_SIGNAL:s_state',
'ENTRY_SIGNAL:s1_state',
'ENTRY_SIGNAL:s11_state',
'INIT_SIGNAL:s11_state',
'<- Queued:(0) Deferred:(0)']
Good, our start is structured well enough that it can run.
Code, Add the guard and t function#
Now lets add the guard function g
and the t
function into s1_state,
this will build this part of the picture:
'''
+----------------------------- s -------------------------------+
| +-------- s1 ---------+ +-------- s2 -------+ |
| | exit / b() | | entry / c() | |
| | +--- s11 ----+ | | +---- s21 -----+ | |
| | | exit / a() | | | | entry / e() | | |
| | | | | | | | | |
| | | | +- T [g()] / t() -> | | | |
| | +------------+ | | +-----------/--+ | |
| | | | *-- / d() -+ | |
| +---------------------+ +-------------------+ |
+---------------------------------------------------------------+
'''
import time
from miros import spy_on, pp
from miros import ActiveObject
from miros import signals, Event, return_status
@spy_on
def s_state(chart, e):
status = return_status.UNHANDLED
if(e.signal == signals.ENTRY_SIGNAL):
status = return_status.HANDLED
elif(e.signal == signals.EXIT_SIGNAL):
status = return_status.HANDLED
else:
status, chart.temp.fun = return_status.SUPER, chart.top
return status
@spy_on
def s1_state(chart, e):
def g(chart):
chart.scribble("Running g() -- the guard, which returns True")
return True
def t(chart):
chart.scribble("Running t() -- function run on event T")
status = return_status.UNHANDLED
if(e.signal == signals.ENTRY_SIGNAL):
status = return_status.HANDLED
elif(e.signal == signals.EXIT_SIGNAL):
status = return_status.HANDLED
elif(e.signal == signals.T):
if g(chart):
t(chart)
status = chart.trans(s2_state)
else:
status, chart.temp.fun = return_status.SUPER, s_state
return status
@spy_on
def s11_state(chart, e):
status = return_status.UNHANDLED
if(e.signal == signals.ENTRY_SIGNAL):
status = return_status.HANDLED
elif(e.signal == signals.EXIT_SIGNAL):
status = return_status.HANDLED
else:
status, chart.temp.fun = return_status.SUPER, s1_state
return status
@spy_on
def s2_state(chart, e):
status = return_status.UNHANDLED
if(e.signal == signals.ENTRY_SIGNAL):
status = return_status.HANDLED
elif(e.signal == signals.EXIT_SIGNAL):
status = return_status.HANDLED
elif(e.signal == signals.INIT_SIGNAL):
status = chart.trans(s21_state)
else:
status, chart.temp.fun = return_status.SUPER, s_state
return status
@spy_on
def s21_state(chart, e):
status = return_status.UNHANDLED
if(e.signal == signals.ENTRY_SIGNAL):
status = return_status.HANDLED
elif(e.signal == signals.EXIT_SIGNAL):
status = return_status.HANDLED
else:
status, chart.temp.fun = return_status.SUPER, s2_state
return status
if __name__ == "__main__":
ao = ActiveObject(name="T_question")
ao.start_at(s11_state)
time.sleep(0.1)
pp(ao.spy())
The guard condition totally makes sense when you look it it in Python.
Functions g
and t
use the chart’s scribble
method which puts little
notes directly into the spy output log. We do this so that our tests will
reveal exactly when g
and t
are called by the event processor.
Code, Add the other functions#
Now let’s frame a
, b
, c
, d
, e
. Notice we re-name the e
function to e_function
to avoid a name collision:
1'''
2+----------------------------- s -------------------------------+
3| +-------- s1 ---------+ +-------- s2 -------+ |
4| | exit / b() | | entry / c() | |
5| | +--- s11 ----+ | | +---- s21 -----+ | |
6| | | exit / a() | | | | entry / e() | | |
7| | | | | | | | | |
8| | | | +- T [g()] / t() -> | | | |
9| | +------------+ | | +-----------/--+ | |
10| | | | *-- / d() -+ | |
11| +---------------------+ +-------------------+ |
12+---------------------------------------------------------------+
13
14'''
15
16import time
17from miros import spy_on, pp
18from miros import ActiveObject
19from miros import signals, Event, return_status
20
21
22@spy_on
23def s_state(chart, e):
24 status = return_status.UNHANDLED
25
26 if(e.signal == signals.ENTRY_SIGNAL):
27 status = return_status.HANDLED
28 elif(e.signal == signals.EXIT_SIGNAL):
29 status = return_status.HANDLED
30 else:
31 status, chart.temp.fun = return_status.SUPER, chart.top
32 return status
33
34
35@spy_on
36def s1_state(chart, e):
37 def b(chart):
38 chart.scribble("Running b()")
39
40 def g(chart):
41 chart.scribble("Running g() -- the guard, which returns True")
42 return True
43
44 def t(chart):
45 chart.scribble("Running t() -- function run on event T")
46
47 status = return_status.UNHANDLED
48
49 if(e.signal == signals.ENTRY_SIGNAL):
50 status = return_status.HANDLED
51 elif(e.signal == signals.EXIT_SIGNAL):
52 b(chart)
53 status = return_status.HANDLED
54 elif(e.signal == signals.T):
55 if g(chart):
56 t(chart)
57 status = chart.trans(s2_state)
58 else:
59 status, chart.temp.fun = return_status.SUPER, s_state
60 return status
61
62
63@spy_on
64def s11_state(chart, e):
65 def a(chart):
66 chart.scribble("Running a()")
67
68 status = return_status.UNHANDLED
69
70 if(e.signal == signals.ENTRY_SIGNAL):
71 status = return_status.HANDLED
72 elif(e.signal == signals.EXIT_SIGNAL):
73 a(chart)
74 status = return_status.HANDLED
75 else:
76 status, chart.temp.fun = return_status.SUPER, s1_state
77 return status
78
79
80@spy_on
81def s2_state(chart, e):
82 def c(chart):
83 chart.scribble("running c()")
84
85 def d(chart):
86 chart.scribble("running d()")
87
88 status = return_status.UNHANDLED
89
90 if(e.signal == signals.ENTRY_SIGNAL):
91 c(chart)
92 status = return_status.HANDLED
93 elif(e.signal == signals.EXIT_SIGNAL):
94 status = return_status.HANDLED
95 elif(e.signal == signals.INIT_SIGNAL):
96 d(chart)
97 status = chart.trans(s21_state)
98 else:
99 status, chart.temp.fun = return_status.SUPER, s_state
100 return status
101
102
103@spy_on
104def s21_state(chart, e):
105 def e_function(chart):
106 chart.scribble("running e()")
107
108 status = return_status.UNHANDLED
109
110 if(e.signal == signals.ENTRY_SIGNAL):
111 e_function(chart)
112 status = return_status.HANDLED
113 elif(e.signal == signals.EXIT_SIGNAL):
114 status = return_status.HANDLED
115 else:
116 status, chart.temp.fun = return_status.SUPER, s2_state
117 return status
118
119
120if __name__ == "__main__":
121 ao = ActiveObject(name="T_question")
122 ao.start_at(s11_state)
123
124 ao.clear_spy()
125 ao.post_fifo(Event(signal=signals.T))
126 time.sleep(0.1)
127 pp(ao.spy())
Challenging Our Hypothesis#
Let’s bring our question and our hypothesis back into view so we can think about it again:
“Suppose we started the above chart in s11
, then we send a T
event to it,
when would each of the functions, a
, b
, c
, d
, e
, g
, and t
happen?”
Our answer:
if g() returns True, then the function order will be:
g
, a
, b
, t
, c
, d
, e
Let’s examine my own personal psychological state. I have been taking tiny steps to keep my cognitive load light, and right now I’m feeling pretty good. I have a theory, but more importantly I have built up some firm reality outside of myself that I can push against. My sense of possession has transfered from my answer into the structure that will be used to attack this answer.
Moreover, I feel a sense of control and I’m feeling satisfaction from building something. The part of my mind that gets a buzz from pursuit, from seeking is activated and I’m feeling ready to grok something about these statecharts.
If you have actually been doing the work and debugging your own code, well, maybe you feel this too.
Now, let’s pull the trigger and see what happens.
['T:s11_state',
'T:s1_state',
'Running g() -- the guard, which return True',
'Running t() -- function run on event T',
'EXIT_SIGNAL:s11_state',
'Running a()',
'SEARCH_FOR_SUPER_SIGNAL:s11_state',
'SEARCH_FOR_SUPER_SIGNAL:s2_state',
'SEARCH_FOR_SUPER_SIGNAL:s1_state',
'EXIT_SIGNAL:s1_state',
'Running b()',
'ENTRY_SIGNAL:s2_state',
'running c()',
'INIT_SIGNAL:s2_state',
'running d()',
'SEARCH_FOR_SUPER_SIGNAL:s21_state',
'ENTRY_SIGNAL:s21_state',
'running e()',
'INIT_SIGNAL:s21_state',
'<- Queued:(0) Deferred:(0)']
Look, it’s different. We got an order of: g
, t
, a
, b
, c
,
d
, e
.
The answer:
g
, t
, a
, b
, c
, d
, e
.
Now let’s see what happens when we adjust the g
function to return a False:
['T:s11_state',
'T:s1_state',
'Running g() -- the guard, which return False',
'<- Queued:(0) Deferred:(0)']
Now that we understand that, let’s re-ask the question:
“Suppose we started the above chart in s11
, then we send a T
event to it,
when would each of the functions, a
, b
, c
, d
, e
, g
, and t
happen?”
If g() returns False, only
g
is calledIf g() returns True, then
g
,t
,a
,b
,c
,d
,e
.
We know this, because we just confirmed the behavior.
Learning for my Mistake#
If you are deeply familiar with the UML specification for statecharts, you will see that our observed behavior is an infraction. The original answer was supposed to describe the behavior. The good news is that this event processor algorithm is based on the work of Miros Samek.
On pages 80-81 of his book titled Practical UML Statecharts in C/C++ Second Edition he wrote:
One big problem with UML transition sequence is that it requires executing actions associated with the transition after destroying the source state configuration but before creating the target state configuration. In the analogy between exit actions in state machines and destructors in OOP, this situation corresponds to executing a class method after partially destroying an object. Of course, such action is illegal in OOP. As it turns out, it is also particularly awkward to implement for state machines.
Executing actions associated with a transition is much more natural in the context of the source state – the same context in which the guard condition is evaluated. Only after the guard and the transition actions execute, the source state configuration is exited and the target state configuration is entered atomically. That way the state machine is observable only in a safe state configuration, either before or after the transition, but not in the middle.
So, our t
function runs within the context of the thing that asked for the
transition. This keeps it out of the strange limbo state described above.
Let’s think about how we could re-adjust our thinking, by re-asking the question and considering how we could approach it the next time we see something like it.
“Suppose we started the above chart in s11
, then we send a T
event to it,
when would each of the functions, a
, b
, c
, d
, e
, g
, and t
happen?”
Knowing that the source state of our T
event was s11 you would first
re-imagine the diagram as:
Then the answer to the question would just reveal itself from your imagined diagram:
g
,t
,a
,b
,c
,d
,e
ifg
returns Trueg
ifg
returns False