Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

@microstates/union - Use disjoint union types to model state machines

NotificationsYou must be signed in to change notification settings

microstates/union

Repository files navigation

npm versionBundle SizeCircleCIChat on Discord

@microstates/union

Model state machines using disjoint union types.

The equilavance between state machines and disjoint union types.

What makes a state machine so useful is that it must always be inexactly one of a set of discreet states. For example, consider thefollowing state diagram for a machine that models the states of a Promise.

Diagram of a the three states of a promise: pending, resolved, and rejected

A promise starts in thePending state, and then, it either moves totheResolved, or theFulfilled state. But however it ends up, atany given point in time itmust be one of those three states.

Union types (also calledenum types in languages likeJava andTypeScript) have this exact same property. While they allrepresent a single abstract data type, each particularvalue thatyou hold a reference to must be an instance of exactly one concretesubtype.

If we were to imagine a type hiearchy that implemented a scheme likethis, it would look something akin to the following with an abstractsuperclass ofPromise that has three concrete constructorsPending,Fulfilled, andRejected:

Abstract Promise class with three concrete subtypes: Pending, Fulfilled, and Rejected

We can never instantiate an instance ofPromise directly, but instead mustalways hold a reference to exactlyone of its subtypes. Just like astate machine! In fact, you can modelany state machine as a union type, wherethere is exactly one concrete constructor for each discreet state inthe state machine.

Furthermore, you can model each state transition from one state toanother as amethod on the type representing the first state thatreturns an instance of the type representing the second.

Let's pencil in those methods representing these state transitionsinto our type hierarchy.

Pending type has resolve method returning Fulfilled type and rejected method returning Rejected type

As you can see, thePending type has two methods,resolve() andreject(). Theresolve() method returns an instance ofFulfilled,and thenreject() method returns an instance ofRejected.

Notice how it's impossible to transition fromFulfilled toRejected or fromFulfilled toPending because there is no methodto do so.

Using aUnion

You can assemble hiearchies like this on your own, but given howcommon it is to model state machines with types,@microstates/unionprovides theUnion helper to assemble it quickly for you.

TheUnion function takes a set ofname: Function pairs and returnsthe abstract union type, where thename is the name of the state,andFunction is a function that takes the abstract superclass as aparameter and returns a subclass that extends that superclass.

We can use it to define our promise type hierarchy.

importUnionfrom'@microstates/union';constPromiseType=Union({Pending:PromiseType=>classextendsPromiseType{},Resolved:PromiseType=>classextendsPromiseType{},Rejected:PromiseType=>classextendsPromiseType{}});

The abstract type will have all of its concrete types attached to it

typeofPromiseType.Pending//=> 'function'PromiseType.Pending.prototypeinstanceofPromiseType//=> truetypeofPromiseType.Fulfilled//=> 'function'PromiseType.Fulfilled.prototypeinstanceofPromiseType//=> truetypeofPromiseType.Rejected//=> 'function'PromiseType.Rejected.prototypeinstanceofPromiseType//=> true

You can instantiate a member of the union using the staticcreatemethod. Every member has boolean properties to help you determine whatkind of state it is:

letpending=PromiseType.Pending.create();pendinginstanceofPromiseType//=> truependinginstanceofPromiseType.Pending//=> truepending.isPending//=> truepending.isRejected//=> falsepending.isFulfilled//=> false

Anything passed to the create method will be used as thestateproperty of the microstate

letfulfilled=PromiseType.Fulfilled.create('result');fulfilled.isPending//=> falsefulfilled.isFulfilled//=> truefulfilled.state//=> 'result';

In order to add transition methods to a member, just add them to itsclass declaration. Here's how we'd add theresolve() andreject()method. Every union type has a privateto[Member] helper method tohelp you transition between states.

importUnionfrom'@microstates/union';constPromiseType=Union({Pending:PromiseType=>classextendsPromiseType{reject(reason){returnthis.toRejected(reason);},resolve(result){returnthis.toFulfilled(result);}},Resolved:PromiseType=>classextendsPromiseType{},Rejected:PromiseType=>classextendsPromiseType{}});letpending=PromiseType.Pending.create();letfulfilled=pending.resolve('here is some data');fulfilled.isFulfilled//=> truefulfilled.state//=> 'here is some data'

Serialization of Union Types

Internally, the value of a union type is stored as a{ type, value }object which can be confusing since it does not correspond to thenormal way in which microstate values are stored internally. However,storing this type internally is necessary since a microstate tree mustalways be computable from its value.

import{valueOf}from'microstates';letfulfilled=PromiseType.FulFilled.create('data');valueOf(fulfilled)//=> { type: "Fulfilled", value: "data" }

In order to deserialize a union type, you should pass the{ type, value } combination:

letrejected=create(PromiseType,{type:"Rejected",value:newError('something went wrong!')});rejected.isRejected//=> truerejectedinstanceofPromiseType.Rejected//=> truerejected.state//=> Error { "something went wrong!" }

About

@microstates/union - Use disjoint union types to model state machines

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Contributors2

  •  
  •  

[8]ページ先頭

©2009-2025 Movatter.jp