October 6, 2017
In this blog R
stands for Ramda.js. More on this later.
Here is code without R.
function isUnique(element, selector) {
const parent = element.parentNode;
const elements = parents.querySelectorAll(selector);
return elements.length === 1 && elements[0] === element;
}
Code with R.
function isUnique(element, selector) {
const querySelectorAll = R.invoker(1, 'querySelectorAll')(selector);
return R.pipe(
R.prop('parentNode'),
querySelectorAll,
elements => R.both(
R.equals(R.length(elements), 1),
R.equals(elements[0], element)
);
)();
}
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.
We have a list of users with name and status.
var users = [
{ name: "John", status: "Active" },
{ name: "Mike", status: "Inactive" },
{ name: "Rachel", status: "Active" },
];
We need to find all active users. Here is a version without R.
jsfiddle{:.code-link}
var activeUsers = function (users) {
return users.filter(function (user) {
var status = user.status;
return status === "Active";
});
};
Here is code with R.
jsfiddle{:.code-link}
var isStatusActive = R.propSatisfies(R.equals("Active"), "status");
var active = R.filter(isStatusActive);
var 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.
var users = [
{ name: "John", status: "Active" },
{ name: "Mike", status: "Inactive" },
{ name: "Rachel", status: "Active" },
{ name: "", status: "Active" },
];
Here is modified code without R.
jsfiddle{:.code-link}
var activeUsers = function (users) {
return users.filter(function (user) {
var status = user.status;
var name = user.name;
return (
name !== null &&
name !== undefined &&
name.length !== 0 &&
status === "Active"
);
});
};
Here is modified code with R.
jsfiddle{:.code-link}
var isStatusActive = R.propSatisfies(R.equals("Active"), "status");
var active = R.filter(isStatusActive);
var isNameEmpty = R.propSatisfies(R.isEmpty, "name");
var rejectEmptyNames = R.reject(isNameEmpty);
var result = R.pipe(active, rejectEmptyNames)(users);
log(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}
var isStatusActive = R.propSatisfies(R.equals("Active"), "status");
var active = R.filter(isStatusActive);
var isNameEmpty = R.propSatisfies(R.isEmpty, "name");
var rejectEmptyNames = R.reject(isNameEmpty);
var result = R.pipe(active, rejectEmptyNames, R.take(2))(users);
log(result);
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.
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.
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.
If this blog was helpful, check out our full blog archive.