July 4, 2018

Refactoring JavaScript: Imperative to Functional

Imagine refactoring 200 lines of code down to only 20. That's just one of the super powers of functional programming. If you want to see how functional programming can reduce technical debt, bugs, and brain-strain, take a look at this comparison.

Refactoring JavaScript: Imperative to Functional

I recently refactored 200 lines of hard-to-read imperative code and turned it into 20 lines of easy-to-read declarative code by using some functional programming tools and techniques. If you want to see how functional programming can reduce technical debt, bugs, and brain-strain, take a look at this comparison.

If you're not familiar with what I mean by "imperative" versus "declarative" code, I'll try to briefly explain. If you're already familiar, just skip to the next section.

In natural languages, the imperative verb tense is how people issue commands to others. "Make your bed" is an imperative tense sentence. Programming is nothing but writing commands, so it's all imperative in that sense. But some ways of programming require writing more commands than other ways, and you could call that code "imperative" compared to more concise ways of doing the same thing. So it's all relative (though, not relative to you, as if we all have our own opinions about what is or isn't imperative/declarative; I mean relative to other code). function a may be very declarative compared to function b, but function a might look rather imperative compared to function c. Maybe a better way to say it is that it's a spectrum.

So imperative code tends toward verbosity. That's because it micro-manages things, giving commands for very common and trivial little details. The problem with that is that it creates more code to write, read, and maintain and more surface area for bugs creep in and hide. Also, it tends to obscure the meaning and intention of your code, which kills readability. Imagine receiving these instructions:

"Step your left foot forward. Land softly to give the step a light, airy feel. Bend your left leg slightly, stepping on the ball of your foot. Step your right foot forward so it is parallel to your left foot. Your feet should be side by side, a bit more than hip distance apart. Move your left foot to meet your right foot. Your feet should be just touching, side by side. Step back with your right foot. Bend your right leg slightly as you step back, keeping your upper body straight and relaxed. Move your right foot back so it is parallel to your left foot. Make sure your feet are side by side, with about 1 foot distance between them. Place your right foot next to your left foot." [1]

That was an imperative program that, if followed by a human, results in someone doing the lead steps for the Waltz. Could you tell? Probably not. The intention of the commands is not easily discernable from them. Also, can you spot the bug in the program? Good luck :}

A highly declarative way of achieving the same thing would be to just give the command, "Waltz." That's an easy program to understand and maintain, don't you think?

That's pretty much covers it. Now check out below how functional programming can help make your code more declarative and easier to maintain!

What the code should do

Before looking at the code, I'll go ahead an explain what it's supposed to do:

Basically, we have an app that tracks the shipment of items from various suppliers. Tracking information is updated as often as suppliers send out updates. In our view component, we want to display the duration of time that has passed since the last notification from each supplier. We're using react for our view templating and redux to manage front end state. In these samples, all API calls and redux logic has been removed and is just being mocked with some fake data.

Here's the view component (not the code we're going to refactor):

The code we're going to look at is the mapStateToProps function that's being imported into index.js. If you're not familiar with working with redux, the mapStateToProps and connect functions are for the react-redux library, which helps us subscribe components to the redux store so they re-render whenever state changes.

There are three versions of mapStateToProps being imported, the original, a vanilla JS version, and a fp (functional programming) version that uses the ramda library. You can toggle the comments to change which one is run and see the result by pressing the (currently) brownish "Show Preview" button.

Let's take a look at the imperative code:

The Imperative JS Approach

There are several layers of problems with this code.

First and foremost, it doesn't work properly. That's not surprising because there is a massive surface area for bugs to creep in here, and all the looping and branching certainly doesn't help.

Another problem is that it's written inside of mapStateToProps and therefore will recompute all this logic every time some field in the redux store updates. It should be written as a function outside of mapStateToProps and memoized for performance. But that is an issue specific-to using react and redux, and not the focus of this post.

In terms of the actual programming decisions, this code commits several programming sins (in my eyes, anyway). But these problems are not the primary point of this post either, so I'll just skim over them:

  • Using var in 2018, in a codebase transpiled by Babel.js
  • Reassigning variables (related to using var)
    • It's pretty taxing trying to reason about what the value of a
      variable is at any given time if its liable to being reassigned all
      over the place.
  • Using for loops to iterate Arrays
    • These always involve mutating variables and data structures
    • It's safer to iterate lists with pure functions using map/filter/reduce
  • High cyclomatic complexity (overuse of if/else)
    • Branching logic multiplies the possible execution paths your program can take; branching within branching compounds that multiplication. The human brain can't easily grok such complexity.
  • Very, very repetitious code
    • Repetitions increases technical debt
    • Repetition increases surface area for bugs to be introduced and hide
    • Repetition makes it take longer to read code
    • Every programmer knows: "Don't repeat yourself" (DRY)

The main problem I want to focus on, though, is that this approach took 175 lines (200 w/ the original comments). That is in large part due to the fact that much of this code is repetitious; imperative programming and repetion tend to go together.

If you have any sense of code smell, you'll quickly detect that this code is doing the exact same blocks of logic four times. The only thing that changes each time is the supplier. So you can easily reduce this code to almost 1/4th its size simply by abstracting away that repetition.

Something as simple as this would do the trick:

const suppliersLastUpdated = {};

['supplier1', 'supplier2', 'supplier3', 'supplier4'].forEach(supplier => {
    //...the previously repetitious code
    suppliersLastUpdated[ supplier ] = timeSinceLastUpdate

That right there could easily reduce this code by 75%, and would even still allow you to indulge all your imperative programming vices! But we can do much, much better by taking advantage of some built-in JavaScript methods and following a couple fundamental principles of functional programming.

Using just vanilla JavaScript (ECMAScript 2018 to be exact), we can get this code down to about 40 lines.

The Functional JS Approach - Vanilla JS

The functional programming principles we want to follow are that functions should be pure and state should be immutable. The second one is most pertinent here, since the imperative approach above is technically pure.

A pure function is a function that only operates on the the arguments you give it (does not affect state outside its scope) and that deterministically returns the same output every time it's given the same input.

Treating state immutably means not reassigning variables and not altering the values stored in any data structures. If you want to change index 5 of someArray, you need to create a new array and copy someArray's values and set index 5 however you want on the new array.

Things to note about this code:

  • Variables are not reassigned

    • You never have to wonder if a variable's value changed at some point. This is easier to reason about, read, and debug.
  • Objects (including Arrays) are not mutated

    • All data is treated immutably using .map and .reduce using pure
      functions. Technically .sort mutates the array, but that is mitigated
      here by cloning a separate instance with Array.from first before sorting.
    • You never have to wonder if some later code is changing the value
      of your data structures. This is easier to reason about, read, and debug.
  • No imperative for loops

    • for loops inherently require mutating state and reassigning variables
    • map, reduce, and filter are pure and can replace (almost) all for-loops
  • Cyclomatic Complexity of 1 (no if/else)

    • There is only one possible execution path for this code, requiring minimal cognitive overhead to understand
  • Not a single repetition of code

With this refactor we could call it a day and feel pretty good about the amount of technical debt we got rid of and the guarantees we built into the code, sparing the next person who has to modify it (maybe ourself!) from having to keep track of tons of state being mutated across 24 possible execution paths. This is about as concise and purely functional as this code can get in vanilla JavaScript without sacrificing legibility.

However, with the right tools, we can do even better. JavaScript is capable of much more powerful functional abstractions that can reduce this code by half without sacrificing legibility. In fact, it will make the code even more legible (in my opinion, anyway)! You know you're onto something really good in programming when you can drastically reduce the amount of code and make the code more expressive.

The techniques I'm talking about are function composition and currying. If you're not already familiar with these terms, I've tried to explain them elsewhere with some basic examples. Understanding those concepts are prerequisite for understanding the code in the next section.

Unfortunately these techniques are not the default way functions are implemented in JavaScript, neither in the core language nor in most popular libraries. So we either need to implement them ourselves or import them from a specialized library.

Thankfully an amazing functional programming library has been in development for some time now and appears to be approaching v1.0.0. It's called ramda.js and aside from including a ton of really useful utility functions (kinda like lodash/underscore), it's defining features are two-fold: 1. All its functions are curried, and 2. All its functions are designed for functional composition, in that they all take the data you want to operate on as the last argument. It's hard to understand how that last one is a crucial feature, but once you see it in use, you'll understand. Taking data last changes everything.

The Functional JS Approach w/ Ramda.js

Alright. This is where things get good. By using several curried utility functions from ramda we can trim our solution to this problem down to about 20 lines, a 90% reduction in code from the original imperative implementation.

Take a look:

There's a lot going on in that little bit of code, but it's just doing the exact same thing as the vanilla JS approach above. It has all the same benefits of the vanilla code, and then some. The main difference is that by using functional composition (pipe) with curried functions, we avoid having to create a bunch of variables to hold intermediate values between operations. Instead, the values are just passed along for us, in-and-out one function and in-and-out the next. Compared to the vanilla JS code, isn't this incredibly readable and expressive?

It reads almost like plain English.

- `getLastUpdatedAtForSuppliers` takes an array of `item` objects
- groups and hashes them by the value of their `supplier_name`
- maps `getDurationSinceLastUpdate` over the hash, replacing each list with a string
- applies a specification to the hash of strings to transform it to an object of the specified shape
- */
- `getDurationSinceLastUpdate` takes an array of `item` objects
- sorts them by their `updated_at` property
- takes the last item in the array (the most recently updated)
- gets the value of its `updated_at` property
- converts it to a `moment.js` Date
- calculates the duration of time that has passed between then and now
- formats the duration as a `String` of an English phrase
- */

All that in 20 lines. Pretty neat, huh? This is the power of declarative, pure functions, immutable data, currying, and functional composition, all of which ramda gives us for free! Does it get any better? (it does, but this is still worth getting excited over)

Wrapping Up

Hopefully, the examples above make a pretty compelling case for the value of practicing functional programming. It certainly doesn't come naturally and requires a different way of thinking about problems and programs. It is a paradigm after all. But maybe it's time for you next paradigm shift. Those are always fun. It's a whole new world.

Some of the side-effects you'll see from practicing functional programming include:

  1. Less code to read and maintain
  2. Less surface area for bugs to hide
  3. Less complex programs that you can more easily reason about in a principled manner
  4. More expressive, readable code
  5. More productivity among teammates
  6. More happiness in life
  7. Health, wealth, and eternal life

Sorry, got carried away there.

If this little demonstration of the benefits of functional programming doesn't convince you of its merits, please! share your thoughts.

If you have not had the chance to explore how to practice functional programming techniques in your daily programming language, I highly recommend you begin dabbling!

If you're a JavaScript/Node programmer, you'll definitely want to start playing around with ramda.js and it's close friend ramda-adjunct.

Hit me up in the comments!

  1. Instructions taken from https://www.wikihow.com/Dance-the-Waltz ↩︎