Skip to content

Instantly share code, notes, and snippets.

@JAForbes
Last active February 10, 2018 03:49
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save JAForbes/dc4bd70b5c274329e18bf7a2b9be0f6a to your computer and use it in GitHub Desktop.
Save JAForbes/dc4bd70b5c274329e18bf7a2b9be0f6a to your computer and use it in GitHub Desktop.
Traffic Light Example with Static Sum Type

There's lots of great sum type libraries in JS. And I think they are all good at different things. For modelling states without a lot of associated data I think these libraries can be a little boilerplatey. I've been working on a library that sits on top of a standard object specification so we can have 1 standard library for traversing sum types but several different interfaces for defining and initializing types.

This library is very much in alpha but you can check it out here: https://gitlab.com/JAForbes/static-sum-type

Despite it's infancy I'm going to use it instead of Daggy, union-type or sum-type because I think it's a lot easier to follow and very applicable to light weight states like the below.

const RoadLight = 
  ylashn.nFold('RoadLight', ['Red', 'Green'])

const Entity = 
  ylashn.nFold('Entity', ['Car', 'Pedestrian', 'Ambulance'])

const Go = 
  ylashn.maybe('Go') // Y | N

// Create a RoadLight:

RoadLight.Red() 
//=> { type: 'RoadLight', case: 'Red', value: null }

// Create a moving Car
Entity.Car( Go.Y() )

// Define behaviour as a fold

// RoadLight -> Entity -> Go
const transition = 
  fold( RoadLight ) ({
    Red: fold( Entity ) ({
      Car: Go.N
      ,Pedestrian: Go.Y
      ,Ambulance: Go.Y
    })
    ,Green: fold( Entity ) ({
      Car: Go.Y
      ,Pedestrian: Go.N
      ,Ambulance: Go.Y
    })
  })

transition ( RoadLight.Red() ) ( Entity.Ambulance() ) //=> Go.Y()
transition ( RoadLight.Green() ) ( Entity.Ambulance() ) //=> Go.Y()
transition ( RoadLight.Red() ) ( Entity.Car() ) //=> Go.N()
transition ( RoadLight.Green() ) (Entity.Pedestrian() ) //=> Go.N()
transition ( RoadLight.Red() ) (Entity.Pedestrian() ) //=> Go.Y()

We can model more things if we like:

const RoadLight = 
  ylashn.nFold('RoadLight', ['Red', 'Green', 'Amber'])
  
fold ( RoadLight ) ({
  Red: TrafficLight.Green
  ,Green: TrafficLight.Red
  ,Amber: TrafficLight.Amber
})

And we can model the stimuli that triggers these transitions if we want too, as just another type.

const RoadLight = 
  ylashn.nFold('RoadLight', ['Red', 'Green', 'Amber'])
  
const Stimulus = 
  yslashn.nFold('Stimulus', [
    'PedestrianCrossing'
    ,'Timer'
  ])

We can compose our existing folds into larger folds to get behaviours for more types.

And we can skip a lot of boilerplate by acknowledging patterns. E.g our Go type can be in 2 states, Y or N symbolizing movement or a lack of movement.

We can use a standard function chain and retain the semantics of existing types like Maybe to write functions for commmon patterns. For example. We may only want to apply the brakes if we are already moving.

We could use fold

const brake = fold (Go) ({
  Y: Go.N
  ,N: Go.N
})

But it's a bit redundant, instead we can use chain which will only call our function if we're in the Y state anyway.

const brake = chain (Go) (Go.N)

In the above case we check the incoming types which may be helpful, but we could also just as easily redefine brake as:

const brake = Go.N

All of the above examples are equivalent.

All our values are serializable so far. So something like this is fine:

JSON.parse(
  JSON.stringify(
    TrafficLight.Road(
      RoadLight.Green()
    )
  )
)

You can pass the value into fold or map or chain or any other standard function and it will work because this library never relies on reference equality or prototype identity.

So you can send these values over the network, you can store states in localStorage or the database. They are already built for persistence.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment