korken89 / smlang-rs

A State Machine Language DSL procedual macro for Rust
Apache License 2.0
202 stars 28 forks source link

Guard language and general question #23

Open x37v opened 2 years ago

x37v commented 2 years ago

the README states:

A guard is a function which returns true if the state transition should happen, and false if the transition should not happen, 

But in the examples we see that guards return Result<(), ()>.. functionally I guess they're the same except I see that you can return a custom error instead of (). But, re my question below, depending on the goal of a guard, an Err(()) might feel a little wrong?

I'm wondering what the general idea for a guard is? I'm assuming it is a way to encapsulate some functionality so that Event data can be evaluated and instead of having ButtonSevenDown you can have Button { down: true, index: 7 } and evaluate the a guard button_down, button_seven_down or button_seven ?

x37v commented 2 years ago

I think my confusion around guards is because transitioning from the same state, with the same event but different guards isn't legal:

statemachine! {
    transitions: {
        *State1 + Event1(MyEventData) [guard1] / action1 = State2,
        State1 + Event1(MyEventData) [guard2] / action1 = State4,
    }
}

but if you look at the boost action-guards example you can see that in their setup, it would be:

...
      , "s3"_s + event<e4> [ !guard1 || guard2 ] / (action1, [] { std::cout << "action3" << std::endl; }) = "s4"_s
      , "s3"_s + event<e4> [ guard1 ] / ([] { std::cout << "action4" << std::endl; }, [this] { action4(); } ) = "s5"_s
...
dzimmanck commented 2 years ago

@x37v, I looked at both of your comments and would like to summarize the 3 options you touch on.

  1. We add support for guards to specified combinatorically matching the boost syntax.
  2. We add support for guards to be specified with pattern matching, effectively turning them into different guards.
  3. We add support for a new guard syntax in the style used by Rust pattern matching that allows logic.

The issue I see here will be around error checking. The main reason to NOT allow 2 transitions with the same starting state and event and different guards is that if both guards are true, you can have can have a transition table with a conflicting output state. Given that this crate was originally designed to mimic the boost syntax, I think it makes sense to mimic it where features overlap. This syntax basically allows for an event to be re-used with different logical combinations of guards, forcing the user to explicitly specify a mutually exclusive transition.

x37v commented 2 years ago

The main reason to NOT allow 2 transitions with the same starting state and event and different guards is that if both guards are true, you can have can have a transition table with a conflicting output state.

Yeah.. I have a fork that allows for this and there is an implicit behavior that the first valid transition takes precedence (as it is just a match)

dzimmanck commented 2 years ago

@x37v, why not do a PR? Its new syntax that is useful that is a user is not required to use.

dzimmanck commented 2 years ago

I do think the Rust style match guard is the most elegant approach, although definitely not what boost uses.

x37v commented 2 years ago

@x37v, why not do a PR? Its new syntax that is useful that is a user is not required to use.

it is quite a departure: https://github.com/x37v/smlang-rs

corecode commented 2 years ago

Multiple transitions with same state/event combination but different guards is important for versatility. Otherwise a user needs to expand what a guard could handle into separate events, which can lead to code duplication.