JavaScript
Article

Hands-on Functional Programming with Ramda.js

By Florian Rappl

This article was peer reviewed by Yaphi Berhanu, Vildan Softic, Jani Hartikainen and Dan Prince. Thanks to all of SitePoint’s peer reviewers for making SitePoint content the best it can be!

For me, one of the things that makes JavaScript so interesting is the functional aspect of the language. From the beginning, functions have been first-class citizens in the JavaScript world. This makes it possible to write elegant and expressive code that can be easily composed together in multiple ways.

However, just by having the ability to do some functional programming does not auto-magically result in functional programming. Ramda.js is a quite popular library (having over 4k stars on GitHub) that we can use to help us getting started with functional programming using JavaScript.

Getting Started

To fully use Ramda.js we should get used to its benefits by creating a small Node.js project. We can simply install it via the Node Package Manager (npm).

npm install ramda

Usually, we will simply import the library’s functionality into the namespace R. This way all calls to Ramda’s methods will have an R. prefix.

var R = require('ramda');

Of course nothing prevents us from using Ramda.js in front-end code. In the browser, we only need to include a proper path to a copy of the library. This may be as simple as the following HTML snippet.

<script src="ramda.min.js"></script>

Ramda.js does not use any DOM or Node.js specific features. It is only a language library / extension, and builds upon structures and algorithms already exposed by the JavaScript runtime (as standardized in ECMAScript 5).

Ready to dive in? Let’s see some of the abilities in action!

Concepts

The most important concept in functional programming is that of pure functions. A pure function is idempotent and won’t change any state. Mathematically this makes sense as functions such as sin(x) seem to be quite natural and do not rely on any external state.

Besides having pure functions, we would also like to have a single argument functions. They are the most primitive. Zero argument functions usually indicate that an external state would be changed, thus not being pure. But in a language like JavaScript we will usually have functions taking more than a single argument.

Currying

The ability to have higher-order functions (i.e., functions that can take functions as input and emit a function as output) combined with closures (capturing local variables) gives us a nice way out: currying. Currying is a process where a function with multiple (let’s say n) arguments is transformed to a function with a single argument returning another function with a single argument. This goes on until all required arguments are collected.

Let’s say we want to use the Ramda.js helper is to write a one-argument wrapper which tests whether its argument is a string. The following code will do the job.

function isString (test) {
    return R.is(String, test);
}

var result = isString('foo'); //=> true

The same thing can be done much easier with currying. Since R.is is part of Ramda.js, the library will automatically return a curried function if we supply less arguments that the function takes:

var isString = R.is(String);
var result = isString('foo'); //=> true

This is much more expressive. Since we used R.is with a single argument we received a function. On the second call (remember, the original function call requires two arguments) we obtain the result.

But what if we did not start with a helper from Ramda.js in the first place? Let’s pretend we already have the following function defined somewhere in our code:

var quadratic = (a, b, c, x) => x * x * a + x * b + c;
quadratic(1, 0, 0, 2); //=> 4
quadratic(1, 0, 0)(2); //=> TypeError: quadratic(..) is not a function

This is the full 2nd order polynomial. It has four parameters do allow all possible values. But usually, we will only want to change the x for a fixed set of parameters a, b, and c. Let’s see how we can transform this with Ramda.js:

var quadratic = R.curry((a, b, c, x) => x * x * a + x * b + c);
quadratic(1, 0, 0, 2); //=> 4
quadratic(1, 0, 0)(2); //=> 4

Again, we are able to simple use the argument evaluation to alias specific subsets. For instance the equation x - 1 may be obtained by:

var xOffset = quadratic(0, 1, -1);
xOffset(0); //=> -1
xOffset(1); //=> 0

In cases where the number of arguments is not given by the parameters of our function we need to use curryN and specify the number of arguments explicitly.

Currying is at the heart of Ramda.js, but without anything more the library would seem less interesting. Another concept that is important in functional programming is immutability.

Immutable structures

The easiest way to prevent functions from changing state is to work only with data structures that cannot be changed. For simple objects we then require read-only accessors, such that

var position = {
    x: 5,
    y: 9
};
position.x = 10; // works!

would not be allowed. Besides declaring the properties read-only we may also turn them to getter functions:

var position = (function (x, y) {
    return {
        getX: () => { return x; },
        getY: () => { return y; }
    };
})(5, 9);
position.getX() = 10; // does not work!

Now this is already a little bit better, however, the object can still be changed. This means someone could just add a custom definition of the getX function, e.g.:

position.getX = function () {
  return 10;
};

The best way to achieve immutability is to use Object.freeze. Together with the const keyword we can introduce an immutable variable that cannot be changed.

const position = Object.freeze({ x: 5, y: 9 });

Another example would involve lists. Adding an element to an immutable list then requires you to make a copy of the original list with the new element added to the end. Of course, we can also use the knowledge of immutability on the original object to optimize the implementation. This way we can replace the copy with a simple reference. Essentially, this may then become a kind-of linked list. We should be aware that the standard JavaScript array is mutable and therefore needs to be copied to ensure correctness.

Methods such as append() work on JavaScript arrays and return such arrays. The operation is idempotent; if we call the function multiple times with the same arguments we will always get the same results.

R.append('tests', ['write', 'more']); //=> ['write', 'more', 'tests']
R.append('tests', ['write', 'more']); //=> ['write', 'more', 'tests']
R.append('tests', ['write', 'more']); //=> ['write', 'more', 'tests']

There is also a remove method that returns the given array without the specified entries. It works as follows:

R.remove('write', 'tests', ['write', 'more', 'tests']); //=> ['more']

As this has a flexible amount of arguments we need the previously mentioned curryN function to apply currying. There are also a set of useful general helpers available.

Utility Methods

The most important concept for all helper functions is that arguments are ordered to facilitate currying. The more frequently an argument is supposed to be changed the less likely it is to be positioned before some other argument.

sum() and range()

The usual suspects such as sum and range can of course be found in Ramda.js:

R.sum(R.range(1, 5)); //=> 10

For the range() helper we can therefore create a wrapper using currying:

var from10ToExclusive = R.range(10);
from10ToExclusive(15); //=> [10, 11, 12, 13, 14]

What if we want to wrap this with a fixed (exclusive) max. value? Ramda.js has us covered by using a special parameter denoted by R.__:

var to14FromInclusive = R.range(R.__, 15);
to14FromInclusive(10); //=> [10, 11, 12, 13, 14]

map()

Furthermore, Ramda.js tries to provide alternatives to JavaScript’s core functions, such as Array.prototype.map, with “better” solutions. These alternatives come with a different argument order and out-of-the-box currying.

For the map function this looks as follows:

R.map(x => 2 * x, [1, 2, 3]); //=> [2, 4, 6]

prop()

Another useful utility is the prop function, which tries to obtain the value of a specified property. If the given property does not exist, undefined is returned. This may be ambiguous if the value is really undefined, but in practice we will rarely care.

R.prop('x', { x: 100 }); //=> 100
R.prop('x', { y: 50 }); //=> undefined

zipWith()

If the previously introduced methods did not convince you that Ramda.js might offer something useful, then these next ones may be more interesting. This time we won’t go over a specific example, but rather look at arbitrarily chosen scenarios.

Let’s say we have two lists and we want to join them. This is actually quite simple using the zip function. However, the usual outcome (an array of elements, which are two-valued arrays themselves) may not be the desired one. This is where the zipWith function comes into play. It uses an arbitrary function to map the values to a single value.

var letters = ["A", "B", "C", "D", "E"];
var numbers = [1, 2, 3];
var zipper = R.zipWith((x, y) => x + y);
zipper(letters, numbers); // ["A1", "B2", "C3"]

Similarly, we could introduce a dot-product for vectors:

var dot = R.pipe(R.zipWith((x, y) => x * y), R.sum);
dot([1, 2, 3], [1, 2, 3]) // 14

We zip the two arrays via multiplication (yielding [1, 4, 9]) and pipe the result to the sum function.

Working with enumerables is the big theme anyway. It should be no surprise that Ramda.js brings a lot of useful helpers to the table. We already introduced R.map to apply a function to each element. Similarly, there are helpers to reduce the number of elements. Either via the most general filter function (yielding another array) or to a single value via the reduce function.

chain()

Operating on arrays comes with a handful useful auxiliary functions. For instance, using chain we can merge arrays easily. Let’s say we have a function primeFactorization using a number as input and giving an array with prime factors as output, we can combine the results of applying the function with a set of numbers as follows:

R.chain(primeFactorization, [4, 7, 21]); //=> [2, 2, 7, 3, 7]

A practical example

So far so good. Now the big question is: What benefits do we have in our daily work by using these concepts introduced by Ramda.js? Let’s pretend we have the following (already quite good looking) code snippet.

fetchFromServer()
  .then(JSON.parse)
  .then(function (data){ return data.posts })
  .then(function (posts){ 
    return posts.map(function (post){ return post.title }) 
  });

How can Ramda.js be used to make this even more readable? Well, the first line is as good as it can be. The second one is already cluttered. What we really want is to extract just the posts property of the supplied argument.

Finally, we have a kind of messy third line. Here we try to iterate over all posts (supplied by the argument). Again, the only purpose it to extract a specific property. How about the following solution:

fetchFromServer()
  .then(JSON.parse)
  .then(R.prop('posts'))
  .then(R.map(R.prop('title')));

This may be close to the optimal solution regarding readability, thanks to the functional programming empowered by Ramda.js. We should note, however, that the ‘fat arrow’ syntax introduced in ECMAScript 6 also leads to very terse, readable code:

fetchFromServer()
  .then(JSON.parse)
  .then(json => json.posts)
  .then(posts => posts.map(p => p.title));

This is nearly as readable, without requiring any knowledge of Ramda.js. Furthermore, we reduced the number of abstractions – which can only be beneficial for performance and maintainability.

Lenses

Finally, we should also talk about useful object helpers. Here the lens function is worth mentioning.

A lens is a special object that can be passed, along with an object or array, to certain Ramda.js functions. It allows those functions to retrieve or transform data from a specific property or index of the object or array respectively.

Let’s say we have an object with two keys x and y – just like the immutability example given at the beginning of the article. Instead of wrapping the object in another object with getter and setter methods, we can create a lenses to ‘focus’ on the properties of interest.

To create a lens that accesses the property x of an object, we can do the following:

var x = R.lens(R.prop('x'), R.assoc('x'));

While prop is a standard getter (this has been introduced already), assoc is a setter function (three value syntax: key, value, object).

Now we can use functions from Ramda.js to access the properties defined by this lens.

var xCoordinate = R.view(x, position);
var newPosition = R.set(x, 7, position);

Note that the operation leaves the given position object untouched (independent of whether we froze it or not).

It should be noted that set is only a specialization of over, which is similar but takes a function instead of an arbitrary value. The function would then be used to transform the value. For example, the following call will multiply the x coordinate by 3:

var newPosition = R.over(x, R.multiply(3), position);

Ramda.js, lodash, or Something Else?

One legitimate question would certainly be why to choose Ramda.js – why shouldn’t we use lodash, or anything else instead? Of course, one could argue that Ramda.js is newer and therefore must be better, but nothing could be further from the truth. The truth is that Ramda.js was built with functional principles in mind – going new ways (for a JavaScript library) regarding argument placement and selection.

For instance, the list iterators in Ramda.js only pass the item by default, not the list. On the other hand, the standard for other libraries (like lodash) is to pass the item and index to the callback function. This may seem like a subtle issue, but it prevents you from using handy built-in functions like parseInt() (which takes an optional second argument), whereas with Ramda.js this works nicely.

In the end, the decision of what to choose may be driven by specific requirements or the team’s experience and/or knowledge, but there are certainly some good arguments for giving Ramda.js the attention it deserves.

Further Reading

Conclusion

Functional programming should not be seen as a magic bullet. Instead, it should be regarded as a natural addition to our existing toolbox that gives us higher composability, more flexibility, and greater fault-tolerance / robustness. Modern JavaScript libraries already try to embrace some functional concepts to use these advantages. Ramda.js is a powerful tool to expand your own repertoire with functional utilities.

What is your opinion on functional programming? Where do you see it shine? Let me know in the comments!

  • Marcus Buffett

    I don’t think idempotent means what you think it means. Append, for example, is not an idempotent function. An idempotent function is one that, when applied multiple times, does not change the result. For example, `abs` is an idempotent function because abs(abs(x)) is always equal to abs(x), as is abs(abs(abs(x))). Sin, however, is not an idempotent function because sin(sin(x)) != sin(x).

  • Constantin Dumitrescu

    Here’s an webpack loader that makes Ramda function available in your code without the “R.” namespace or having to define every single function in the header of a file:
    https://www.npmjs.com/package/ramda-loader

  • ferakpeter

    This is a great article. I use similar code examples to introduce people to functional concepts, and I will have to add some of these to mine! Thank you.

    • ferakpeter

      One question: does the “.then()” function come from Ramda?

Recommended

Learn Coding Online
Learn Web Development

Start learning web development and design for free with SitePoint Premium!

Get the latest in JavaScript, once a week, for free.