Optimize JavaScript code for composability with Ramda.js

Neeraj Singh

By Neeraj Singh

on October 6, 2017

In this blog R stands for Ramda.js. More on this later.

Here is code without R.

1function isUnique(element, selector) {
2  const parent = element.parentNode;
3  const elements = parents.querySelectorAll(selector);
4  return elements.length === 1 && elements[0] === element;
5}

Code with R.

1function isUnique(element, selector) {
2  const querySelectorAll = R.invoker(1, 'querySelectorAll')(selector);
3
4  return R.pipe(
5    R.prop('parentNode'),
6    querySelectorAll,
7    elements => R.both(
8                  R.equals(R.length(elements), 1),
9                  R.equals(elements[0], element)
10                );
11  )();
12}

Is the refactored code better ?

What is R? What's invoker? What's pipe?

The "code without R" reads fine and even a person who has just started learning JavaScript can understand it. Then why take all this extra complexity. Shouldn't we be writing code that is easier to understand ?

Good questions. Who could be against writing code that is easier to understand.

If all I'm writing is a function called isUnique then of course the "before version" is simpler. However this function is part of a bigger thousands of lines of code software.

A big software is nothing but a collection of smaller pieces of code. We compose code together to make code work.

We need to optimize for composability and as we write code that is more composable, we are finding that composable code is also easier to read.

At BigBinary we have been experimenting with composability. We previously wrote a blog on how using Recompose is making our React components more composable.

Now we are trying same techniques at pure JavaScript level using Ramda.js.

Let's take a look at another examples.

Example 2

We have a list of users with name and status.

1var users = [
2  { name: "John", status: "Active" },
3  { name: "Mike", status: "Inactive" },
4  { name: "Rachel", status: "Active" },
5];

We need to find all active users. Here is a version without R.

jsfiddle{:.code-link}

1var activeUsers = function (users) {
2  return users.filter(function (user) {
3    var status = user.status;
4    return status === "Active";
5  });
6};

Here is code with R.

jsfiddle{:.code-link}

1var isStatusActive = R.propSatisfies(R.equals("Active"), "status");
2var active = R.filter(isStatusActive);
3var result = active(users);

Now let's say that user data changes and we have a user with an empty name. We don't want to include such users. Now data looks like this.

1var users = [
2  { name: "John", status: "Active" },
3  { name: "Mike", status: "Inactive" },
4  { name: "Rachel", status: "Active" },
5  { name: "", status: "Active" },
6];

Here is modified code without R.

jsfiddle{:.code-link}

1var activeUsers = function (users) {
2  return users.filter(function (user) {
3    var status = user.status;
4    var name = user.name;
5    return (
6      name !== null &&
7      name !== undefined &&
8      name.length !== 0 &&
9      status === "Active"
10    );
11  });
12};

Here is modified code with R.

jsfiddle{:.code-link}

1var isStatusActive = R.propSatisfies(R.equals("Active"), "status");
2var active = R.filter(isStatusActive);
3var isNameEmpty = R.propSatisfies(R.isEmpty, "name");
4var rejectEmptyNames = R.reject(isNameEmpty);
5var result = R.pipe(active, rejectEmptyNames)(users);
6log(result);

Notice that change we needed to do to accommodate this request.

In the none R version, we had to get into the gut of the function and add logic. In the with R version we added new function and we just composed this new function with old function using pipe. We did not change the existing function.

Now let's say that we don't want all the users but just the first two users. We know what need to change in the without R version. In the with R version all we need to do is add R.take(2) and no existing function changes at all.

Here is the final code.

jsfiddle{:.code-link}

1var isStatusActive = R.propSatisfies(R.equals("Active"), "status");
2var active = R.filter(isStatusActive);
3var isNameEmpty = R.propSatisfies(R.isEmpty, "name");
4var rejectEmptyNames = R.reject(isNameEmpty);
5
6var result = R.pipe(active, rejectEmptyNames, R.take(2))(users);
7log(result);

Data comes at the end

Another thing to notice is that in the R version nowhere we have said that we are acting on the users. All the functions have no mention of users. In fact all the functions do not take any argument explicitly since the functions are curried. When we want result then we are passing users as the argument but it could be articles and our code will still hold.

This is pointfree programming. We do not need to know about "pointfree" since this comes naturally when write with R.

I'm still not convinced that Ramda.js is solving any real problem

No problem.

Please watch Hey Underscore, You're doing it wrong video by Brian Lonsdorf. Hopefully that will convince you to give Ramda.js a try.

If you are still not convinced then, the author of Ramda.js has written a series of blogs called Thinking in Ramda. Please read the blogs. Slowly.

Ramda brings functional concepts to JavaScript

Functional programming is another way of thinking about the code. When we move to Elm, Haskell or Elixir to get functional concepts then we are wrestling with two things at once - a new language and functional concepts.

Ramda.js brings functional concepts to JavaScript. In this way we can slowly start using functional concepts in our day to day JavaScript code.

The best part is that if you write any JavaScript code then you can start using Ramda.js today. Whether you are using React.js or Angular.js, it's all JavaScript and you can use Ramda.js.

Stay up to date with our blogs. Sign up for our newsletter.

We write about Ruby on Rails, ReactJS, React Native, remote work,open source, engineering & design.