Xstate: Dot notation for transitioning to substates is confusing

Created on 30 Jan 2018  路  10Comments  路  Source: davidkpiano/xstate

Hi there,

SCXML doesn't seem to use the dot-notation for transitioning to sub-states. Is there a reason why xstate doesn't do the same?

SCXML:

<state id="foo">
  <state id="fooSub">
  </state>
</state>
<state id="bar">
  <transition event="barEvent" target="fooSub" /> <!-- here xstate requires `foo.fooSub` instead of just `fooSub` -->
</state>

It seems that having unique-global state names allows also for substates to transition to their parent siblings, or actually any other parent state; described also in this other issue https://github.com/davidkpiano/xstate/issues/52

<state id="foo">
  <state id="fooSub">
    <transition event="fooSubEvent" target="bar" /> <!-- not sure xstate allows this? -->
  </state>
</state>
<state id="bar">
  <transition event="barEvent" target="fooSub" />
</state>

Thoughts?

invalid question

Most helpful comment

@lmatteis about your implementation, you have states such as MyStatechart_FetchingImages. MyStatechart_FetchingImages_Init, which is a bit redundant. By having state names such as ...FetchingImages_Init, you're still wanting to communicate hierarchy in your state names (which is good), but xstate already does that for you.

How about this?

const statechart = {
  key: 'MyStatechart', // namespace
  states: {
    FetchingImages: {
      on: { RESOLVE: 'Images.Loading' },
      states: {
        Init: { ... },
        More: { ... },
        ByAccount: { ... },
      }
   }
   Images: { 
     states: {
       Loading: { ... }
     }
   }
}

This achieves exactly what you desire - state names that are namespaced, and that maintain the hierarchy: "MyStatechart.FetchingImages.Init", etc.

All 10 comments

Dot notation allows for composition. You can have this config:

const toggleMachine = Machine({
  initial: 'on',
  states: {
    on: { on: { TOGGLE: 'off' } },
    off: { on: { TOGGLE: 'on' } }
  }
});

And you can _reuse_ that in any other statechart:

Machine({
  parallel: true,
  states: {
    frontLight: { ...toggleMachine.config },
    backLight: { ...toggleMachine.config }
  }
});

Pretty nice, right? And the reason that works is because the state keys are _relative_, which was a design decision specifically made to allow composition like the above. In SCXML, you would have to hardcode the exact "frontLight.on", "frontLight.off" etc. transitions and have ad-hoc toggle state machines. That's not ideal.

Also, dot notation translates nicely to representing the exact hierarchical structure of your state machine, e.g.:

// "frontLight.on" refers to...
{
  frontLight: 'on' 
}

This direct translation is not possible by using arbitrary keys that don't refer to the structure of the statechart.

Also, with dot notation, we eliminate the risk of name collisions. That risk becomes much higher when you have global state names in a hierarchical structure, especially when trying to mix in other sub-statecharts.

I'm not sure how much startecharts can be reusable though - as per our discussion on spectrum, it would be hard to find generic "actions" that are tied to a generic statechart.

I'm also not sure about naming collision because as I'm working on a project at work using statecharts I found myself making constants (similar to redux action constants) to avoid naming collisions, and then simply use the constants in my statechart definition such as:

const namespace = 'MyStatechart_'
const states = {
  FetchingImages: namespace + 'FetchingImages',
  Init: namespace + 'FetchingImages_Init',
  More: namespace + 'FetchingImages_More',
  ByAccount: namespace + 'FetchingImages_ByAccount',
  Images: namespace + 'Images',
  Loading: namespace + 'Images_Loading',
}

const statechart = {
  states: {
    [states.FetchingImages]: {
      on: { RESOLVE: states.Images + '.' + states.Loading },
      states: {
        [states.Init]: { ... },
        [states.More]: { ... },
        [states.ByAccount]: { ... },
      }
   }
   [states.Images]: { 
     states: {
       [states.Loading]: { ... }
     }
   }
}

So the JSON still reads nicely, and avoids naming collision. The dot-notation transition though is a bit awkward as you can see there with the string concatenation.

I like the fact that the dot.notation tells you also about the hierarchy, but there could be a programmatic way of retrieving such info via a static method?

Overall I'm not sure the benefits outweigh the cons, but for now it's not really a big problem for me. Thoughts others @mogsie @MicheleBertoli ?

Probably the biggest advantage (and the driving design around the API) is the hierarchy:

Simple states
Represented as strings, such as "green" or "yellow".

Composite states
In other implementations, this would be represented by an array, such as ["red", "walk"], which denotes being simultaneously in the "red" and "walk" states. This can easily be ["walk", "red"] as well, since there is nothing about the states that tells you that "walk" is a substate of "red".

However, in xstate, this is represented as { red: "walk" }, which concisely denotes that we're in the "red" and "walk" states simultaneously, but _also_ that "walk" is a substate of "red".

This is especially advantageous for...

Parallel states
Suppose there are two traffic lights - north and east. In other implementations, to represent being in the "green light of north" and the "red light of east, with the walk sign on" states at the same time, you'd probably have a state value that looks like:

[
  "north",
  "northGreen',
  "east",
  "eastRed",
  "eastWalk"
]

It's difficult to derive the state structure of this, much less use this to represent application state - you would have to write ad-hoc code to represent the states of north, northGreen, etc. and merge them in a safe way. Not easy.

Compare to xstate:

{
  north: "green",
  east: {
    red: "walk"
  }
}

This matches the hierarchical state structure exactly, is much more compact, much more intuitive, and can be placed directly in your application's state - ready to use.

This is why I chose dot notation. I write many statecharts for various applications, and avoiding having to determine the state hierarchy, as well as avoiding having to _name things_ uniquely and redundantly is a huge productivity boost.

@lmatteis about your implementation, you have states such as MyStatechart_FetchingImages. MyStatechart_FetchingImages_Init, which is a bit redundant. By having state names such as ...FetchingImages_Init, you're still wanting to communicate hierarchy in your state names (which is good), but xstate already does that for you.

How about this?

const statechart = {
  key: 'MyStatechart', // namespace
  states: {
    FetchingImages: {
      on: { RESOLVE: 'Images.Loading' },
      states: {
        Init: { ... },
        More: { ... },
        ByAccount: { ... },
      }
   }
   Images: { 
     states: {
       Loading: { ... }
     }
   }
}

This achieves exactly what you desire - state names that are namespaced, and that maintain the hierarchy: "MyStatechart.FetchingImages.Init", etc.

You convinced me! There's a certain elegance with having concise local names rather than global ones.

My only problem (actually comment review by colleagues) are all the dangling 'Images.Loading' string transitions. For instance if Images is changed to ShowImages you have to change stuff all over your code (I also use matchesState which uses the dot-notation to check if it's in such state).

Thoughts on making this a bit more elegant?

That depends, how are you referencing the actual strings? Ideally, these "string paths" should actually never be referenced, since the state value is represented as an object: { Images: 'Loading' }, which you can feed directly back into machine.transition(...).

They're referenced in my rendering using matchesState and also in my statechart definition (transitions).

I'm late to the party here, but I wasn't aware of the dot notation, but thought it would be a cool thing to have, so I was pretty happy when I realized that it's standard.

I'm also late to the party, but I want to say I love the dot notation.
I wrote a utility function to turn an object into an array of dotted paths.

That said, one of the main issues @lmatteis is referencing the current machine state in the components. So that whenever the state graph changes, the components are broken (and we go back to the coupled/decoupled discussion).
In react-automata this is partially solved by using the glob patterns - but I still prefer actions :)

Also, in my experiments, I never had to reference the target of a transition using the dot notation within the statechart. When I move to a different state, I usually take advantage of the initial property.

For example:

const statechart = {
  states: {
    [states.FetchingImages]: {
      on: { RESOLVE: states.Images },
    }
    [states.Images]: { 
      initial: states.Loading,
      states: {
        [states.Loading]: { ... }
      }
    }
  }
}

Would this work for you?

Was this page helpful?
0 / 5 - 0 ratings

Related issues

hnordt picture hnordt  路  3Comments

bradwoods picture bradwoods  路  3Comments

greggman picture greggman  路  3Comments

carloslfu picture carloslfu  路  3Comments

bradwoods picture bradwoods  路  3Comments