Responding to Adsett...
We are repeating ourselves, which is a sure sign of talking past each
other. I took one more pass at clarifying my position, but I think we
are close to the point where we need to agree to disagree.
>>As far as mapping the the problem space is concerned, it seems to me
>>that placing lubricating and cooling as entry/exit actions in a single
>>state like [Prepared] is constructing the FSM around a preconceived view
>>of the solution rather than the invariants of the problem space.
>
>
> You still misunderstand. Lubrication on would be the on-entry action of
> [running], lubrication off would be the on-exit action of [running].
> Cooling doesn't get involved anywhere, it was a separate example.
OK, I guess I misunderstood. I thought you provided lubrication and
cooling as examples of why one would need entry and exit actions.
However, my point here still stands. I think the same sort of logic
still applies to any entry/exit action couplet. If they represent rules
and policies that are different enough to justify encapsulation in
separate actions AND they are invoked in different contexts (e.g.,
different times by different events) AND some relative sequencing rules
apply, then I think they should be associated with different states to
provide maximum maintenance flexibility.
>>>The most recent occaision I've had reason for using that coupled on-
>>>entry/on-exit construct was for flagging a fault state. On entry to the
>>>fault state the flag would be set, on exit it would be cleared. You
>>>could do the same thing by performing the action on the transitions
>>>instead but since there could be multiple entry and exit transistions it
>>>was clearer to use the entry/exit actions. Syntatic sugar sure, but
>>>useful. You could also use events to announce the entry and exit and
>>>then implement another state machine to set the flag but other than
>>>increasing the size of the code and the execution time I don't see much
>>>effect.
>>
>>As you may recall, I already said I don't have that big a problem with
>>exit actions per se on a case-by-case basis. I just feel one should
>>understand the trade-off for maintenance risk and that trade-off rarely
>>comes out in favor of exit actions.
>
>
> OK, but this example is identical to the lubrication example you are
> objecting to.
Then I'm confused about which example you are talking about here. I
thought you were talking here about the FSM example you provided. That
was a simple Moore model with no exit actions.
>>>I think you've misunderstood the problem. The fact that direction
>>>consists of four states is a consequence of the user interface not an
>>>internal program design decision. This state machine is using the
>>>signals from the user interface to determine the the signals that will
>>>be fed to the actual motor control.
>>
>>I can't buy that. The UI is a separate subsystem whose job is to
>>convert the user view to the problem solution view. Using ON/ON and
>>OFF/OFF combinations in the interface between the UI and the problem
>>solution is a matter of interface design. The interfaces to the problem
>>solution's subsystems are under the developer's control. Even if the UI
>>wants to export ON/ON and OFF/OFF, each subsystem has its own two-way
>>interface and one can deal the problem in the glue code between them.
>>So my pushback would be to fix those interfaces.
>
>
> As I said I think you've misunderstood the problem. This example does
> not simply receive information from the UI it provides the proper
> behaviour for the UI. It is providing the safety interlocks. And the
> input neutral is definitely distinct from forward and reverse.
You said that the ON/ON combination was a user input error that
triggered a transition to the Stop state. I am arguing that input
errors should be detected in the UI, not the problem solution. So that
combination should never get to the FSM in the problem solution if the
UI software is working correctly. (Unless there is some need for the
system to respond to input errors, in which case the UI should generate
the appropriate <unconditional> transition event itself.)
[I am beginning to suspect the real issue in this subdiscussion is
application partitioning. More on that below.]
>>>>That is very similar to return values being "normal" data when positive
>>>>and error codes when negative. Such semantic double duty can lead to
>>>>problems during maintenance (e.g., when requirements change and make
>>>>some negative values "normal" data or when your criteria for stopping
>>>>changes). IOW, separation of concerns applies to data domains are well
>>>>as behaviors.
>>>
>>>
>>>I can understand why you would think that. It's not really the case
>>>though. In that sense it's more akin to input validation.
>>>
>>>I am with you on the separation of normal data from error codes. The
>>>trouble is all four states are normal in a very real sense. Even if you
>>>were to decide to treat the both directions selected as a separate event
>>>you would still have three direction states.
>>
>>I still think it's still double duty; its is just spread over two data
>>domains.
>>
>>Forward ON; Reverse OFF. Redundantly specifies forward operation
>>Forward OFF; Reverse ON. Redundantly specifies reverse operation
>
>
> Yes
>
>
>>Forward ON; Reverse ON. An input error.
>
>
> Sort of. As I said think deadman switch or e-stop as well. I won't
> object if you want to consider it separately though.
At one point you said you were checking user input.
I think things like deadman's switches are quite different. Ultimately
they are hardware interrupts that would normally be manifested as unique
external events addressed to the FSM. In that case, the FSM's response
would be unconditional.
>>Forward OFF; Reverse OFF. The motor goes to neutral.
>
>
> No. The input state goes to stop, what the motor does is beyond this
> example. Typically the motor will indeed follow, probably to a neutral
> state or something similar but there are likely to be lags.
In the FSM you provided States 3 and 4 both had,
"Tr C - (!Forward && !Reverse) To Neutral. State 2"
Those states only went the State 1 (Stop) in the ON/ON situation.
> Don't get too hung up on this. Remember I said this was a simplified
> example. This input although it exists serves also to stand-in for all
> the other possible ways you get back to the stop state.
I can only discuss what you give me. I think we need to drop this
subdiscussion because I am too confused about what the requirements are.
>>That's the UI's job and
>>shouldn't even be an issue in the controller context except, possibly,
>>as a DbC assertion for the interface to the UI.
>
>
> No. It is very much the job of this routine. This is where it's
> determined that the operator has satisfied the required conditions for
> proceeding. The actual motor control is decoupled from this as it
> should be.
>
> If it helps, consider it part of the UI, after all it's about ensuring
> the operator has satisfied the input requirements and then generating
> the appropriate output to go to the motor control.
I think there are some megathinker application partitioning issues here.
Basically you can get external input from three sources: an
interactive user; from other software, through a programmatic API; or
from the hardware, through network ports, interrupts, etc..
An interactive UI is encapsulated in a subsystem because the controller
doesn't care what the communication paradigm is (GUI, browser, smoke
signals, whatever). In that subsystem one abstracts to the paradigm in
hand (e.g., Window/Control objects for a GUI, Page/Section objects for a
browser). At that level of abstraction the problem semantics is
expressed as simply text data values for window and control labels.
If there is a consistency constraint on the input values, it is
expressed in terms of values. Thus one checks if the value for Control
X and the value for Control Y are both ON in a GUI. Thus the semantics
of what "ON" means and what Control X and Control Y represent is not
relevant. So if one were going to check for consistency in the UI, I
don't think one would use an FSM that that had all the problem space
semantics that your FSM has. OTOH, one should provide simplistic
value-based rules for consistency that the GUI subsystem can check.
[The classic model for this is a RAD IDE like Access where consistency
checks for input form fields are specified purely in terms of field data
domains.]
A programmatic API is essentially a "smart" interface. It could be just
a Facade class or it could be a full subsystem depending on the how many
syntactic mismatches there are between the client software's view of the
functionality and that of the service. (Subsystem interfaces are
two-way explicitly so one can provide that sort of glue to resolve
syntactic mismatches when applying large scale reuse.)
However, a programmatic API represents a client/service DbC contract.
That contract would require that the client provide consistent values
for Forward and Reverse. So, like the interactive UI, bad combinations
should never get to the interface. If the service needs to be informed
that the inconsistency exits on the client side, then there should be a
unique interface message to announce that. That's because the semantics
of the existence of an inconsistency is quite different than the
semantics of Forward/Reverse operation.
If the application is a hardware controller, then processing hardware
signals is part of its job. However, one needs to convert hardware
register data into something the software understands. The natural way
to do that is though event messages when the hardware does a write. One
may have a subsystem whose sole job in life is to provide that
conversion, which can be very handy for hiding things like bitsheet
changes (e.g., ECOs) from the controller's invariant logic. In the end
any asynchronous hardware signal like a deadman's throttle being
released will be manifested directly as a unique event before it ever
gets to the FSM. The FSM will then have an unconditional transition to
deal with it.
Bottom line: if the ON/ON combination is provided by a user, then any
consistency errors should be dealt with in the UI using the UI
semantics. Similarly, for a programmatic API, they should be dealt with
by the client. In either case, if the controller needs to know that the
inconsistency exists, that should be dealt with through a different
interface message with unique semantics. Finally, if such input comes
from the hardware it will already be cast as an event that FSM must
respond to unconditionally.
[BTW, there is a category on Application PArtitioning in my blog that
you may find interesting.]
>>Similarly, the notion of neutral really isn't the same as NOT forward
>>AND NOT reverse. That is manifested in the redundancy when specifying
>>when the motor really is operating in forward or reverse. What is
>>needed is to separate the semantics of direction vs. running, as in:
>>
>>Forward ON. Specifies forward operation, if running
>>Forward OFF. Specifies reverse operation, if running
>
>
> And neutral which is different. A motor can be powered but not in
> either forward and reverse. If you don't believe me try operating an
> automobile without using neutral.
That neutral is different is exactly my point. That's why using
Forward/Reverse to convey that is forcing the directional semantics to
do double duty.
>
> More to the point since this state machine does not deal with the motor
> but the input to the motor, neutral is a distinct input state. The
> operator can distinctly specify neutral and will.
That's fine. I submit, though, that neutral is different than forward
or reverse so one needs to convey that with separate semantics than
value of Forward/Reverse. My problem here is with the semantics of the
data. Another way to handle it would be with a data domain with
different semantics:
OperatingMode ON. Specifies forward operation
OperatingMode OFF. Specifies reverse operation
OperatingMode IDLE. Specifies neutral operation
I am not fond of that because neutral really isn't just another flavor
of direction. So one would get in trouble if, like a hand drill, one
can set forward/reverse once and start/stop the motor many times without
re-specifying direction. So...
>>Powered ON. Specifies motor is powered
>>Powered OFF. Specifies motor is not powered
>
>
> Generally if the motor (controller) is not powered you won't even get
> this far. Error checking during startup should ensure that. Also you
> are confusing the motor state with the input state this state machine is
> providing. The motors own control and states are separate from this
> state machine.
What I am seeking here is a semantics that properly decouples the
direction from what the motor is doing. So if one needs to be careful
about neutral vs. stopped, I would prefer something like:
OperatingMode POWERED. The motor is running in some direction
OperatingMode NEUTRAL. The motor is unpowered
OperatingMode STOPPED. The motor is reset
One achieves the neutral vs. stopped distinction but that is fully
decoupled from the semantics of forward/reverse.
I don't think it matters whether the FSM is the actual motor itself or
some preprocessor for motor commands. The preprocessor still has to
capture the same rules and policies that govern how the motor operates.
IOW, for a preprocessor, all one needs to do is substitute "motor
should be" for "motor is" in the above descriptions.
>>>>I also have a problem with using them to trigger a transition to the
>>>>[Stop] state, which you said was an error state. Since they are input
>>>>alphabet values (i.e., part of the event data packet), what is being
>>>>checked is that consistent data is being provided.
>>>
>>>
>>>As I said that's at least half the point of the state machine. It's not
>>>checking for consistent data per se. The data is perfectly consistent
>>>but something has caused an input that forces a return to stop. Note
>>>that in a complete example more and different kinds of conditions can
>>>cause a return to stop state.
>>
>>You indicated [Stop] was an error state. That's fine. But getting
>>there should be triggered by an event that is raised explicitly by
>>detecting the error condition _outside the FSM_. The FSM /responds/ to
>>changes in the application state by switching states.
>
>
> That's exactly what it is doing, responding to changes in the
> application state by switching states.
I have to strongly disagree. The rules for detecting the error are the
same, but where they are applied is quite different. You have including
the error detection logic in the transition conditions rather than
outside FSM.
I am arguing is that there should be a specific E1:Stop event that is
generated outside FSM based upon those conditions. Then in the FSM
there should be unconditional transitions from State 2, State 3, and
State 4 to State 1 that are all triggered when that particular event is
received. IOW, whoever understands how to detect an error should have
all the information in hand and Just Do It. Then the FSM responds
directly to the announcement that an error exists.
[BTW, I am not making this stuff up. B-) I can honestly say that I
have /never/ seen a state machine where moving to an error state was not
unconditionally triggered by an explicit external event. I think that's
because detecting errors is something that one wants to do as close to
where they occur as possible.]
>>If some other external situation also
>>triggers a reset to the error state, whoever detects that condition
>>should generate the appropriate event. It isn't up the the FSM to
>>detect error conditions in the state of the application (e.g., user
>>input errors); the FSM just responds appropriately when such conditions
>>prevail by consuming the triggering event.
>
>
> That's what it's doing.
Picture me jumping up and down, Yoda-like, screaming at the monitor: No
it isn't! B-) The condition detection (i.e., identifying the ON/ON
combination) is in your FSM as you select a transition. A conditional
transition is a fundamental structural element of the FSM itself. IOW,
the FSM is analyzing the context and making a decision about what to do
next rather than simply responding to a change in context.
This is really an important difference. Detecting condition changes and
generating events is about determining WHEN a behavior response is
needed in the overall solution context. FSMs are about WHAT intrinsic
behavior responses a particular subject matter is responsible for
providing. In my view that is a very important distinction. If we
can't agree that it is an important distinction, we aren't going to get
anywhere with the rest of this discussion.
>>>>If one is using DbC
>>>>for design, that should be a responsibility of whoever generates the
>>>>event. Thus it is a precondition of consuming the event that the data
>>>>in the data packet be consistent. IOW, when using DbC any assertions
>>>>made about the input data are checking the correctness of the software,
>>>>not the consistency of the data.
>>>>
>>>>When the software becomes broken, that's a job for exception handling.
>>>>One might still need a [Stop] state for recovery, but getting there is a
>>>>job for the exception handler rather than FSM design.
>>>
>>>
>>>Getting to stop is a function of working software not broken software.
>>>Responding to faults is something the software must do and do correctly.
>>>Think deadman switch or e-stop.
>>
>>You indicated that going to the Stop state was the result of
>>inconsistent user input data for the directions and that is the case I
>>am addressing.
>
>
> I also indicated you got there from start. And in neither case does it
> indicate there is anything wrong with the software.
Fine, but in this context I am only talking about the ON/ON combination.
That is special because it is an error condition. My objections here
are that: (a) the Forward/Reverse data domains are serving double duty;
(b) user input errors should be detected in the UI; and (c) any error
should be detected as close to where it occurs as possible. Only the
first is an issue for the other transition conditions. I focused on the
error because <I hoped> it would be more obvious what the nature of the
problem is for conditional transitions.
>>>Actually a very common technique in embedded systems is a table of
>>>function pointers. One that a client has me maintain (I didn't design
>>>it) uses a list of pointers to define possible transitions for each
>>>state. Each transistion is defined by a pointer to condition function,
>>>a pointer to an action function to be executed if the condition function
>>>returns true and a pointer to the next state if the transistion is taken
>>>(the condition returns true). If the next state is null the state
>>>doesn't change. And what happens? You can probably guess at his point.
>>>The conditions functions actually perform actions in some cases.
>>
>>That table of function pointers is basically the second form of STT that
>>I mentioned above. The condition function is just the sort of
>>indirection overhead one gets into if one supports the /possibility/ of
>>conditional relationships. If one doesn't use conditional relationships
>>the table just contains the action pointers. (Of course if one also
>>might have exit actions, one needs yet another table.)
>
>
> Note also thst the condition functions map very cleanly to the
> conditions I use in the example I've been using. All that's happened is
> each conditions have been wrapped up in a function. Of course my
> conditions don't perform actions.
I don't think the conditions and what they map to are at issue. To me
the issue is where they should be evaluated.
>>>>[All commercial full code generators employ the event queue
>>>>infrastructure.
>>>
>>>
>>>Not all of them, the one I use doesn't. It uses that function call. A
>>>lot of state machines I've seen use some variation on that. Some use
>>>tables of function pointers, some use other constructs. There are
>>>actually reasons you might not want tables of constant values in some
>>>embedded systems.
>>
>>Just out of curiosity, which one is that?
>
>
> BetterState, WindRiver sells it, although I'm not sure they sell it
> separately anymore. They acquired it as part of their purchase of ISI.
> ISI bought it from R-Active concepts.
OK. That's not a full code generator; it just deals with individual
FSMs. In my world (translation) a code generator produces a full 3GL or
Assembly application generated from a UML OOA model, including computing
space optimization. IOW, UML with an abstract action language is used
as a 4GL.
[FYI, most translation code generators can also produce system
documentation (e.g., in MS Word) from the same OOA model. The solutions
are also portable; they can generate proper code for any supported
computing environment, including target languages, without modification
of the model. All translation tools also provide a model simulator so
the OOA model can actually be executed for validation prior to producing
3GL or Assembly code. (It is SOP to run the same test cases for
functional requirements against the model as one runs against the
executable; only the test harness changes.)]
>>>>>I suppose you could convert all the conditional checks by running them
>>>>>externally and producing event ids for each one. Seems a lot of work
>>>>>just get get a abstract ID though. The other disadvantage I see with
>>>>>that is the actual condition is now several steps removed from the
>>>>>transition it causes.
>>>>
>>>>Not necessarily. Recall my model for Motor. It was actually somewhat
>>>>simpler than your model. Its actions could do everything you said were
>>>>done in your FSM actions and its transitions enforced the same
>>>>sequencing rules that yours did.
>>>
>>>
>>>No it didn't. It failed on two accounts. It never returned to stop and
>>>it didn't have any forward/reverse assymetry (admittedly a later
>>>clarification). It didn't even have a stop state, just a neutral state.
>>
>>That's because in your original FSM description Stop didn't do anything
>>except set DriveRequest to 0, which was what Neutral did.
>
>
> No, I went back and double checked the revised/clarified version I used
> just to be sure. To exit [stop] to [Neutral] direction must be neutral
> and throttle must be 0. To exit [neutral] a direction must be selected
> but it is not necessary to have the throttle at zero. This enforces the
> sequencing that both throttle and direction indicators must be in a
> certain state before starting.
That's the condition for selecting the transition. That has nothing to
do with the object's behavior responsibilities. I was talking about
what the state action actually did (i.e., the rules and policies that
apply when the state condition prevails). With my revised transitions
and state variables those conditions weren't part of the FSM.
>>>It also excluded the event generation.
>>
>>I am not sure what this means. You FSM didn't have any event generation
>>either; it just selected transitions given an event. Do you mean I
>>didn't have conditional transitions? If so, that came with changing the
>>semantics of the state variables and events.
>
>
> I mean you don't have the full logic available. The full logic includes
> the causes of the events as well as their effects. Without that it is
> impossible to tell whether the state machine provides the required
> behaviour.
Then I have to disagree strongly with the second sentence. The causes
of events should have nothing to do with the FSM structure. Events are
supposed to be generated outside the FSM. Therefore the conditions
associated with generating the events (i.e., why they are generated)
should be external to the FSM. FSMs respond to changes in external
conditions, they don't define those changes.
In fact, this is substantially what I have against conditional
transitions -- they effectively move the definition of the cause of the
state change into the FSM, which trashes cohesion and separation of
concerns.
Ignoring FSMs, consider the following pseudo code procedures:
computeBenefits ()
foreach benefit
if (employeeType == hourly)
computeHourlyBenefit (benefit, employeeBaseSalary)
else
....
computeHourlyBenefit (benefit, employeeBaseSalary)
if (employeeBaseSalary < 5000) return
switch benefit
// compute benefit using employeeBaseSalary
Even a die hard procedural developer from the '70s would probably
realize there was something wrong here about the way responsibilities
were allocated and would rewrite this as:
computeBenefits ()
foreach benefit
if ((employeeType == hourly) &&
(employeeBaseSalary >= 5000))
computeHourlyBenefit (benefit, employeeBaseSalary)
else
....
computeHourlyBenefit (benefit, employeeBaseSalary)
switch benefit
// compute benefit using employeeBaseSalary
That's because the decision about whether the benefit should be computed
needs to be evaluated in one place at a higher level of abstraction than
/within/ computeHourlyBenefit. IOW, computeHourlyBenefit's job in life
should be limited to the computation and determining whether the
computation should be done at all is left as a job for a different trade
union. Then when requirements change so that the rules change for
whether a benefit should be computed, one only has to touch the code in
one place.
Conditional events essentially do they same thing as splitting up the
condition in the first version. They move <part of> the definition of
what changed in the outside world into the FSM. The FSM should only
provide behaviors and enforce intrinsic relative sequencing rules among
those behaviors; it should not be concerned with why and how the outside
conditions changed.
>>>>It just did it with different
>>>>conditions (states), events, and state variables.
>>>>
>>>>I think the condition is only one step removed.
>>>
>>>
>>>Instead of
>>> call statechart
>>> check condition
>>
>>You forgot the last step: select a transition. That is equivalent to
>>generating an event below.
>
>
> OK so add select a transition to the below as well. Once an event has
> been generated you still have to match it with the specific transistion.
That's not necessary in my version. When the check is positive the
right event (QW or QE) is generated. The transition it triggers in the
FSM will be unconditional so there is no selection there.
>>>you have
>>> check condition
>>> generate event
>>> call statechart
>>>
>>>I count an extra step. More if you include the fact you are now check
>>>more conditions than necessary for a given state.
>>
>>The point is that somebody always detects a condition and announces it
>>with an event. The FSM always responds to that announcement event by
>>transitioning to a new state and executing the associated action. That
>>is about as peer-to-peer as the collaboration can get without bleeding
>>cohesion between the FSM and the external environment.
>
>
> Which may make sense (probably does) in another environment, here it
> just obscure the logic and adds overhead.
What overhead is added? The checks are, at worst, exactly the same. In
effect one has:
Your caller:
IF (time to generate event)
generateEvent (Forward, Reverse, Throttle)
and in your FSM:
IF (Forward && Reverse)
dispathTable[current state](Forward, Reverse, Throttle);
ELSE
...
while in my caller:
IF ((time to generate event) && Forward && Reverse)
generateEvent (QW, Forward, Reverse, Throttle)
ELSE
...
and in my FSM:
dispatchTable[current state, event ID](Forward, Reverse, Throttle);
The only difference is where the IF checks are located. [Note that you
actually have an extra indirection because in your FSM you need to
invoke both statechart to select a transition and the individual stateN
action while I go directly to the action. Worse, that indirection
exists whether the transitions are conditional or not (unless you write
each FSM without any reuse).]
As far as obscuring the logic is concerned, I'm afraid we are on
different planets here. To me separating (time to generate event) and
the conditions on Forward/Reverse makes it much more difficult to grok
what is going on in the collaboration.
[I use "(time to generate event)" as a placeholder for whatever
condition causes someone in your software to call your "Statechart
function". IOW, it defines whatever the caller did that justifies
generating an event.]
>>When has a condition check that determines the transition inside the
>>FSM, one is bleeding cohesion because the FSM is doing part of the job
>>that the <external> event generator should be doing. Part of the
>>detection (the trigger event) is outside the FSM and part is inside
>>(selecting a transition).
>
>
> Sorry, trigger event? There are no trigger events outside the state
> machine unless maybe you are referring to the fact that the state
> machine is clocked? I don't see it making a practical difference.
Your statechart function's signature is the event that your FSM responds
to. When that function is invoked, somebody outside the FSM is
generating an event. That means some condition has changed in the world
outside the FSM and invoking the statechart function announces that change.
Your FSM responds to that event by transitioning to a new state.
Because you use conditional transitions, the proper transition to
navigate must be selected. But whichever one is selected, it is
triggered by the "statechart function" event. Corollary: that is the
/only/ event your FSM responds to, regardless of what transition is
navigated. If we were to draw the FSM graphically as an STD every
transition would have the same event ID attached to it.
Note that including the dispatch mechanisms explicitly in the FSM
further obscures what is actually going on. (Not to mention having to
reinvent the wheel with each FSM by supplying the same dispatch
infrastructure.) That pretty much hides the fact that your FSM uses
conditional events in a manner that reduces the triggering events to
exactly one. In a normal FSM there would be multiple events, each one
associated with a different causative context. I think attaching those
events to <unconditional> transitions would greatly clarify how the FSM
relates to external context. (It took me awhile to even realize it was
a simple Moore machine because it was such an unconventional
representation.)
>>IOW, detecting condition changes is one thing
>>while responding to them is quite another and I think those two concerns
>>should be separated.
>
>
> They are, into condition and action :)
But they are still tightly coupled in the same subject matter (module,
object, problem space abstraction, or whatever).
>>>>>So far I don't see the abstract IDs as any simpler than the conditional
>>>>>check. Actually I don't see that they are not fundamentally identical
>>>>>unless you have something running around changing values behind the
>>>>>state machines back which would be bad design practice IMO.
>>>>>
>>>>>Why is
>>>>> if( a < DFG ) {
>>>>> QueueEvent(queue, QW);
>>>>> }
>>>>> if( b == GHJZZ ) {
>>>>> QueueEvent(queue, QE);
>>>>> }
>>>>> StateChart( queue, out);
>>>>>
>>>>>superior to
>>>>> StateChart( a, b, out);
>>>>
>>>>There are several reasons. From an aesthetic perspective the FSM
>>>>provides /invariant/ constraints on /relative/ sequencing. They should
>>>>be invariant relative to the external context.
>>>
>>>
>>>And how are the constraints I'm using less invariant than events?
>>
>>They depend upon specific state variable values.
>
>
> So do the events.
EXACTLY! Events announce external changes in the state of the
application. Events are external inputs to an FSM, not part of its
internal its definition. One maps events to transitions in order to
link the fundamental FSM structure to the outside world. And one can do
that after the FSM is designed. However, conditional events make
specific state variable values part of the FSM structure.
>>The FSM doesn't care
>>about the specific values; it only cares that /some/ condition announced
>>by QW or QE exists.
>
>
> So the causes of the transitions is obscured by generating events. I
> can see this as an advantage in some systems but not in the example I'm
> using. If the events are named something convienient like forward I
> don't see any difference from checking the direction flags.
Actually, how one names events is an interesting and ongoing debate. I
happen to belong to the camp that names events for the causative changes
in the environment. That is, I name events for the external situation
rather than the receiving FSM semantics. That links the events very
specifically to the causes.
Let's assume some events are provided from a winch operator. To that
operator the proper perspective might be 'wind' and 'unwind'. For a
crane operator the proper semantics might be 'up' and 'down'. It is up
to the software to map that into 'forward' and 'reverse' for the motor
powering the winch. However, the motor is going to respond in exactly
the same way regardless of what the events are named (i.e., what the
context semantics is) so long as they are associated with the right
transitions. In a case like this I would find it more useful to attach
E1:wind and E2:unwind to the transitions because that provides a better
mapping to the rest of the software context. Since associating an event
with a transition is all about mapping to the external context, that
seems quite reasonable.
[There are other advantages to naming events by the generating context.
For example, allowing the same event from a given object to be
broadcast to multiple receiving FSMs having different semantics. That's
a lot cleaner from the event generation side than generating several
different events that may have different semantics for the various
receivers when the generator shouldn't know who is receiving the events,
much less their semantics in that context.]
>>The sequencing constraints in the FSM define
>>intrinsic constraints on when the state actions can be performed
>>relative to one another without regard to solution context. The
>>constraints on 'a' and 'b' represent a mapping of intrinsic FSM
>>sequencing constraints to specific context semantics in the overall
>>problem solution. That mapping to solution specifics is none of the
>>FSM's concern.
>>
>>Suppose there is another object with a method that does something like:
>>
>>if (x > ABC)
>> QueueEvent (queue, QW)
>>if (y != LMNOP)
>> QueueEvent (queue, QE)
>>StatrChart (queue, out)
>
>
> Keep in mind how constrained the system is. These signals have a very
> narrow effect. The chance of the signals being used by another context
> approaches zero. The chance of the of the same conditions being used in
> another context is even smaller. I've never seen either happen.
My point here is just to demonstrate why it is a good idea to separate
the concerns. It is difficult to anticipate what changes to
requirements the future will bring. Rather than trying to be prescient,
it is better to ignore the future and build for today. But one should
do so in an manner that can be readily maintained in case do things
change; IOW, one builds for generic change. Separating concerns and
encapsulating related rules and policies is a very useful tool for
providing that sort of generic maintainability.
>>>OK so why wouldn't conditions based on those same values be well
>>>constrained?
>>
>>I am talking about data integrity scope here. One can manage things
>>like thread pausing and whatnot easier if scope is defined by a single
>>procedure. Similarly, one can focus on the design issues better if
>>scope of data access is limited to single method scope.
>
>
> It is so limited. It's limited to the single call of the function doing
> the statechart evaluation.
No, in your FSM there are two calls. The transition is selected in your
"statechart function". That's the scope where the condition variables
are accessed. But the actual state action is another method that is
called from the statechart function ("call state action").
Consider what happens if the state variables in the transition
conditions aren't in the event data packet (as they usually would not be
in an OO context). It would be possible for the values to change after
the transition was selected but before the specific state action was
invoked. Now the action might access a data structure that was deleted
as part of the context of changing the state variables. One can prevent
that fairly easily, but one needs to worry about it explicitly because
the relevant scope spans multiple procedures.
>>>>The other thing they can affect is the sequencing of actions within the
>>>>FSM. That is essentially what conditional events do; they use context
>>>>variables to change the structure of the FSM on the fly.
>>>
>>>
>>>No more than events do.
>>
>>Very much more so! If there are no conditional transitions the
>>structure and sequencing of the FSM for the incoming events is
>>completely invariant. It depends solely on {event ID, current state}.
>>No matter how many times that tuple is processed, the path is exactly
>>the same each time.
>
>
> The same is true of the conditionals I've used.
>
>
>>However, if there are conditional transitions the structure is
>>effectively modified. Instead of one possible path for a given event
>>and current state there are multiple paths that depend upon {event ID,
>>current state, state var1, state var2, ...}. Thus the structure of the
>>FSM itself is potentially different each time that {event ID, current
>>state, ...} tuple is processed.
>
>
> No more than it would be if you used the (event ID, current state)
> approach. Different events or conditions cause different transistions
> to occur. That's rather the point isn't it?
>
> All that happens is the conditions get turned into events.
I honestly don't know how to respond here. If there are conditional
transitions, then the FSM structure itself potentially changes each time
a state variable in the condition changes. It is a different state
machine with different transitions...
>>To put it another way, if one drew the
>>FSM as if only one condition existed, it would have a different
>>structure than if one drew it for only one of the other conditions.
>
>
> That last sentence is sort of obvious. If you draw a statechart for one
> event only it's different than if you draw it for another event only. I
> don't see how that's particularly useful though.
That's the point! B-( But the event isn't changing. I could draw
three FSMs for your example, one for each combination of the state
variable values, and use the same event on the transition in each one.
What is changing is the way the states are linked through transitions
(i.e., the fundamental structure of the FSM). IOW, the same event just
triggers a transition to a different state in each FSM.
If the transitions are unconditional, there is only one set of paths
through the FSM for a given set of events. When you introduce state
variables and conditional transitions one actually has multiple
different "pure" FSMs with unconditional transitions and one essentially
chooses which FSM to use as the "current" FSM based upon the state
variable values. The same events are input but they trigger different
paths.
It is exactly like an assigned GOTO in FORTRAN. The graph of the flow
of control changes depending on how the label is assigned. Once a
particular graph is selected it remains that way until one makes a new
label assignment to the GOTO, regardless of how many times it is
traversed. But when one changes the label one now has a new graph. The
flow of control graph in the FORTRAN program is exactly analogous to a
"pure" FSM with unconditional events. The label assignment to change
the graph is exactly analogous to modifying the values of the condition
state variables in the FSM.
>>>>To me that is
>>>>kind of like an assigned GOTO in FORTRAN; it opens all sorts of
>>>>opportunities for unexpected side affects. IOW when one changes the
>>>>data semantics of the state variables one not only needs to worry about
>>>>what one computes from the data but which formula one uses to do the
>>>>computation.
>>>
>>>
>>>That's sort of the point. The result depends on the state you are in.
>>>If you don't compute it there you have to have knowledge of which state
>>>the state machine is in externally when you compute it. As far as I'm
>>>concerned that opens the inner workings of the state machine to external
>>>view too much. The during actions express that clearly and directly.
>>
>>When there are no conditional transitions, one /always/ knows exactly
>>what path will be taken for a given {event ID, current state} tuple,
>>regardless of what might be happening in the overall solution.
>
>
> And with the conditionals one always knows what path will be taken. You
> seem to be arguing against something that isn't occurring.
Au contraire. You only know that if you know what the current values of
the condition variables are. That is pretty much the point. The state
variables in the conditions are assigned dynamically outside the FSM so
there is no way to predict what the new state will be for a given
current state and a given event ID by simply looking at the FSM
structure. You must know the dynamic context outside the FSM at
run-time to determine the actual path a given set of events will traverse.
>
>
>>As soon
>>as one starts to make the path variable, one has an assigned GOTO where
>>the assignment is done outside the FSM.
>
>
> The path isn't variable, at least not more so than using events.
>
> If you are in state A and b is true then you take transition C.
In this case it is also given that you have event in hand; whatever
transition is followed must be triggered by an event.
>
> if b is true then generate event B
> If you are in state A and event B occurs take transistion C
>
> I see no difference in the result of the above approaches.
In the first case I cannot look at the STD with the given event when the
current state is A and determine what the new state will be. To
determine what the new state will be I must also know what the value of
'b' is. That value is dynamic.
In the second case I know that I will always follow the C transition to
the same state when whenever the current state is A and the event in
hand is B. I can determine that just be glancing at the STD. That's
because the new state is determined statically by the FSM structure.
There is a huge difference in the relative complexity of the FSM
operation for the two cases when the uncertainty of dynamic state
variable dispatches in introduced in the first case -- just like
introducing label assignments to GOTOs in FORTRAN. The moral equivalent
of instant spaghetti code.
>>>>That is an issue of separation of concerns. The FSM structure is
>>>>inherently static. Modifying transitions dynamically adds another
>>>>dimension of complexity to the FSM structure and that is probably not a
>>>>good idea.
>>>
>>>
>>>Who's modifying transistions dynamically?
>>
>>You are any time you modify the state variable used to select a
>>conditional transition during the life of the FSM.
>
>
> What are you talking about? The state machine doesn't modify anything
> it uses to make a decision. Sure the inputs will vary on each cycle but
> surely with events the events would be expected to change on each call
> as well.
The events don't change. When a given context prevails externally the
same event is always generated. The current value of the state variable
essentially identifies a particular unconditional transition for the
event to trigger. Changing the state variable substitutes a different
unconditional transition for the event to trigger.
What I think you are referring to is the difference between a generic
event whose condition does not include all the condition details and a
specific event whose condition does include the condition details. Your
"Statechart function" is a generic event whose full condition has not
been specified. It's condition is simply "(time to generate event)".
It only becomes fully specified when one includes the state variable
values used to select a specific transition. Once those state variables
are specified, one has a specific event that corresponds exactly to one
transition choice.
In your example the condition on your "statechart function" event is
simply the criteria that indicates it is time to generate some sort of
event for the motor. For a given current state that event is associated
with /each/ of the exit transitions from that state; it can trigger any
of those transitions. Each exit transition has a particular state
variable condition associated with it.
generic event -> condition: (time to generate event)
select transition A -> condition: Forward && Reverse
select transition B -> condition: !Forward && Reverse
select transition C -> condition: !Forward && !Reverse
That is exactly the same as:
event X -> condition: (time to generate event) && Forward && Reverse
event Y -> condition: (time to generate event) && !Forward && Reverse
event Z -> condition: (time to generate event) && !Forward && !Reverse
where I associate:
event X with <unconditional> transition A
event Y with <unconditional> transition B
event Z with <unconditional> transition C.
The only difference lies in whether the condition to be evaluated is
split between the point where the condition is raised and the FSM or
entirely localized where the condition is raised.
However, that distinction has profound affects on maintainability and
cohesion. That is manifested in a much simpler FSM achieved at the cost
of a more complex condition for generating the event. And the
complexity of the condition is mitigated by the fact that all the rules
and policies describing the cause of the event are located in one place
and that place is where the cause originates.
>>>>One way that is manifested is in the notion of OO announcement messages
>>>>(I'm Done) compared to procedural imperative messages (Do This). The
>>>>event being generated announces a change in condition /outside/ the FSM.
>>>> That is, the event itself is announcing something happening
>>>>externally. As it happens that depends upon 'a' and 'b'. But it
>>>>announces a compound condition that also includes whatever the caller
>>>>did. IOW, the condition being announced is {(a < DFG), caller completed
>>>>its part of the solution} OR {(b == GHJZZ), caller completed its part of
>>>>the solution}.
>>>
>>>
>>>Or just as likely both. The state machine then has to discard the
>>>irrelevant events.
>>
>>I think ignoring events in the STT is a quite different thing than
>>conditional transitions.
>
>
> But it does become a question of efficiency. I've become used to having
> more than 16K of RAM available but I'm still not inclined to waste it by
> providing a queue most of the elements of which are used to buffer
> items that will be generated and then immediately thrown away. That's
> w/o mentioning the extra time taken.
This is yet another OT issue. I disagree; the net size penalty of an
event queue is minor. It certainly pales in comparison to the size of
the redundant infrastructure code built into every one of your FSMs.
However, given our existing disagreements I don't want to go here and
open up new ones. So let's agree to disagree on this.
>>There is no law that requires one to do
>>something in response to raising a condition, so it is valid to ignore
>>certain events when the FSM is in a particular state.
>>
>>The issue here is choosing a transition path from among alternatives
>>_when the event cannot be ignored_.
>
>
> What? Choose via condition or choose via condition transformed in an
> event, I see no difference in the end result. I do see the condition
> code adding a layer of complexity.
All I am saying is that ignoring events is completely orthogonal to
conditional transitions. Semantically they are different; the
mechanisms for handling them are different; it's apples & oranges.
let's not get side tracked on ignoring events.
>>>>More importantly, it includes a context decision that the IF statements
>>>>themselves define. Those IF statements implement problem space rules
>>>>and policies to determine exactly What should be announced.
>>>
>>>
>>>Beg pardon? I think you are envisioning an entirely different kind of
>>>problem than I am. The state machine must be aware of all the inputs
>>>(or events) anything else would be unsafe. The only way anything
>>>external to the state machine could filter the events/data to a subset
>>>would be if it were aware of the state it was in.
>>
>>The FSM only needs to be aware of events and the relevant input alphabet
>>for a transition that is processed by the corresponding action. That's
>>all an FSM does: it responds to events by processing the the input alphabet.
>
>
> And?
>
>
>>My point here is when you select a transition you are defining a
>>particular state for the FSM (i.e., the target state for the selected
>>transition).
>
>
> Of course.
>
>
>>The precondition for entering that state is a compound
>>condition composed of:
>>
>>- the postcondition of the action the generated the trigger event
>
>
> So an event occurred/a condition was met.
>
>>- any data integrity constraints on state variables to be accessed by
>>the action
>
>
> ??? I think you are envisioning a different problem set.
The state action usually accessed state variable data. There will be
conditions on the timeliness of that data (i.e., it has been properly
updated).
>>- the conditions used to select the transition.
>
>
> That's a repeat of the first line.
I don't think so. Whoever generates the event (invokes your "statechart
function") has done something that changed the state of the application.
That something is characterized by the postcondition of the caller
method (i.e., the first line). The event is announcing that
postcondition prevails. That postcondition is quite different than the
specific conditions that determine which transition to navigate.
>>IOW, the conditions used to select the transition are necessarily part
>>of the precondition for entering the target state. If they did not
>>evaluate TRUE, transitioning to that state would be inappropriate.
>
>
> The conditions to enter the state must be true or you can't enter the
> state. That much does seem obvious.
>
>
>>The primary issue of this whole subdiscussion is whether that
>>precondition should be evaluated in one place or in two places.
>
>
> What? I'm evaluating as the check on the transistion. You are
> evaluating to generate events. I don't see a difference.
That is only part of the condition. The other part is the condition
that raises your generic event. Whoever generates the event has done
something to change the state the the application and whatever that is
must take place before the precondition of the action is satisfied.
That is what I keep referring to above as "(time to generate event)" for
lack of more detailed requirements.
As I see it, the fundamental issue here is splitting up those clauses of
the compound precondition between the caller's postcondition and the
FSM's transition conditions. I think that is a very bad idea, which is
why I never use conditional transitions.
>>>>The FSM
>>>>only responds to the announcement. If one moves the IF statements into
>>>>the FSM itself, one is moving that external context decision into the
>>>>FSM. That external context decision probably isn't any of the FSM's
>>>>business; it should just respond to the decision's /result/.
>>>
>>>
>>>?? That external context is the entire reason for the state machine to
>>>exist. Having said that the external context may be abstracted or
>>>idealized. The direction input could be a single multivalued input
>>>rather than a pair of flags as a for instance.
>>
>>The state variables being tested /are/ the external context. Forward
>>and Reverse or 'a' and 'b' in your examples have their values assigned
>>based upon conditions that exist outside the FSM.
>
>
> Yes and that's the entire reason for the state machine to exist is to
> evaluate what state to be in as a result of those values.
Once again, picture me jumping up an down screaming at the monitor: no,
No, NO! B-)
Events are raised by context outside the FSM. All the FSM does is
respond to the change in condition. It is not the FSM's job to evaluate
whether a change in condition has taken place or analyze the nature of
that change to determine what to do. It is the developer's job to
provide the mapping of the FSM structure to the external context by
assigning events to <unconditional> transitions.
>>They have semantics
>>that is defined in terms of that external context.
>>
>>I am arguing that such external context should be evaluated externally
>>(preferably in one place) and an appropriate event generated to the FSM
>>based on that evaluation. If one always encapsulates the rules and
>>policies for detecting state changes that way in a place that logically
>>understands the context semantics, one never needs conditional events in
>>the receiver FSMs.
>
>
> You've just called them events instead and now to unserstand the state
> machine you have to have the state chart on one page and the definition
> of all the events on another. It'll certainly work but for the context
> I'm using it in it does seem pointless complexity.
The event just announces the change in condition. Detecting that change
in condition requires an evaluation that applies particular rules and
policies. The FSM describes intrinsic object behavior that _responds to
changes in external condition_. The FSM doesn't need to know and
shouldn't know what the criteria were for detecting the change; it just
needs to know the change occurred and the event tells it that. Mapping
the event to a transition allows the developer to link the intrinsic FSM
behaviors to the external context. But that can be done after the FSM
has been designed.
What pointless complexity?!? The FSM without conditional events will
clearly be simpler, if for no other reason than the lack of conditions,
and it will certainly be easier to understand the intrinsic sequencing
constraints without worrying about the dynamics of state variable
changes. The rules and policies for detecting state changes still need
to be encoded, but if the change detection is separated from the
behavior responses one can focus on each much more easily.
>>>>Finally, when managing complexity it is wise to isolate related rules
>>>>and policies via encapsulation. It is highly unlikely that the rules
>>>>and policies that determine what event to generate will be logically
>>>>related to the rules and policies that the FSM exists to resolve. So if
>>>>one moves those rules and policies into the FSM, one is trashing the
>>>>cohesion of the FSM.
>>>
>>>
>>>While there is a lot to encapsulation and isolation it is quite possible
>>>to take it too far. Data and actions that are tightly coupled should
>>>not be separated.
>>
>>True. That's why one needs a design methodology to draw the lines in
>>the sand. The OO paradigm draws lines in the sand based on problem
>>space abstraction while Structured Programming drew them based upon
>>functional decomposition.
>>
>>But I would still argue that the rules and policies for detecting state
>>changes are going to be quite different than those that govern the
>>response to state changes, regardless of what methodology one uses. B-)
>
>
> Condition and action are different. Well yes.
If you have apples and oranges, putting them in different bins will tend
to make it easier to manage them.
*************
There is nothing wrong with me that could
not be cured by a capful of Drano.
H. S. Lahman
hsl@pathfindermda.com
Pathfinder Solutions
http://www.pathfindermda.com
blog: http://pathfinderpeople.blogs.com/hslahman
"Model-Based Translation: The Next Step in Agile Development". Email
info@pathfindermda.com for your copy.
Pathfinder is hiring:
http://www.pathfindermda.com/about_us/careers_pos3.php.
(888)OOA-PATH