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

Fallible actions support #44

Closed
ryan-summers opened this issue Aug 17, 2022 · 5 comments · Fixed by #73
Closed

Fallible actions support #44

ryan-summers opened this issue Aug 17, 2022 · 5 comments · Fixed by #73

Comments

@ryan-summers
Copy link
Collaborator

Right now, actions are only used to always return data associated with the new state (or no data).

However, in real-world state machines, an action is often fallible (e.g. needs to return a Result).

Currently, with a fallible action, you have to perform the action in a guard. This is undesirable because:

  1. It is unintuitive for users
  2. It doesn't allow for passing data to the final state

It seems like it may be most beneficial to have actions return a Result<NextStateData, Error> to accomodate this use case.

@andresv
Copy link

andresv commented Oct 7, 2022

Just ran into same problem. Your proposed change would be very beneficial.

@oblique
Copy link
Contributor

oblique commented Oct 27, 2022

I had the same issue, but I simulated this by having an intermediate state and an event queue/channel.

A naive example:

use anyhow::{bail, Result};
use rand::RngCore;
use std::collections::VecDeque;

smlang::statemachine! {
    transitions: {
        *Idle + Start / start_something = Starting,
        Starting + StartSucceeded / start_succeeded = Started,
        Starting + StartFailed(anyhow::Error) / start_failed = Idle,
    }
}

struct Context {
    events: VecDeque<Events>,
}

impl Context {
    fn new() -> Self {
        Context {
            events: VecDeque::new(),
        }
    }
}

impl StateMachineContext for Context {
    fn start_something(&mut self) {
        match randomly_fails() {
            Ok(()) => self.events.push_back(Events::StartSucceeded),
            Err(e) => self.events.push_back(Events::StartFailed(e)),
        }
    }

    fn start_succeeded(&mut self) {
        println!("Yay!");
    }

    fn start_failed(&mut self, error: &anyhow::Error) {
        println!("Error: {error}");
    }
}

impl StateMachine<Context> {
    fn process_events(&mut self, ev: Events) -> Result<&States, Error> {
        self.context_mut().events.push_back(ev);

        while let Some(ev) = self.context_mut().events.pop_front() {
            self.process_event(ev)?;
        }

        Ok(self.state())
    }
}

fn randomly_fails() -> Result<()> {
    if rand::thread_rng().next_u32() % 2 == 0 {
        Ok(())
    } else {
        bail!("Oh no!");
    }
}

fn main() {
    let mut sm = StateMachine::new(Context::new());

    // Notice that I use `process_events` instead of `process_event`.
    // I know it's a bad name.
    let state = sm.process_events(Events::Start).unwrap();

    match state {
        States::Idle => println!("new state: Idle"),
        States::Started => println!("new state: Started"),
        _ => unreachable!(),
    }
}

@oblique
Copy link
Contributor

oblique commented Oct 27, 2022

Also in my opinion, if we consider the action to be a transition to the next state and if our action fails at the half of the process, that means we need to restore some of the state back. My example above will force you to implement this correctly. If the action was fallible, most probably mistakes will happen.

@oblique
Copy link
Contributor

oblique commented Nov 14, 2022

Just a recomendation since my PR (#49) affects this:

If you plan to add this feature, one way to provide a way to the user to have owned values, is to pass to the actions a &mut values. With this way, a user can have an Option<T> as state data, and then use .take() to get the owned value.

@dkumsh
Copy link

dkumsh commented Jun 27, 2024

I create a PR for this feature.
Could you please review it.

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

Successfully merging a pull request may close this issue.

4 participants