NPM Packages You Should Be Using: ramda

Welcome to the latest instalment of a series which brings to your attention NPM modules you may not be aware of, but which are very useful.

Today we’ll be looking at ramda.

If you’ve come to JavaScript from another mainstream language, you’ll probably feel that it’s missing a lot of fairly basic functionality out of the box. The API for many of the built in data types (such as strings, objects and arrays) often seems either lacking or clunky. You wouldn’t be alone in thinking this. Ramda seeks to fill in the gaps left by ‘out of the box’ JavaScript. It is a library which makes common tasks in JavaScript easier, and it’s API is written in such a way as to make it functional programming friendly.

Here are some examples of how we would solve some problems using vanilla JavaScript vs how we would solve them using ramda.

Checking If An Item Exists In An Array

// Vanilla
var x = 3;
var list = [1,2,4]
var isXInList = (list.indexOf(x) !== -1);

// Ramda
var x = 3;
var list = [1, 2, 4];
var isXInList = R.contains(x, list);

Get The Last Element In An Array

// Vanilla
var a = [1, 2, 3];
var x = a[a.length - 1];

// Ramda
import R from 'ramda';
var a = [1, 2, 3];
var x = last(a);

Omitting Properties From Objects

// Vanilla
var x = {
    firstName: 'David',
    lastName: 'Smith',
    city: 'London',
};
var y = { ...x };
delete y.city;

// Ramda
var x = {
    firstName: 'David',
    lastName: 'Smith',
    city: 'London',
};
var y = omit(['city'], x);

Getting An Array Of Unique Values

// Vanilla
var a = [1, 3, 3, 9];
var u = a.filter((x, i) => a.indexOf(x) === i);

// Ramda
var a = [1, 3, 3, 9];
var u = uniq(a);

There are other similar libraries out there, lodash being a prominent example. In addition to this ramda also replicates some existing functionality which is built into JavaScript (e.g. map, filter). So why use ramda?

Ramda is a big win if you wish to program in a functional style. This is because ramda has been built from the ground up to be used in a functional manner. Ramda supports a functional style in the following ways:

  • Everything is a stand alone function, rather than using objects with member functions
  • All functions are curried
  • The argument order for the functions always makes sense for currying (the data that is being operated on is always the last argument)

Curried By Default

Just pretend I made a joke about spicy food and then move on.

All ramda functions are curried by default. A curried function is a function which is ready to be partially applied. This means you can call it with less than it’s full number of arguments and it will return a function which takes the remaining arguments. Only once all the arguments have been passed will the function actually return the end result. Let’s look at an example.

import R from 'ramda';
var userNames = [
  { id: 10, name: 'Dave' },
  { id: 20, name: 'Steve' },
  { id: 98, name: 'James' },
];

var userLocations = [
  { id: 10, location: 'London' },
  { id: 20, location: 'Paris' },
  { id: 98, location: 'New York' },
];

var currentUserId = 20;
var findCurrentUser =
  R.find((item) => item.id === currentUserId);

// Will log { id:20, name: 'Steve' }
console.log(findCurrentUser(userNames));

// Will log { id:20, location: 'paris' }
console.log(findCurrentUser(userLocations));

We have multiple lists of user information, and we also have the id of our current user. We want a function which will always find the current user’s entry in any user based list.

This is where the magic happens.

var findCurrentUser = R.find(
  (item) => item.id === currentUserId
);

R.find is a function which takes two arguments. The first is a function, which is acts as a predicate. The second is an array. R.find will return the first element in the array which satisfies the predicate (or undefined if there a none which do). You’ll note, however, that we only pass find one, rather than two arguments. We just pass the predicate, not the array. Since we have not passed all arguments to the function, the return value will not be the final result, but instead another function. This is because find, like all ramda functions, is curried. We place the resulting function into the findCurrentUser variable. findCurrentUser is now a function which takes a single argument, an array, and will return the element from the array which matches the current userId we specified. This is handy, as it has allowed us to use a general purpose function, R.find, in order to create a very specific function which we can reuse.

There are many practical ways this can be used. Another example would be to create functions for type checking. The ramda function R.is will return true iff the second argument is of the type stated in the first argument. So we can create our type checking functions as follows:

import R from 'ramda';

var isString = R.is(String);
var isObject = R.is(Object);
var isNumber = R.is(Number);

isString('foo');// True

// Without currying
R.is(String, 'foo');// True

Notice how in the examples shown, the arguments for the ramda functions are in an order that make them very useful when curried. As an example imagine R.find was called R.find(array, predicate) instead of R.find(predicate, array). Done the former way we would only ever be able to get a function that searched one array returned by passing the first argument, as opposed to a function that searches any array for something that matches a predefined predicate, which is what the latter does when passed just the predicate. I’m sure you can see that ramda puts the arguments in the best order. This is no happy accident, whoever created ramda designed it this way for just this reason.

I’m sure by now you can see how ramda is a nifty library. It really is one to add to your toolbox, it can save you a lot of time. I was going to cover more of it’s functionality, but decided that would turn this more into a functional programming in JavaScript post. That’s a whole series in itself, and I’m lazy, so I’ll save that for another day.

I really do urge you to check out ramda’s list of functions. I’ll whet your appetite by sharing a list of some of my personal favourites

  • mapObjIndexed: Like map, but for objects instead of arrays.
  • head: Get first element in an array
  • tail: Get last element in an array
  • test: Returns true iff a string matches a regex
  • curry: Curries a function
  • flip: Takes a function and returns the same function except with the arguments in reverse order. Useful if you want to curry a function who’s arguments aren’t in a useful order like ramda’s are.
  • compose: creates a function pipeline ie the result of the previous function is used as the argument to the next in the pipeline. You pass in values and they are passed down the pipeline till you get the result. Useful for creating a big function out of multiple smaller ones.

One comment

Leave a Reply

Your email address will not be published. Required fields are marked *