The question that someone will invariably ask when learning functional programming after coming from another paradigm is, “What does it look like?” For me, the solution was to open someone else’s project on GitHub to learn. Coming from a Java/Spring background, the first genuinely functional code I saw was from Twitter’s Scala codebase. It was so different, and I had no clue what I was looking at. The feeling for me was similar to the way I felt when I first saw a Picasso.

Just like a Picasso, I could see there was simplicity and structure. Yet, when I took a step back, everything appeared disconnected and foreign. In this blog, part 3 of our foray into functional programming, we’ll be looking at a more practical example of how we can leverage tools like Lodash and TypeScript to write functional code.

Recently, we encountered a situation on a project where we had to apply some grouping and sorting operations to two sets of heterogeneous data. Our project happened to be using Lodash and TypeScript, and using these tools seemed like a perfect fit for our problem.

Identify your Types

One approach that I have often found useful for functional programming is to identify our function signature. In this example, the goal of the code was to take two separate objects and sort them by their location name (i.e. city, province, country, etc.) alphabetically and return a list of the disparate items.

In this case, our source objects had the following types:


type Reports = {
  [reportId: string]: Report;
}
type Report = {
  location: Location;
  ...
}

type Locations = {
  [locationName: string]: Location;
}

type Location = {
  name: string;
  ...
}

Given this, we knew that we wanted to create a function with the following signature:

(reports: Reports, locations: Locations) => Array<Report|Location>

Pipes

Lodash provides a package called lodash/fp, which provides most of the functionality of Lodash, but uses functional interfaces. One of the most useful operators it provides is the Pipe operator, which allows for easy composition of the functional operators. The fp package can be imported using:

import fp from ‘lodash/fp’

The Pipe operator in Lodash is like the pipe operator in Unix, where the result of one command is piped into the next. In the case of Lodash, the result of one function call is passed as the input to the next function call. In some functional programming languages, this operation is natively available and commonly denoted by |>. This operator is expected to be added to JavaScript in the future and is currently under review.

Using the Pipe operator combined with TypeScript enables developers to design and implement their data pipelines by following the types. The process usually starts by defining the input and output types of the system and focusing on how to transform the data to match these types.

Plan of attack

When working with dissimilar types, a common solution is to create a type that can be used to operate on both types. Since our first goal was to sort the items by location name, it made sense to start by grouping the reports by location name. We defined our data pipeline for grouping reports as such:

type LocationName = string;

const groupReportsByLocation =
  fp.pipe<
    [Reports],
    Dictionary<LocationName, Report>>(
  // TODO
)

Type aliasing was used to aide in the readability. Using the type definitions, we can see that the pipe operator takes in Reports and outputs a dictionary of Report by location name. The output revealed a new problem, how can we turn Reports into Report by location name? Fortunately, we were only interested in the values of the Reports objects, so we needed a step to convert the Reports into an array of Report:

const groupReportsByLocation =
  fp.pipe<
    [Reports],
    Array<Report>,
    Dictionary<LocationName, Report>>(
  // TODO
)

To fully implement the group by pipeline, we leveraged the operations provided by Lodash and in the end we created the following:

const groupReportsByLocation =
  fp.pipe<
    [Reports],
    Array<Report>,
    Dictionary<LocationName, Report>>(
  fp.values,
  fp.groupBy(fp.get(‘location.name’))
)

Sorting it out

With the groupReportsByLocation function completed, we were left with two dictionaries where the keys were the location name. We used a similar approach for implementing the sort. We started with the following:

type ReportLocation = Report | Location;

const sortByLocationName =
  fp.pipe<
    [Dictionary<LocationName, ReportLocation>],
    Array<ReportLocation>>(
  // TODO
)
 

To achieve the final output, we realized that an intermediary step was required to make sorting easier. This was done using the Pair operator, which returns an object as an array of tuples for each entry. The final implementation of the sort function was achieved with the following:

const sortByLocationName =
 fp.pipe<
    [Dictionary<LocationName, ReportLocation>],
    Array<[LocationName, ReportLocation]>,
    Array<ReportLocation>>(
   fp.pair,
   fp.sortBy(0),
   fp.map(fp.nth(1))
)

Merging it all together

To complete the data pipeline, we simply merged the two location dictionaries together and applied the sort. The final implementation was defined as follows:

function(report: Reports, locations: Locations):
 Array<ReportLocation> {
   return fp.pipe<
     [Reports],
     Dictionary<string, ReportLocation>,
     Array<ReportLocation>>(
    groupReportsByLocation,
    fp.assign(locations),
    sortByLocationName
  )(reports)
}

The function as a canvas

A key point when working with functional programming is to identify the data transformation pipelines to get the data that you have into the form that you need. Type systems are extremely valuable in designing these pipelines. By starting with what you have and what you need, you can then identify the steps in between to help you assemble your code.

The word ‘assemble’ is an important point here. In the above implementation, we can see that much of the code is implemented using operators within the Lodash library. Most of these operations are standard idioms of functional programming and are available in most functional languages or provided by functional libraries. As the JavaScript specification incorporates more features of functional programming, we can expect to see more of these operators becoming available natively. Over time, we can refactor each operator out to the use native version for added reliability and performance.

This example was a simple glimpse of what functional programming can look like. Many more exciting patterns can be used to allow for more expressive and reliable development. Hopefully, this example has given you a taste for exploring the wonderful world of functional programming! Happy coding!