Functional JavaScript programming using Ramda

Original author: Andrew D'Amelio and Yuri Takhteyev
  • Transfer
We at rangle.io have long been passionate about functional programming, and have already tried Underscore and Lodash . But recently we came across the Ramda library , which at first glance looks like Underscore, but differs in a small but important area. Ramda offers about the same set of methods as Underscore, but organizes the work with them in such a way that the functional composition becomes easier.

The difference between Ramda and Underscore is in two key places - currying and composition.

Currying


Currying is the transformation of a function that expects several parameters into one that, when passing fewer parameters to it, returns a new function that waits for the remaining parameters.

R.multiply(2, 10); // возвращает 20


We passed both parameters to the function.

var multiplyByTwo = R.multiply(2);
multiplyByTwo(10); // возвращает 20


Cool. We created a new multiplyByTwo function, which is essentially 2, built into multiply (). Now you can pass any value to our multiplyByTwo. And maybe that's because in Ramda all functions support currying.

The process goes from right to left: if you skip a few arguments, Ramda assumes that you skipped the ones on the right. Therefore, functions that take an array and a function usually expect the function as the first argument and the array as the second. But in Underscore the opposite is true:

_.map([1,2,3], _.add(1)) // 2,3,4


Against:

R.map(R.add(1), [1,2,3]); // 2,3,4


Combining the “first operation, then data” approach with right-to-left currying allows us to set what we need to do and return to the function that does it. Then we can pass the necessary data to this function. Currying is easy and practical.

var addOneToAll = R.map(R.add(1));
addOneToAll([1,2,3]); // возвращает 2,3,4


Here is a more complicated example. Suppose we make a request to the server, get an array and extract the cost value from each element. Using Underscore, one could do this:

return getItems()
  .then(function(items){
    return _.pluck(items, 'cost');
});


Using Ramda you can remove unnecessary operations:

return getItems()
    .then(R.pluck('cost'));


When we call R.pluck ('cost'), it returns a function that extracts cost from each element of the array. And that is exactly what we need to pass to .then (). But for complete happiness it is necessary to combine currying with the composition.

Composition


A functional composition is an operation that takes functions f and g and returns a function h such that h (x) = f (g (x)). Ramda has a compose () function for this. Combining these two concepts, we can build the complex work of functions from smaller components.

var getCostWithTax = R.compose(
    R.multiply(1 + TAX_RATE), // подсчитаем налог
    R.prop('cost') // вытащим свойство 'cost' 
);


It turns out a function that pulls the value from the object and multiplies the result by 1.13.

The standard function “compose” performs operations from right to left. If this seems counterintuitive to you, you can use R.pipe (), which works, R.compose (), only from left to right:

var getCostWithTax = R.pipe(
    R.prop('cost'), // вытащим свойство 'cost' 
    R.multiply(1 + TAX_RATE) // подсчитаем налог
);


The functions R.compose and R.pipe can take up to 10 arguments.

Underscore, of course, also supports currying and composition, but they are rarely used there, since currying in Underscore is inconvenient to use. Ramda makes it easy to combine these two techniques.

First we fell in love with Ramda. Her style generates extensible, declarative code that is easy to test. The composition is performed naturally and leads to code that is easy to understand. But then ...

We found that things get more confusing when using asynchronous functions that return promises:

var getCostWithTaxAsync = function() {
    var getCostWithTax = R.pipe(
        R.prop('cost'), // вытащим свойство 'cost' 
        R.multiply(1 + TAX_RATE) // умножим его на 1.13
    );
    return getItem()
        .then(getCostWithTax);
}


Of course, this is better than without Ramda at all, but I would like to get something like:

var getCostWithTaxAsync = R.pipe(
    getItem, // получим элемент
    R.prop('cost'), // вытащим свойство 'cost' 
    R.multiply(1 + TAX_RATE) // умножим на 1.13
);


But this will not work, since getItem () returns a promise, and the function returned by R.prop () expects a value.

Promised Composition


We contacted the Ramda developers and proposed a version of the composition that would automatically unwrap promises, and asynchronous functions could be associated with functions that expect value. After much discussion, we agreed to implement this approach in the form of new functions: R.pCompose () and R.pPipe () - where “p” means “promise”.

And with R.pPipe we can do what we need:

var getCostWithTaxAsync = R.pPipe(
    getItem, // получим обещание
    R.prop('cost'), // вытащим свойство 'cost'
    R.multiply(1 + TAX_RATE) // умножим на 1.13
); // возвращает обещание и cost с налогом

Also popular now: