microstates icon indicating copy to clipboard operation
microstates copied to clipboard

Serializable transitions and tracking state/transition history

Open rkeeler opened this issue 5 years ago • 3 comments

I've been playing around with microstates for a couple of days now and trying to push them to the limits. I'm very encouraged by what I've seen, and I'm excited for the future of this project. It's obvious that you guys understand state management because nearly all of the pain points I've experienced with other state managements solutions have been handled out of the box by microstates, all while managing to keep a simple to use API. I know this isn't the final form of what you intend for microstates to be, so that's why I wanted to get involved in the discussion now.

Something that I believe is important is the ability to maintain an action/transition log in a serializable form (i.e. plain JS objects). This makes some really cool use cases possible (e.g. redux time travel, synchronizing actions between peers or client-server, etc) that would otherwise be a nightmare to bolt onto a state management solution.

I have not been able to achieve this with microstates yet. I tried a few different solutions, such as using observables and middleware. I am able to maintain a history of states and successfully swap around which point in time is the current active state. The problem is that having the intermediate states isn't always enough, sometimes you need to know what caused the model to go from one state to the next (e.g. what object was effected, with what transition, what set of arguments, and where that object is located in the state tree).

The current structure of middleware provides me 3 of these pieces (the object effected, the transition, the arguments) but does not provide the final piece (the position of that object in the state tree). I could probably apply a graph traversal algorithm and determine the path from the root node to the effected node. However, if you guys agree that supporting this kind of use case is valuable, then some kind of first party implementation would probably be easier and more performant. The nested microstates clearly have some way of finding the root context node they are associated with so that when you invoke a transition you get the root node back. It might be easier to tap into that method of tracking the position of nodes than it would be to use a graph traversal algorithm.

What do you think? Is there a way to do this with microstates currently that I am missing? If not, is this something you would like to see supported? I'm willing to dive into the code base and try to make a PR, but I didn't want to do that without getting a sense for whether my vision for this is aligned with yours.

rkeeler avatar Jul 21 '18 21:07 rkeeler

Very happy that you’re enjoying working with Microstates. Ergonomics and completeness are very important to us and I’m glad to see that it’s paying off for you.

You’re absolutely right. We need a more robust mechanism for manage construction of next microstate.

This is actually where I stopped working on the last PR because current version’s transitions did not allow to control how the lens creates a local microstate.

This is where we want to go. Picostates branch is the next step towards that goal. Identity mechanism gives us a lot of flexibility but a middleware is not implemented yet.

Maybe @cowboyd has some ideas about first steps with the middleware.

taras avatar Jul 21 '18 22:07 taras

@rkeeler You might find my experiments with async-microstates interesting - I don't know if that code works anymore but it had working time travel debugging. This was a spike to see how far I could push the middleware. I suspect it will be much easier to implement in Picostates architecture.

Last week we recorded 1st in a series of podcasts on Microstates.

taras avatar Jul 23 '18 11:07 taras

Something that I believe is important is the ability to maintain an action/transition log in a serializable form (i.e. plain JS objects). This makes some really cool use cases possible (e.g. redux time travel, synchronizing actions between peers or client-server, etc) that would otherwise be a nightmare to bolt onto a state management solution.

This is a great question and the answer is that we definitely need something like this. Like you, we've tried several mechanisms and they haven't been fully satisfying, or the code that you build things with them ends up being difficult to work with.

A lot of these things: time travel, syncing with remote state we want to be able to model with Microstates itself. For example, time-travel in redux is nice for debugging, but not really practical as an undo/redo mechanism. In microstates-land, we've always envisioned this as a microstate.

export const History = parameterized(T => class History {  
  states = [T];
  currentIndex = Number;

  get current() {
    return this.states[this.currentIndex.state];
  }

  undo() {
    return this.currentIndex.decrement();
  }

  redo() {
    return this.currentIndex.increment();
  }
});

// I now have a time-traveling microstate.
let todoHistory = create(History.of(TodoMVC));

todoHistory
  .current.todos.push({name: 'take out trash'})
  .current.todos.push({name: 'buy milk'})
  .undo()
// etc....

The magic that we currently lack is how to intercept any sub transition and push it onto the states array.

I'm going to hand-wave here and say that it will likely be some form of Lens-based function composition. Every node is now aware of its lens, and because the fundamental lens operation is "given a current value embedded within a current context, returning a new, equivalent context with a new value embedded into it." I think that has all of the information you need

  • [x] old value
  • [x] old context
  • [x] new value
  • [x] new context
  • [x] lens describing location.

But the short answer is that it will probably play out as the lens of a given node will be composed with some function to introduce some new logic.

I know this is vague, but its because there is still some learning to do around the best way to compose lenses, and whether we might need some other form of optic like prisms or traversals which we don't really know about yet.

A key goal is to keep whatever "middleware" mechanism we decide on as functional as possible: given some transition happening, return a higher-order transition derived from the prior transition.

In summary "yes it's critical. No we don't exactly know how. But, we hope the mechanism we introduce will make modeling very high level things like history, or state syncing possible with microstates itself."

I hope this answers your question, if in a roundabout way.

As a thought experiment: what would it take to model a synchronizer with microstates?

export const Synchronizer = parameterized(T => class Synchronizer {
  left = History.of(T);
  right = History.of(T);
  
  // ??
});

cowboyd avatar Jul 23 '18 17:07 cowboyd