FrozenCanuck / Ki

A Statechart Framework for SproutCore
http://frozencanuck.wordpress.com
Other
105 stars 7 forks source link

Why does a state with substates *require* an initialSubstate? #13

Closed devinus closed 13 years ago

devinus commented 13 years ago

Why can't I have a parent state that happens to have substates but is also itself a full fledged state?

Currently it's like this:

When I really just want this:

FrozenCanuck commented 13 years ago

There are few things to take into consideration.

The first issue is with state transitioning. If a state has one or more substates, then the statechart needs to know what substate to enter. Entering the parent state but not one of its substates leaves the statechart in an ambiguous mode. What does it mean to be in a parent state but not its substate? According to Harel's paper, this is not valid.

The second thing is what does it mean to be a parent state. According to Harel, a parent state is an abstraction for the substates. Therefore, the parent state can take on a common event that all the substates handle. It's basically a form of inheritance.

Now, regarding your example code, your CONTACTS state can indeed be used to show a contact screen upon the state being entered. However, if you are not creating or editing a contact but are just in a default mode, then you simply have a state called READY to represent that default state. This is a common practice that I follow when putting together my statecharts. So you essentially have the following:

CONTACTS: Ki.State.design({,
  enterState: function() { /* show the contacts area */ },
  exitState: function() { /* hide the contacts area */ },

  initialSubstate: 'READY',

  READY: Ki.State.design({ ... }),
  CREATE_CONTACT: Ki.State.design({ ... }),
  EDIT_CONTACT: Ki.State.design({ ... })
})

The READY state is a statechart pattern that I haven't externalized, and, immediately, that's something I should probably end up writing a blog post on, just like other statechart patterns I have floating around in my head. But by applying the READY state, it will resolve your issue.

erichocean commented 13 years ago

What does it mean to be in a parent state but not its substate? According to Harel's paper, this is not valid.

Citation please? That's totally valid (and not uncommon) in my experience. It means you're in the parent state (only), and it's no different than adding a useless, empty child state and transitioning to that.

In other words, the semantics of being in a parent state and not one of its potential children is completely well-defined.

FrozenCanuck commented 13 years ago

It could certainly be the case that I have perhaps taken things too strictly with Harel based on how parent states are a explicit form of abstraction and clustering of states. Therefore, Eric, your point is taken.

devinus commented 13 years ago

Did we come to a conclusion on this? I'm still of the opinion that a parent state shouldn't require an initial substate and can itself be a state, but if that violates some core tenets in Harel's paper and somebody has a good argument against it, I can easily change my mind.

FrozenCanuck commented 13 years ago

The framework is now designed so that a state no longer has to provide an explicit substate. If no initial substate is provided then a default empty state is created implicitly for the state. This provides the general equivalent of being able to enter a parent state but not entering any of its substates. Therefore, there is no need to add any additional substates, such as a "ready" state, that has to entered explicitly. Example:

obj = SC.Object.create(Ki.StatechartManager, {

  initialState: 'stateA',

  stateA: Ki.State.design({

    foo: function() {
      this.gotoState('stateB');
    },

    bar: function() {
      this.gotoState('stateC');
    },

    stateB: Ki.State.design({

      reset: function() {
        this.gotoState('stateA');
      }

    }),

    stateC: Ki.State.design({

      reset: function() {
        this.gotoState('stateA');
      }

    })

  })

});

Above, stateA has no initialSubstate defined, therefore an empty state is implicitly created for stateA. Note that the root state for a statechart must always have an initial substate defined, which in the case is definied via the initialState property on the parent object obj.