Has anyone managed to create idiomatic xstate bindings?

I’ve been trying to hack away at this with my limited knowledge of creating bindings and coming up short. I’d like to get to an API like this:

type states = Idle | Loading | Success | Failure
type events = Fetch | Resolve | Reject | Retry
type context = {retries: int}

open XState

let machine = Machine.make({
  id: "fetch",
  initial: Idle,
  context: None,
  // Atomic state corresponds to the node type here: https://xstate.js.org/docs/guides/statenodes.html#state-node-types
  states: [Atomic({name: Idle, on: [(Fetch, Loading)]})],
})

But I’m hitting a wall in a couple of places. Firstly, the output of rescript variants aren’t suitable to be passed to XState as state names as they are compiled to numbers or tagged objects where the tag is a number. This breaks a core goal of xstate where the serialised machine should be readable so that you can pass it to a visualisation engine for example.

My goals are:

  • Machines should be serialisable and readable
  • Strongly typed machine functions. eg. machine->Machine.transition(UndefinedEvent) should not compile
  • Idiomatic to rescript developers
  • Idiomatic to xstate developers (there is some wiggle room here)

Any smart ideas or prior art in this space?

3 Likes

Have you tried using polyvariants instead? Polyvariants (without payloads) compile to strings.

I have considered this, but I think regular variants (being nominally typed) make more sense here. A #Fetch event on one machine is not the same as a #Fetch event on another machine.

I haven’t tried it so in practice this might not be a problem, or maybe my intuition about events being only applicable to one fsm is wrong.

Well, I can only say that while your argument sounds correct, I’ve never encountered problems like that with polyvars, so personally I’d at least try if they help solving the problem in question.

1 Like

Quick update, polyvars seem to work quite nicely. I don’t think my concern has any issues in real projects to be honest and I think xstate developers are already familiar with structural types anyway.

Here’s how my current API is looking:

module FetchMachine = Machine({
  type state = [#idle | #loading | #success | #failure]
  type event = [#FETCH | #RESOLVE | #REJECT | #RETRY]
  type context = {retries: int}

  let id = "fetch"
  let initial = #idle

  let states = [
    State({
      name: #idle,
      on: [(#FETCH, #loading)],
    }),
    State({
      name: #loading,
      on: [(#RESOLVE, #success), (#REJECT, #failure)],
    }),
    Final({
      name: #success,
    }),
    State({
      name: #failure,
      on: [(#RETRY, #loading)],
    }),
  ]
})

I have some helpers under the hood to transform this representation into the config that xstate’s createMachine expects but other than that it’s zero cost. I’m hoping to create a ppx too that allows you to define this as an object as you would in JS

4 Likes

I’m not familiar with how xstate works, but ideally there shouldn’t be a ppx or functor here.

I am relatively new to bindings so maybe I am overcomplicating things…

However, I haven’t managed to get any API at all working in a type safe way apart from with the functor as posted above.

1 Like

I think some good bindings for xstate would be welcomed by many, myself included.

Isn’t the xstate team still struggling on their own to make everything compatible with TyeScript? I know I’ve had “fun” chasing down some weird errors caused by their types. I think they are trying to solve this in xstate 5. So maybe it’s not possible to create sound types for everything before that release.

I’m too new to ReScript and bindings to help out, so I can only offer encouragement.

Keep up the great work @tom-sherman!

1 Like

What are you using xstate for? I am struggling to find a use-case within ReScript since we can easily represent state machines with variants already… the visualization tool is nice, but I wonder if all of this extra layers is worth the complexity.

2 Likes

XState is a pretty decent way to use actors, it’s much more than a state machine library (although there is a more minimal @xstate/fsm)

When you bring this into react you have a great way to manage all side effects and state.

1 Like

Right. Need to check out some full blown apps written with this to get a better picture.

Personally I’ve been using it in our TypeScript codebase. But as I transition that into ReScript I end up replacing it with, as you say, variants and pattern matching.

But xstate has some great features out of the box. Like parallel and nested machines. And features such as timed transitions and actors as Tom say.

And it has a strong community, already in a mindset where ReScript could be a natural next step.

So a good interop story with xstate could be beneficial for both communities.

But that’s just my 2 cents :relaxed:

5 Likes

The ideal woukd be to simulate something like Lucy inside ReScript:

https://lucylang.org/

which outputs XState (for interop).

Like tom sherman already attempted:

See also:

2 Likes