POST DIRECTORY
Category software development

This is Part 2 of a three-part guide on refactoring JavaScript from imperative and/or object-oriented patterns to declarative functional ones. For a background on the concepts used in this tutorial, refer to Part 1.

As a reminder, the point of this series is to demonstrate how to solve a common data transformation using function composition by thinking in terms of “functions first, data last”. Along with that, I want you to learn how to identify and use currying, partial application, and pointfree style. The goal is not for you to feel like a functional programming expert, but to finish this series with better intuition for how to write declarative, composable JavaScript.

And the essence of functional programming (FP) is:

  1. Break down a problem into a set of problems that are as small and/or simple as possible.
  2. Write a function for each small problem.
  3. Stitch together those smaller functions via composition to solve the larger problem at hand.

Makeover Time

Let’s say we’ve been given a task to prepare an array for the DOM by filtering it down to only objects with a given year property value selected by a user. In this guide, we’ll focus specifically on the JavaScript logic needed to prepare the array. Here is a simple collection of (some of my favorite) books.

const books = [
  {
    title: "To Kill A Mockingbird",
    author: "Harper Lee",
    year: 1960
  },
  {
    title: "The Secret History",
    author: "Donna Tartt",
    year: 1992
  },
  {
    title: "Infinite Jest",
    author: "David Foster Wallace",
    year: 1996
  },
  {
    title: "Fight Club",
    author: "Chuck Palahniuk",
    year: 1996
  },
  {
    title: "Harry Potter and the Sorcerer's Stone",
    author: "J.K. Rowling",
    year: 1997
  }
]

Let’s filter down these fine stacks of pulp.

Using a for loop

Our starting implementation is similar to the evens example; it uses a very hands on and imperative style.

const booksInYear = (books, year) => {
  const matches = []

  for (book of books) {
    if (book.year === year) {
      matches.push(book)
    }
  }

  return matches
}

booksInYear(books, 1996)
/* => [
  {
    title: "Infinite Jest",
    author: "David Foster Wallace",
    year: 1996,
  },
  {
    title: "Fight Club",
    author: "Chuck Palahniuk",
    year: 1996,
  },
]; */

Let’s first take advantage of built-in JavaScript abstractions for working with arrays.

Using Array.prototype.filter

const booksInYear = (books, year) => books.filter(book => book.year === year)

booksInYear(books, 1996)
/* => [
  {
    title: "Infinite Jest",
    author: "David Foster Wallace",
    year: 1996,
  },
  {
    title: "Fight Club",
    author: "Chuck Palahniuk",
    year: 1996,
  },
]; */

The predicate function book => book.year === year returns true or false, and only book elements that return true from the predicate are included in the array returned from filter.

If we want to reuse this function, we always have to supply two arguments at a time. Let’s increase the flexibility of this function with currying.

Currying and partial application

const booksInYear = books => year => books.filter(book => book.year === year)

Now the function takes a books argument and returns a partially applied function that takes the year argument. Once books and year are supplied, the function completes its application. With this in place, we can preload the function with books.

const booksIn = booksInYear(books) // year => books.filter(...)
booksIn(1996)
/* => [
  {
    title: "Infinite Jest",
    author: "David Foster Wallace",
    year: 1996,
  },
  {
    title: "Fight Club",
    author: "Chuck Palahniuk",
    year: 1996,
  },
]; */

booksIn(1997)
/* => [
  {
    title: "Harry Potter and the Sorcerer's Stone",
    author: "J.K. Rowling",
    year: 1997
  }
]; */

As it stands, the last bit of data needed to fully apply the function is year, but year is only used by the predicate function to filter the books collection. year is simply data for the logic, not the data we are transforming. Let’s think of the data we actually want to transform as the star of the show; we want that data to be fashionably late.

Argument order: functions first, data last

We want our function parameters to be ordered so that functions come first and data comes last. In terms of data, values that support the transformation come before the values that are transformed. In our current implementation, we don’t have any function parameters, just two data parameters. We are interested in filtering an array of books based on a year value, so that array is the most important bit of information. The year parameter is simply the criteria we use for filtering the array.

In our case, year is supporting the filter method to transform the books collection, so we change our function to:

const booksInYear = year => books => books.filter(book => book.year === year)

Now the parameters are ordered to our advantage for function composition: books comes last. Looking at the body of the function, however, books still appears first (reading left to right). To focus more on the flow of the transformation and less on the data being transformed, our goal now is to also move books to the end of the function body, mirroring its position in the function signature.

Functional wrappers

In FP, everything is a function, so let’s create a wrapper function that creates a functional API for Array.prototype.filter:

const filter = predicate => collection => collection.filter(predicate)
const booksInYear = year => books => filter(book => book.year === year)(books)

filter is a curried function that takes a predicate function, then a collection. Using this wrapper function, books can come last in the parameter list as well as in the function body. Take a look at this snippet:

books => filter(book => book.year === year)(books)

It’s now an anonymous function that gives its sole parameter to another function. Thinking back to pointfree style, this is the exact situation that allows us to trim our code to a more declarative syntax:

const booksInYear = year => filter(book => book.year === year)

booksInYear now returns the result of a partially applied filter function, which itself waits for a collection to actually filter. With the explicit reference to books removed, we can write our function in a more generic way.

const whereYear = year => filter(item => item.year === year)
const in97 = whereYear(1997)
in97(books)
/* => [
  {
    title: "Harry Potter and the Sorcerer's Stone",
    author: "J.K. Rowling",
    year: 1997
  }
]; */
const otherYearable = [{ year: 1996 }, { year: 1997 }]
in97(otherYearable) // => [{ year: 1997 }];

Now it is more clear that our function is applicable to any array of objects where each object has a year property.

Everything to a function

Everything is what in FP? Oh yeah: a function. There are still two elements of logic in our implementation that are not functions: item.year and ===.

const whereYear = year => filter(item => item.year === year)

Similar to what we did with .filter, let’s make wrapping functions that achieve the same functionality through a functional API.

const prop = propName => object => object[propName]

const getId = prop("id")
getId({ id: 10, name: "Alice" }) // => 10

prop is a curried function that takes a property name, then an object, then returns the value of the property name on the object.

const equals = a => b => a === b

equals(1)(2) // => false
equals(2)(2) // => true

equals is a curried function that takes one value, then another, then returns the boolean result from assessing the equality of both values with the strict equals operator.

With these wrapping functions in place, we can write whereYear as:

const whereYear = year => filter(item => equals(prop("year")(item))(year))

Eventually we want to remove the explicit reference to item, for the same reasons we wanted to remove books: to place emphasis on the flow of the transformation, not the data being transformed. To remove an explicit reference to item, it has to to come last in the body of filter‘s predicate function as an argument to a function. Essentially, we want to build a function that is (implicitly) poised to receive an object as an argument (in this case, item). Let’s start by swapping the order of arguments for equals.

const whereYear = year => filter(item => equals(year)(prop("year")(item)))

item is now the last value when reading left-to-right, but it is an argument to the evaluated function of prop("year"), which is then an argument to the evaluated function of equals(year). In other words, item reads last, but it is not a final argument that invokes a preceding function. We cannot refactor to pointfree style yet. To do so, we need a tool that will be introduced in Part 3 (and hinted at in the conclusion). For now, I’m just going to leave you to reflect on this ugly party of parentheses.

Conclusion

Through currying, partial application, pointfree style, and structuring our function signatures to be “functions first, data last”, our implementation now consists entirely of declarative functions.

Another way to think of how we order function parameters is to ask “Which parameters are used to do and/or support a transformation, and which parameter (this will typically be a single parameter) is being transformed?”. The supporting/specifying data should come before the data that is meant to be transformed. By ordering function parameters with intention, we can build up partially applied functions that are poised to transform the final argument provided to them.

Coming up: pipelines

The current implementation is admittedly hard to read. We are using function composition, which is great, but we are doing it manually. It’s awkward and clunky. Since everything is now a function, however, we are in a position to introduce automated function composition. This general pattern involves the following:

  1. Provide any number of functions to create a “pipeline” of functions.
  2. Provide an initial input.
  3. The first function is applied to the initial input, then the next function is applied to the return value of the previous function’s output. Inputs become outputs, outputs become inputs.

We’ll save the magic powers of automated function composition for Part 3, the last phase of this JavaScript makeover.

''