Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Initial should accept deeply nested IDs #675

Closed
Andarist opened this issue Sep 22, 2019 · 10 comments
Closed

Initial should accept deeply nested IDs #675

Andarist opened this issue Sep 22, 2019 · 10 comments

Comments

@Andarist
Copy link
Member

While reading SCXML spec I've noticed that it might be possible to specify deeply nested IDs for initial attribute (and <initial> element) - it's not restricted to direct descendants.

Those tests confirm this:

In addition to that - initial should support specifying multiple IDs (to target multiple deep parallel states). This can only be pursued after we add support for deep initials though.

@davidkpiano
Copy link
Member

davidkpiano commented Sep 22, 2019

We'll have to take types into consideration if we decide to do this, as well as dev-time validation. For example, if there an initial: 'foo.bar' but foo has initial: 'baz', how do we resolve this? Do we throw an error?

@mogsie
Copy link
Collaborator

mogsie commented Sep 22, 2019

No, if a state outer has initial: 'foo.bar' it means that when outer is entered, it enters foo, then bar. But if foo is re-entered, then it enters its initial state baz. So there's no conflict.

@Andarist
Copy link
Member Author

Andarist commented Sep 22, 2019

Actually - the first situation described by @mogsie is not correct from what I remember. (crossed out because I haven't taken into consideration that @mogsie was talking solely about entering states - which ain't any different with initial states, you always "enter full path of nodes" up to leaf nodes) You can “skip” some states with initial and jump directly to some deeper state. This skipping also skips whatever executable content that would be defined on transition defined on outer - but that is quite logical as that initial transition has not been chosen after all.

EDIT:// not sure if that was clear from my reply so I’ll try to explain more explicitly what I have meant. For initial specified as foo.bar the foo state is ofc entered as its ancestor of bar - so @mogsie is right about that. I was kinda fixated over initial transitions - they might be skipped.

The relevant part of the spec:

As an example, suppose that parent state S contains child states S1 and S2 in that order. If S specifies S1 as its default initial state via the 'initial' attribute (or fails to specify any initial state), then any transition that specifies S as its target will result in the state machine entering S1 as well as S. In this case, the result is exactly the same as if the transition had taken S1 as its target. If, on the other hand, S specifies S1 as its default initial state via an element containing a with S1 as its target, the can contain executable content which will execute before the default entry into S1. In this case, there is a difference between a transition that takes S as its target and one that takes S1 as its target. In the former case, but not in the latter, the executable content inside the transition will be executed.

@mogsie
Copy link
Collaborator

mogsie commented Sep 22, 2019

👍 I was just responding to @davidkpiano's question about how to resolve it. It is indeed simple to resolve. The fact that an <initial> can contain executable content, which can be skipped by targeting a non-pseudo-state is indeed interesting. I'm not sure how we'd model that in xstate, there's nothing like that. It could be modeled as a transient state, of course, but it would be tricky to get full two-way conversion going, if that's ever going to be a goal.

<state id="S">
  <initial>
    <transition target="S1">
      <my-executable-content />
    </transition>
  </initial>
  <state id="S1"/>
  <state id="S2"/>
</state>
  • Targetting "S" here ends up hitting "S1", and executing the side effect.
  • Targetting "S1" also ends up in "S1" but doesn't execute the side effect.

But maybe that's a different issue?

@Andarist
Copy link
Member Author

Yeah - you were right, I just had some other context in mind (I was looking into supporting <initial><transition/></initial> today) and for a brief moment, I just thought that what you have written meant something else. Was responding while walking and didn't focus enough 😅

The fact that an can contain executable content, which can be skipped by targeting a non-pseudo-state is indeed interesting. I'm not sure how we'd model that in xstate, there's nothing like that.

I'm looking into adding support for this with:

{
  initial: {
    target: 'child_id',
    actions
  }
}

Seems like a straightforward API, closely resembling what is specified by the SCXML spec. Just figuring out how to maintain correct execution order of those actions in combination with entry actions.

It could be modeled as a transient state, of course, but it would be tricky to get full two-way conversion going, if that's ever going to be a goal.

I've started to think that ideal two-way conversion is not possible as XState aims to be more strict in some regards, allowing some of the SCXML semantics only "on-demand" through existing, but non-default APIs/options. With such constraints, conversion from XState to SCXML might be lossy and converting ideally back to XState might not be possible (at least not easily).

But maybe that's a different issue?

Yes and no, for this to be solved we have to add support for deeply nested initial IDs (this issue) and for initial with actions (#466)

@thomasaull
Copy link

@davidkpiano I just had this issue, where, when I try to use a nested state on the initial property of my state machine, I'm getting an error:

Initial state 'anotherState.default' not found on 'myStateMachine'

The example state machine:

createMachine({
    id: 'myStateMachine',
    initial: 'anotherState.default',

    states: {
      default: {},

      anotherState: {
        initial: 'nonDefaultSubState',
        states: {
          default: {},
          nonDefaultSubState: {},
        },
      },
    },
  })

This issue was closed without any comment about why, it seems the issue is still present. Do you consider reopening or is this something which won't be fixed?

@davidkpiano davidkpiano reopened this Feb 3, 2022
@Andarist
Copy link
Member Author

Andarist commented Feb 3, 2022

@thomasaull could u describe ur use case for this feature?

@thomasaull
Copy link

@Andarist In my case, I was trying to start a state machine in an arbitrary state, by stopping the interpreter, update the state machine stateMachine.initial = 'my.nested.state and starting up everything again. However I noticed this does not work as I would expect it, so for my usecase it's useless anyway. Mainly I was just wondering, because the outcome of the discussion seem to lead to that this should work and then it was closed without any further comment. If you guys decide you won't implement it, that's totally fair though

@r1jsheth
Copy link

r1jsheth commented Feb 8, 2022

Following for the similar use case.

@davidkpiano
Copy link
Member

We've discussed this and determined that only child keys should be allowed for initial states, for both implementation simplicity and type safety.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

5 participants