Stop Writing for-loops (compose)

Introduction

Grasshopper3D Screenshot — Function outputs flow directly into function inputs

This article is covers an important part of functional programming: function composition.

I will briefly describe what function composition is at a high-level; show you what function composition looks like in Javascript; implement two functions, pipe(..) and compose(..), that will help you leverage function composition in your own code; and along the way discuss why function composition may be a tool you should be comfortable exploiting.

We will look at a few code examples that use function composition along with some other functional programming techniques. Some of these techniques have been covered in previous articles I’ve written while others may not have been explicitly covered. The examples should still be intelligible nonetheless.

What does function composition look like?

Before we dive into the details, let’s take a look at what function composition looks like in Javascript. The following example uses these helper functions:

  • clog: logs and returns a value
  • path: retrieves a value from an object
  • parseJSON: parses JSON
  • httpGet: makes an HTTP GET request for the provided URL and returns the response object (see note)
const url = 'https://api.icndb.com/jokes/random';
compose(
clog,
path(['value', 'joke']),
parseJSON,
httpGet
)(url);
// => "There are no steroids in baseball. Just players Chuck Norris has breathed on."

Note: to simplify this example, let’s assume that our httpGet(..) function is a synchronous, blocking operation.

This example first fetches some JSON data at the provided url. It then parses it, retrieves a joke property from the data, and logs that value to the console.

If you’re new to functional programming in Javascript, this snippet could look uncomfortably unfamiliar and perhaps somewhat mystical.

Function composition in Javascript relies on a few, helpful abstractions that aren’t difficult to understand. We will breakdown the mechanics of these abstractions to examine what’s going on behind the scenes.

Moving forward we’re going to start with a simple function defined in an imperative programming style. We’ll take that same function and transform it — refactoring it to a more declarative programming style using function composition.

Example 1

Now we’ll write a function, calc(..), that accepts a number, x, squares it, adds 10 to the result of that operation, and divides the sum by 2. Let’s express this equation in Javascript to get a more tangible understanding of the problem.

function calc(x) {
const squared = Math.pow(2, x);
return (squared + 10) / 2;
}
calc(3);
// => 9

Of course you can refactor the logic of calc(..) to be more condense. This would obviate our temp variable, squared and potential tidy up our code.

function calc(x) {
return (Math.pow(2, x) + 10) / 2;
}
calc(3);
// => 9

When writing in an imperative style, it is common to refactor code like this — eliminating temporary storage variables that hold intermediary values that will be eventually passed as input into the next step of the function.

I have refactored code like this numerous times. But frankly, while we may benefit from removing unnecessary intermediate steps like variable allocations, we may lose code readability.

In this particular example, it isn’t paramount that we choose either snippet over the other since both are simple and easy enough to read. However for larger, more complex functions, the trade-off between code readability and code conciseness becomes increasingly more difficult to weigh.

I believe that the less time we can spend fixing mundane errors related to mismatching parens, miscalculating the order of our operations, or creating names for temporary storage variables, the more time we can spend writing meaningful logic for our applications.

Enter function composition.

Function Composition

Function composition may be able to help us out with a few of our problems. Overall we have two options that allow us to compose functions together to form new more complex functions:

  1. pipe: combines functions together that are evaluated from left-to-right
  2. compose: combines functions together that are evaluated from right-to-left

The choice between using pipe(..) or compose(..) isn’t something you should sweat. I find that I can read function pipes more naturally since I am comfortable writing code in a language that reads from top-to-bottom, left-to-right. Piping should also be familiar to developers who have worked with pipes in shell scripts. e.g.

$ find . -type f -name '*.js' | xargs cat | wc -l

However, it seems to me that the compose(..) syntax is more traditional in functional programming, and for that reason people who are coming from that background may prefer it.

I believe that since both yield the same results, you should choose whichever feels more natural to you and your team.

pipe(..)

In a previous article, we recreated a function that we called pipe(..). Here’s an implementation of a pipe function in case you would like to examine its construction.

const pipe = (...fns) => (...args) =>
fns.slice(1).reduce(
(result, fn) => fn(result), fns[0](...args));

compose(..)

Here’s the implementation of a compose function. Notice how similar it is to pipe(..).

const compose = (...fns) => (...args) =>
fns.reverse().slice(1).reduce(
(result, fn) => fn(result), fns[0](...args));

With pipe(..) defined, we can start writing some interesting Javascript.

pipe(
x => -x,
x => x * 2,
x => x + 5
)(10);
// => -15

If we trace the course of our pipeline from top-to-bottom, we can see that we…

  1. negate 10…
  2. multiply that result by 2…
  3. add 5 to that product giving us -15

In case you haven’t noticed the strange syntax…

pipe(...)() // <-- looks strange

…it is important to point out what is happening here.

When a pipe(..) or compose(..) is executed, it returns a new function. This is analogous to scenarios in Javascript where a function returns an array and we chain additional function calls onto the end of the original function call like this:

'This is a string'.split(' ') // returns an array
.map(char => char.toUpperCase())

This works because String.prototype.split(..) returns an array, which can then have Array.protoype.map(..) called upon it.

Speaking of map(..), function composition plays nicely with iterable operations. For this, we will need curried forms of our iterable functions, map(..), reduce(..), and filter(..). If you are uncomfortable with currying or partial application, check out the previously released article that covers these topics.

const fn = compose(
reduce(add, 0),
map(divide(_, 2)),
filter(isEven)
);
fn([11, 40, 21, 2, 14, 16]);
// => 36

Need help debugging? Inject the clog(..) function from earlier into compose to examine your outputs at each stage in the function.

const fn = compose(
clog, // 36
reduce(add, 0),
clog, // [20, 1, 7, 8]
map(divide(_, 2)),
clog, // [40, 2, 14, 16]
filter(isEven)
);

Why use function composition?

Function composition provides developers with a wonderfully declarative syntax that showcases some of Javascript’s beauty. Pipelines can be created with named functions, anonymous functions, or a mixture of the two. However, we can benefit the most by composing exclusively with named functions — enriching our code with meaningful semantics.

For the following examples, we’re going to use Ramda.js. Specifically, we’re going to use the following functions, so if you’re following along, add this snippet to the top of your file.

const { __: _, add, curry, divide, pipe } = R;

We will also need to implement a function called pow, which looks like this…

option 1

const pow = x => y => Math.pow(x, y);

option 2

const pow = curry((x, y) => Math.pow(x, y));

Note: for the purposes of our example, either implementation will be fine. It’s important to realize, however, that there are meaningful differences between option 1 and option 2. For more information, check out the previous article on function currying and partial application.

With all of these prerequisites, we can rewrite our calc(..) from earlier as…

pipe(
pow(2),
add(10),
divide(_, 2)
)(3);
// => 9

By removing the squared variable from earlier, we are eliminating intermediate identifiers. This is an optimization because it reduces the cognitive load on the developer by freeing him or her from having to create a meaningful name for the identifier.

To recap, function composition is useful because:

1. It’s declarative.

The syntax is easy to read. Declarative programming styles benefit developers by freeing them from many of the implementation details. The helper functions, like map(..), or compose(..), or reduce(..) take care of operations like creating arrays, objects or other intermediary values and assigning values to them.

These helper functions come with contracts and terms of usage that describe their intended behavior. If developers comply to these terms, they benefit by being able to write more logic with less code.

Most importantly, since general concepts like function composing, iterable mapping or filtering have codified inputs and outputs, we are oftentimes writing code that has the least amount of boilerplate with the most predictable of outcomes, making debugging simpler and less frequent.

2. Stop naming things.

“There are only two hard parts of Computer Science: cache invalidation and naming things.” — Martin Fowler

I believe that as programmers one of our guiding principles should be to reduce our cognitive stress so that we can focus on solving complex problems and writing impactful code. There are many aspects to programming that can make our daily activities stressful.

I would argue that constantly coining names for variables and functions can lead to decision fatigue. While this cannot be avoided entirely, it can be avoided in many situations.

Sometimes naming things provides developers with meaningful semantics that enrich the codebase making it more approachable and understandable. Other times, however, especially when coining names for temporary identifiers, naming things can be a nuisance.

Function composition solves the problem of overly compact one-liner code by providing developers with an elegant solution for passing outputs into inputs. When combined with other techniques like function currying and partial application, function composition can be potent enough to transform the way that you write Javascript.

Fin

I hope you found this article helpful. Keep an eye out for more articles about functional programming in Javascript. There are a few more functional programming concepts that I would like to write about before concluding the “Stop Writing for-loops” series, so stay tuned!