-
Notifications
You must be signed in to change notification settings - Fork 53
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
Introducing Stability and Structural Sharing to Microstates #113
Conversation
Life is too short to overcomplicate it. Let the Array and Object contents just be what they be.
Contains both `pure()` and `flatMap()`.
This reverts commit 8e9667d.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good. I think that I might be missing a few instances of where things are used because of GitHub collapsing the diff.
I think in parallel to updating microstates/react
and debugging in React Native, we should begin to explore using Applicative
as the primary method of structural sharing.
}) | ||
|
||
Keys.instance(Array, { | ||
keys(array) { return [...array.keys()] } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this to clone the keys? Why not just return array.keys()
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
right...
@@ -0,0 +1,7 @@ | |||
// Pull from preact-shallow-compare | |||
// https://github.com/tkh44/preact-shallow-compare/blob/master/src/index.js | |||
export default function shallowDiffers (a, b) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are we using this function anywhere?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yes, to figure out if the the children changed
import { type } from 'funcadelic'; | ||
|
||
const Values = type(class Values { | ||
values(holder) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we still use this type class?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, in desugar.
Microstates was conceived as a tool to allow developers to write composable data structures that followed the rules of functional programming and immutability. From functional programming perspective, a microstate is the result of a pure function of Type and value.
A Type represents the structure of the data. It describes the relationship between composed types, the operations that can be performed on the composted types, computations that are associated with each type and how the value should be interpreted to create state.
In this example,
Person
Type is composed offirstName
andlastName
which areString
type andaddress
which is anAddress
type. TheAddress
is further composed ofstreetNumber
,street
,city
andcountry
. When we create a microstate usingMicrostate.create
we tell Microstates to interpret the Type and associate the provided value with composted types.For example,
Inside of the Microstates object, we represent the Type as a tree structure. The tree consists of information about each type and types that are composed into it. The
Person
type would be roughly expressed as the following tree.A microstate is an object that is generated from this tree with two kinds of objects: transitions and state. Transitions are closure functions that allow to immutably change the value that was used to create the microstate. State are regular JavaScript objects that are instantiated from their respective ES6 classes.
Microstate object instances are created eagerly but the tree, the transitions and the state on the microstate are created lazily. Very little work is done when a microstate is created. This makes it possible for us to delay building large microstates until the time that the state and transitions on a microstate are needed by the application.
Lazy objects are awesome, but as we started to use Microstates in React applications we found that this was not enough. Our goal for Microstates is to make developer's lives easier. We want Microstates to be a tool that developers trust to lead them to success.
To achieve this goal, we have to make sure that Microstates scales well to whatever level of complexity that developers might require and make it easy to manage this complexity in a performant way. Microstates must make easy what is prohibitively difficult to do on any average project.
We found that the current version of Microstates did not live up to this standard when used with React. React uses exact equality on props to determine if a component needs to re-rendered. Microstate generated state was causing function components to re-render on every transition because microstate's state was not stable.
As I mentioned in the beginning of this PR's description, Microstates was conceived as a pure function of
Type
andvalue
. A pure function that creates an object, will always return a new object regardless if you passed the same value to the object previously. This does not work well with React.React uses references to determine if component props have changed. To prevent unnecessary re-renders, props must reference the same object if value used to create the referenced object did not change.
To make Microstates work well with React, we needed Microstates to re-use previously created state objects if the value for the state did not change. This PR introduces structural sharing between trees to allow us to reuse portions of state where value did not change.
This PR introduces internal changes to Microstates architecture to allow structural sharing. No external APIs are changed by this PR.
The biggest architectural change introduced by this PR is giving the Tree a much bigger responsibility. The tree is now the primary internal mechanism for changing the shape of the data that's used to build the microstate. The tree is now responsible for instantiating the microstate. The
Microstate.create
still acceptsType
andvalue
but it delegates to the Tree to construct thetree
structure and instantiate the microstate for theType
andvalue
.The tree structure that is created by the Tree constructor is similar to previous tree but it only has 2 properties:
meta
anddata
. All other properties are getters that point to values on one of these objects. Themeta
object describes the information about this Tree. It has all of the information used to describe how the state and transitions for the microstate of this tree should be built.data
contains all of the data that has to be stable. Stable data is what is carried over if the value for a particular tree is not changed. When you invoke a state transition, the tree will receive a new tree and figure out how that tree should be applied to the current tree. The merged result will be a new Tree and from this tree the next microstate will be created.This architectural change allows us to control the structural change of the microstate at a more granular level to allow state to be reused. It also allows us to perform more complex transitions by providing a private mechanism to immutably manipulate the tree structure.
I'm excited to see what this new version of Microstates will allow us to do. After this PR, we should be ready to share Microstates with the world. @cowboyd and I are happy about all of this.