Practical Functional Programming in JavaScript - Techniques for Composing Data
Tags: javascript, functional, webdev, beginners
Published: 2020-07-15 17:13:13

Welcome back to my series on practical functional programming in JavaScript. Today we'll go over techniques for **composing data**, that is best practices that make life easy when working with structured data inside and in between functions. Composing data has to do with the shape and structure of data, and is about as fundamental as [transformation](https://dev.to/richytong/practical-functional-programming-in-javascript-intro-to-transformation-55hm) when it comes to functional programming in JavaScript. If all transformations are `A => B`, composing data deals with how exactly `A` becomes `B`  when both `A` and `B` are structured data. [From Geeks](https://www.geeksforgeeks.org/what-is-structured-data/)

> Structured data is the data which conforms to a data model, has a well defined structure, follows a consistent order and can be easily accessed and used by a person or a computer program.

Structured data could represent anything from a user profile to a list of books to transactions in a bank account. If you've ever worked with database records, you've worked with structured data.

There's a ton of ways to go about composing data since the territory is still relatively undeveloped. Good data composition means the difference between easy to read/work with code and hard to maintain/annoying code. Let's visualize this by running through a structured data transformation. Here is some structured user data

```javascript
const users = [
  {
    _id: '1',
    name: 'George Curious',
    birthday: '1988-03-08',
    location: {
      lat: 34.0522,
      lon: -118.2437,
    },
  },
  {
    _id: '2',
    name: 'Jane Doe',
    birthday: '1985-05-25',
    location: {
      lat: 25.2048,
      lon: 55.2708,
    },
  },
  {
    _id: '3',
    name: 'John Smith',
    birthday: '1979-01-10',
    location: {
      lat: 37.7749,
      lon: -122.4194,
    },
  },
]
```

Say we needed to turn this user data into data to display, for instance, on an admin panel. These are the requirements
 * Only display the first name
 * Show the age instead of the birthday
 * Show the city name instead of the location coordinates

The final output should look something like this.

```javascript
const displayUsers = [
  {
    _id: '1',
    firstName: 'George',
    age: 32,
    city: 'Los Angeles',
  },
  {
    _id: '2',
    firstName: 'Jane',
    age: 35,
    city: 'Trade Center Second',
  },
  {
    _id: '3',
    firstName: 'John',
    age: 41,
    city: 'San Francisco',
  },
]
```

At a high level, `users` is structured as an array of user objects. Since `displayUsers` is also an array of user objects, this is a good case for the map function. From [MDN docs](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map),

> The map() method creates a new array populated with the results of calling a provided function on every element in the calling array.

Let's try to solve the problem in one fell swoop without composing any data beyond the top level mapping.

```javascript
Promise.all(users.map(async user => ({
  _id: user._id,
  firstName: user.name.split(' ')[0],
  age: (Date.now() - new Date(user.birthday).getTime()) / 365 / 24 / 60 / 60 / 1000,
  city: await fetch(
    `https://geocode.xyz/${user.location.lat},${user.location.lon}?json=1`,
  ).then(res => res.json()).then(({ city }) => city),
}))).then(console.log) /* [
  { _id: '1', firstName: 'George', age: 32, city: 'Los Angeles' },
  { _id: '2', firstName: 'Jane', age: 35, city: 'Trade Center Second' },
  { _id: '3', firstName: 'John', age: 41, city: 'San Francisco' },
] */
```

This works, but it's a bit messy. It may benefit us and future readers of our code to split up some functionality where it makes sense. Here is a refactor of some of the above into smaller functions.

```javascript
// user {
//   name: string,
// } => firstName string
const getFirstName = ({ name }) => name.split(' ')[0]

// ms number => years number
const msToYears = ms => Math.floor(ms / 365 / 24 / 60 / 60 / 1000)

// user {
//   birthday: string,
// } => age number
const getAge = ({ birthday }) => msToYears(
  Date.now() - new Date(birthday).getTime(),
)

// user {
//   location: { lat: number, lon: number },
// } => Promise { city string }
const getCityName = ({ location: { lat, lon } }) => fetch(
  `https://geocode.xyz/${lat},${lon}?json=1`,
).then(res => res.json()).then(({ city }) => city)
```

These functions use [destructuring assignment](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment) to cleanly grab variables from object properties. Here we see the beginnings of composing data by virtue of breaking down our problem into smaller problems. When you break things down into smaller problems (smaller functions), you need to specify more inputs and ouputs. You thereby compose more data as a consequence of writing clearer code. It's clear from the documentation that `getFirstName`, `getAge`, and `getCityName` expect a `user` object as input. `getAge` is further broken down for a conversion from milliseconds to years, `msToYears`.

 * `getFirstName` - takes a `user` with a `name` and returns just the first word of the name for `firstName`
 * `getAge` - takes a `user` with a `birthday` e.g. `1992-02-22` and returns the corresponding `age` in years
 * `getCityName` - takes a user with a `location` object `{ lat, lon }` and returns the closest city name as a Promise.

Quick aside, what is a Promise? From [MDN docs](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise)
> The **Promise** object represents the eventual completion (or failure) of an asynchronous operation, and its resulting value.

I won't go too much more into Promises here. Basically, if the return value is not here yet, you get a Promise for it. In `getCityName`, we are making a request to an external API via `fetch` and getting a Promise because sending a request and waiting for its response is an asynchronous operation. The value for the city name would take some time to get back to us.

Putting it all together, here is one way to perform the full transformation. Thanks to our good data composition, we can now clearly see the new fields `firstName`, `age`, and `city` being computed from the `user` object.
```javascript
Promise.all(users.map(async user => ({
  _id: user._id,
  firstName: getFirstName(user),
  age: getAge(user),
  city: await getCityName(user),
}))).then(console.log) /* [
  { _id: '1', firstName: 'George', age: 32, city: 'Los Angeles' },
  { _id: '2', firstName: 'Jane', age: 35, city: 'Trade Center Second' },
  { _id: '3', firstName: 'John', age: 41, city: 'San Francisco' },
] */
```

This code is pretty good, but it could be better. There is some boilerplate Promise code, and I'm not the biggest fan of the way we're expressing the `async user => ({...})` transformation. As far as vanilla JavaScript goes, this code is great, however, improvements could be made with library functions. In particular, we can improve this example by using `all` and `map` from my asynchronous functional programming library, [rubico](https://github.com/a-synchronous/rubico). And no, I don't believe we could improve this example using another library.

 * [map](https://doc.rubico.land/#map) is a function pretty commonly implemented by asynchronous libraries; for example, you can find variations of `map` in the [Bluebird](http://bluebirdjs.com/docs/api/promise.map.html) and [async](https://caolan.github.io/async/v3/docs.html#map) libraries. `map` takes a function and applies it to each element of the input data, returning the results of the applications. If any executions are Promises, `map` returns a Promise of the final collection.
 * You won't find [all](https://doc.rubico.land/#all) anywhere else but rubico, though it was inspired in part by parallel execution functions like [async.parallel](https://caolan.github.io/async/v3/docs.html#map) and [Promise.all](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/all). `all` is a bit like `Promise.all`, but instead of Promises, it takes an array **or object** of functions that could potentially return Promises and evaluates each function with the input. If any evaluations are Promises, `all` waits for those Promises and returns a Promise of the final value.

We can express the previous transformation with functions `all` and `map` like this
```javascript
// users [{
//   _id: string,
//   name: string,
//   birthday: string,
//   location: { lat: number, lon: number },
// }] => displayUsers [{
//   _id: string,
//   firstName: string,
//   age: number,
//   city: string,
// }]
map(users, all({
  _id: user => user._id,
  firstName: getFirstName,
  age: getAge,
  city: getCityName, // all and map will handle the Promise resolution
})).then(console.log) /* [
  { _id: '1', firstName: 'George', age: 32, city: 'Los Angeles' },
  { _id: '2', firstName: 'Jane', age: 35, city: 'Trade Center Second' },
  { _id: '3', firstName: 'John', age: 41, city: 'San Francisco' },
] */
```

No more Promise boilerplate, and we've condensed the transformation. I'd say this is about as minimal as you can get. Here, we are simultaneously specifying the output array of objects `[{ _id, firstname, age, city }]` and the ways we compute those values from the user object: `getFirstName`, `getAge`, and `getCityName`. We've also come full circle; we are now declaratively composing an array of user objects into an array of display user objects. Larger compositions are easy when you break them down into small, sensible compositions.

Of course, we've only scratched the surface. Again, there are a lot of directions your code can take when it comes to composing data. The absolute best way to compose data will come from your own experience composing data in your own code - I can only speak to my own pitfalls. With that, I'll leave you today with a rule of thumb.

 * If you need to get an object or array with new fields from an existing object or an array, use all.

Thanks for reading! You can find the rest of the articles in this series on rubico's [awesome resources](https://github.com/a-synchronous/rubico#awesome-resources).