We at
rangle.io have been
fond of functional programming for a long time, and have already tested
Underscore and
Lodash . But we recently stumbled upon the
Ramda library, which at first glance looks like Underscore, but differs in a small but important area. Ramda offers roughly the same set of methods as Underscore, but it 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 to one that, when it receives less of them, returns a new function that waits for the remaining parameters.
R.multiply(2, 10);
')
We passed both parameters to the function.
var multiplyByTwo = R.multiply(2); multiplyByTwo(10);
Cool. We have created a new function multiplyByTwo, which is essentially 2, embedded in multiply (). Now you can pass any value to our multiplyByTwo. And perhaps this is because in the Ramda all functions support currying.
The process goes from right to left: if you miss a few arguments, Ramda assumes that you missed the ones on the right. Therefore, functions that take an array and a function usually expect a function as the first argument and an array as the second. And in Underscore, the opposite is true:
_.map([1,2,3], _.add(1))
Vs:
R.map(R.add(1), [1,2,3]);
Combining the “first operation, then the data” approach with the right-to-left currying allows us to specify what we need to do and return to the function that does it. Then we can transfer the necessary data to this function. Currying becomes simple and practical.
var addOneToAll = R.map(R.add(1)); addOneToAll([1,2,3]);
Here is an example more complicated. Suppose we make a request to the server, get an array and extract the value of the cost (cost) 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. Namely, this is what we need to transfer to .then (). But for complete happiness, it is necessary to combine the 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),
It turns out a function that pulls the cost out of the object and multiplies the result by 1.13
The standard “compose” function performs right-to-left operations. 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'),
The R.compose and R.pipe functions can take up to 10 arguments.
Underscore, of course, also supports currying and composition, but they are rarely used there, because currying in Underscore is inconvenient to use. Ramda makes it easy to combine these two techniques.
At first we fell in love with Ramda. Its style generates extensible, declarative code that is easy to test. The composition is performed in a natural way and leads to a code that is easy to understand. But then ...
We found that things become more confusing when using asynchronous functions that return promises:
var getCostWithTaxAsync = function() { var getCostWithTax = R.pipe( R.prop('cost'),
Of course, this is better than without Ramda, but I would like to get something like:
var getCostWithTaxAsync = R.pipe( getItem,
But this will not work, because getItem () returns a promise, and the function returned by R.prop () expects a value.
Promise composition
We contacted the developers of Ramda and offered 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,