At LiveRamp, we’ve decided to adopt TypeScript for a lot of our backend services, a move motivated by the amazing benefits a type system provides. So far we are very happy with this choice. However, there were a few things we found repetitive, and so we decided to release a couple of packages to improve common workflows.
To build a helpful abstraction, I looked towards functional programming patterns I had used in the past. However, given that not everyone is familiar with functional programming, it was critical to create something more like what the team works with today. This means not going straight to functional purity, but setting out on the path towards it together.
A surprising amount of engineering is really just translating between data structures. However, the translation often requires frequent type casts and repetitive code blocks. To reduce the risk of human error, and increase the fluency, we decided to build an abstraction, allowing for “map” and “flatMap” style interactions that play nicely with the type system.
While we were at it, we decided to implement it using lazy evaluation, which means the operations you do using map and flatMap do not execute until the resulting object is needed.
The resulting package lends itself well to in-place mutation without intermediate variables. This empowers you to express business logic fluidly by abstracting away the boilerplate. Here’s a link to a workbook where we’ll transform an object into a differently shaped object!
Higher Order Promise
Nothing talks quite like an example, so here’s an interactive RunKit notebook.
Summary and Next Steps
These two packages cut down on boilerplate, increasing developer satisfaction and reducing the likelihood of human error. They also maintain the types of the data involved, enabling a very tight feedback loop while the code is under iteration. Finally, their expressiveness enables code to track closely to the actual business logic, making it simple to understand its behavior in aggregate, rather than in isolation.
An interesting area to explore next is modifying mutations to work better with asynchronous data. Essentially, expressing these mutations and objects as Streams, rather than fully formed objects. Another area to explore is intelligent operation sequencing, e.g. moving filter steps earlier in the mutation stream to save unnecessary translation work.
Either way, we’re excited to share the journey together – please feel free to submit pull requests, file issues, or simply keep an eye on us on GitHub!