[Qt-interest] Resolving nearly orthogonal paralle states

K. Frank kfrank29.c at gmail.com
Wed May 19 18:03:51 CEST 2010


Hi Neil -

On Wed, May 19, 2010 at 8:44 AM, Neil Eccles
<neil.eccles at cambridgeflowsolutions.com> wrote:
> Can anyone help  a state machine user newbie.

Welcome to the world of states!

> I have an app that needs to enable a widget depending on the current state
> of two orthogonal states:-
>
> i.e. I  have two parallel  states each with a true false states in side them

My first comment:  A state machine is a fine way to tackle this, but this
particular problem is simple enough that good ol' if-than code is a
legitimate approach, as well.

> ...
> Qstate *top = new QState(::ParallelStates);
> Qstate *a = new QState(top);
> QState *b = newQState(top);
> Qstate *aTrue = new QState(a);
> Qstate *aFalse = new QState(a);
> Qstate *bTrue = new QState(b);
> Qstate *bFalse = new QState(b);

I will comment that **in this case** using parallel states ("and-states" in
HSM lingo) is probably overkill.

Naively, you have four states:

   a--true_b--true
   a--true_b-false
   a-false_b--true
   a-false_b-false

Even without your wrapper states, a and b, your design has four states,
offering no simplification (at least as measured by the number of states).
Add in your wrapper states, and you have six states -- 50% more than
the naive solution.

> ...
> I am trying to work out how to use the state machine to look after setting
> the enable/disabled for this widget.
>
> Ideas I have had so far include:-
>
> 1.Having one state engine with  the combinations of a and b as unique states
> ie aTrue and bTrue; aFalse and bTrue but this seems bad coding from a
> scaling up principle

If you want to use a state machine, this is the approach that I would
recommend **in this case**.

As for scaling, this generally means what happens when some number of
"things" is increased.  In your case you potentially have two different
numbers: the number of items whose settings can be changed -- in your
case two (a and b); and the number of settings each of these items has --
in your case two (true and false).  Call these numbers m and n, respectively.

This naive solution scales poorly -- you need n^m states (that is n raised
to the power of m).  The and-state solution scales much better -- you need
m*n (m multiplied by n) states, or, if you include the wrapper states, m*(n+1).

But this only matters if m and/or n is actually going to be changed, and
increased to at least a moderately large value.

Does your widget have m = 2 and n = 2, and that's it?  Or are you describing
to us a specific example (where, for simplicity, m = 2 and n = 2) of a more
general widget where m and n are expected to grow in practice?

> 2 Creating two properties inside the widget and setting these when the
> states transition and letting the widget decide if it is enabled or disabled
> (this seems to be moving the state engine mechanics into the widget and
> hiding it, however it could be argued that it is encapsulating it exactly
> where it is needed).

I think that this is also a fine approach, *except* that I would not bother
implementing it as a state machine.  As mentioned above, I would just
use if-then code to enable and disable the widget.

> 3. put a third parallel state with widgetEnabled and WidgetDisabled and
> trying to use guarded transitions. However, I believe that the transition
> guard will be dependent on the other parallel state for example going from
> state aFalse to aTrue should only cause a transition to widgetEnabled if
> bTrue is also set.

Two comments here:

First, it's not unreasonable, but I wouldn't bother adding a third
parallel state.
Your widget already contains an enabled flag.  The parallel state
merely duplicates
this information.  And (whether you use two parallel states or the
four naive states)
the state of your state machine already encodes whether the widget should be
enabled, so a third parallel state is again redundant.

Second, regarding guarded transitions:

If you need to scale, i.e., m and/or n can get big, guarded
transitions (by which
I mean transitions that depend on "extended state data" -- data other than the
formal states of the state machine) are the way to go.  But in this
case, I would
not make the items that have settings (m) and their possible settings (n) actual
formal states of the state machine -- I would make it all "extended state data."
(If scaling is important to you, we could discuss this approach in more detail.)

> Or am I just missing the point and going around in circles?

On the contrary -- I think you are addressing they key issues head on.

> Finally,  I suspect that I am not looking at the state diagram from the
> right angle and will become very clear if the diagram is massaged.

I don't think that it's a question of massaging the state diagram.  I think
it's that your specific case -- m = 2, n = 2 -- is sufficiently simple that
there is little to be gained by refining the state diagram (or even using a
state machine).

After all, exponential scaling, n^m, sounds scary, much worse that m*n.
But 2^2 = 2*2 (and 2^2 < 2*(2+1)).  So if m and n are really fixed at 2,
the "exponential" solution is just fine.

One general comment about state machines:  I think state machines can
be very powerful, and dramatically simplify some programs.  I also think
applying state-machine techniques to simple problems is a very good
way to learn how to use state machines (and specific frameworks, such
as Qt's).  However, I do think -- like all design techniques -- state machines
are sometimes overused.  I consider state machines to be a rather
"heavy-weight" approach, and I find that until a problem reaches a certain
level of complexity, state machines aren't helpful.  They don't make the
code simpler, or easier to understand, or easier to maintain.

I would draw an analogy:  Suppose we had a class that contained two items,
a and b (where two is a fixed, inherent property of the class), and for some
reason, a and b had to be kept ordered, a <= b.  I would be appalled if the
class implementation teed up std::sort (or qsort, or whatever) to impose
the condition a <= b.  And I would be at least annoyed (but probably not
appalled) if this were the case for a class with three items, a <= b <= c.
Even though std::sort (and qsort, etc.) are well-designed and easy to use,
they still add heft, and the scaffolding needed to use them would make
the code more complicated and error-prone.

> Thanks
>
> Neil Eccles

Good luck.


K. Frank




More information about the Qt-interest-old mailing list