Hands-on Functional Programming with Ramda.js

Share this article

Hands-on Functional Programming with Ramda.js
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!

Frequently Asked Questions (FAQs) about Functional Programming with Ramda

What is the main advantage of using Ramda in functional programming?

Ramda is a practical functional library for JavaScript programmers. It is designed specifically for a functional programming style, which makes it easier to create functional pipelines, and never mutates user data. The main advantage of using Ramda is its emphasis on immutability and side-effect free functions. This means that functions do not alter their input data, making your code more predictable and easier to test. Ramda’s functions are automatically curried, which allows you to easily build up new functions from old ones without getting lost in a tangle of parentheses or callback hell.

How does Ramda differ from other JavaScript libraries?

Unlike other JavaScript libraries, Ramda is designed to support functional programming and does not mutate data. It offers a set of utility functions that are curried by default, which means they are designed to be used together to create new functions. This is a significant departure from libraries like Underscore or Lodash, which are not curried by default and often require you to write additional code to achieve the same results. Ramda’s API is also more consistent and easier to use, with a focus on simplicity and readability.

Can I use Ramda with other JavaScript libraries or frameworks?

Yes, Ramda can be used with other JavaScript libraries and frameworks. It is a standalone library that does not depend on any other libraries or frameworks, and it does not modify the JavaScript object prototype. This makes it safe to use alongside other libraries or frameworks without worrying about conflicts or unexpected side effects. Whether you’re using jQuery, React, Angular, Vue, or any other library or framework, you can use Ramda to help write cleaner, more functional code.

Is Ramda suitable for beginners in functional programming?

Ramda is a great tool for those new to functional programming. Its API is designed to be simple and intuitive, with clear and consistent naming conventions. The documentation is also very thorough, with plenty of examples to help you get started. However, like any new tool or paradigm, there is a learning curve. It may take some time to get used to thinking in a functional way and to understand how to use Ramda’s functions effectively. But with practice, you’ll find that it can greatly simplify your code and make it easier to reason about.

How does Ramda handle null and undefined values?

Ramda treats null and undefined values as empty, similar to how other functional programming languages handle them. This means that you can safely pass null or undefined to Ramda’s functions without causing errors or exceptions. However, it’s always a good idea to check for null or undefined values before passing them to a function, to avoid unexpected behavior.

Can I use Ramda in a Node.js environment?

Yes, Ramda can be used in a Node.js environment. It is a universal library that works in both the browser and Node.js. You can install it via npm and require it in your Node.js modules just like any other package.

How does Ramda handle asynchronous operations?

Ramda does not have built-in support for asynchronous operations, as it is primarily a synchronous library. However, you can use it in conjunction with other libraries that support asynchronous operations, such as Promises or async/await. You can also use Ramda’s functions inside async functions or then callbacks.

How can I contribute to the Ramda project?

Ramda is an open-source project, and contributions are always welcome. You can contribute by reporting bugs, suggesting new features, improving the documentation, or submitting pull requests. Before contributing, it’s a good idea to read the contributing guidelines on the Ramda GitHub page.

Is Ramda still maintained and updated?

Yes, Ramda is actively maintained and regularly updated. The maintainers are committed to keeping the library up to date and addressing any issues or bugs that arise. You can check the GitHub page for the latest updates and releases.

Can I use Ramda for commercial projects?

Yes, Ramda is licensed under the MIT License, which means you can use it for commercial projects. However, it’s always a good idea to read the license agreement in full to understand your rights and responsibilities.

Florian RapplFlorian Rappl
View Author

Florian Rappl is an independent IT consultant working in the areas of client / server programming, High Performance Computing and web development. He is an expert in C/C++, C# and JavaScript. Florian regularly gives talks at conferences or user groups. You can find his blog at florian-rappl.de.

functional programmingnilsonj
Share this article
Read Next
Get the freshest news and resources for developers, designers and digital creators in your inbox each week