Practical Functional Javascript with Ramda

ramda_header

At rangle.io we’ve been fans of the functional programming style for a while and have used Underscore and Lodash extensively on many projects. However, recently we started using a new library, Ramda, that on the surface seems very similar to Underscore, but which turns out to be different in a small but significant way. Ramda offers roughly the same set of methods as Underscore, but the way it offers them makes functional composition easy.

The difference between Ramda and Underscore comes down to two core concepts: currying and composition.

Currying

Currying is the process of turning a function that expects multiple parameters into one that, when supplied fewer parameters, returns a new function that awaits the remaining ones.

R.multiply(2, 10); // returns 20

Here we have to pass both parameters, 2 and 10, to call the function.

var multiplyByTwo = R.multiply(2);
multiplyByTwo(10); // returns 20

Pretty neat! We created a new function multiplyByTwo which is just the value 2 baked into multiply(). We can now pass any value to our multiplyByTwo function. The reason we can do this is because all of Ramda’s function are curried.

Currying proceeds from right-to-left: when you skip some arguments, Ramda assumes you’ve skipped the ones on the right. Because of that, Ramda functions that take an array and a function normally expect the function as the first argument and the array as the second. This is the reverse of how Underscore does it:

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

Versus:

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

The combination first-operation-then-data with right-to-left currying allows us to just specify what we want to do and get back a function that does that. We can then call that function with the actual data. Currying becomes easy and practical:

var addOneToAll = R.map(R.add(1));
addOneToAll([1,2,3]); // returns 2,3,4

We’ve now got a function addOneToAll() that we can reuse in a variety of contexts.

Here is a somewhat more complex but also more practical example. Suppose we want to make a request to the server, get an array of items, and then extract the field “cost” from each item. Using Underscore we might do this:

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

Using Ramda, we can eliminate some boilerplate:

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

This is because, when we call R.pluck('cost'), it returns a function that extracts the “cost” field from each item in the supplied array – which is precisely the function we want to pass to .then().

To get the full benefit of currying, however, we need to combine it with composition.

Composition

Mathematically speaking, functional composition is an operation that takes functions f and g, returns a function h such that: g(x) = f(g(x)). Ramda offers a compose() function to do this. Compose mixes well with currying, since we can build larger functional behaviors, from smaller functional components.

var getCostWithTax = R.compose(
    R.multiply(1 + TAX_RATE), // calculate the tax
    R.prop('cost') // pull out the 'cost' property
);

This gives us a function that gets the property “cost” out of an object and then multiplies the result by 1.13.

The standard “compose” function is right-associative. That is, operations proceed from right to left. If you find this unintuitive, you can use R.pipe(), which is the same as R.compose() but works from left to right:

var getCostWithTax = R.pipe(
    R.prop('cost'), // pull out the 'cost' property
    R.multiply(1 + TAX_RATE) // calculate the tax
);

You are not limited to composing just two functions. R.compose and R.pipe can take up to 10 arguments.

It’s worth noting that currying and composing are supported by libraries such as Underscore too. They are rarely used, however, since the data-first order of parameters makes currying Underscore’s methods impractical. Ramda makes it easy to apply currying and composition in practice.

At first, we quickly fell in love with Ramda. Ramda’s style leads to code that’s extendable, composable, testable, and declarative. Composing functions feels very natural, and results in JavaScript that’s just easier to understand.

then…

As we started using Ramda more and more we discovered that things can get a bit messier when you have asynchronous functions returning promises:

var getCostWithTaxAsync = function() {
    var getCostWithTax = R.pipe(
        R.prop('cost'), // pull out the 'cost' property
        R.multiply(1 + TAX_RATE) // multiply it by 1.13
    );

    return getItem()
        .then(getCostWithTax);
}

While this is cleaner than it would have been without Ramda, what we wished we could do was just this:

var getCostWithTaxAsync = R.pipe(
    getItem, // get the item
    R.prop('cost'), // pull out the 'cost' property
    R.multiply(1 + TAX_RATE) // multiply it by 1.13
);

The reason we couldn’t do this is because getItem() returns a promise, while the function returned by R.prop() expects to be called with the actual value.

Promise-Aware Composition

We got in touch with Ramda contributors and proposed a version of compose that would automatically unwrap promises, so that asynchronous functions could be composed together with functions that expect actual results. After a long discussion, we settled on implementing these as new functions: R.pCompose() and R.pPipe() – with “p” standing for “promise”.

With R.pPipe we could do exactly what we wanted to do:

var getCostWithTaxAsync = R.pPipe(
    getItem, // get a promise for the item
    R.prop('cost'), // pull out the 'cost' property
    R.multiply(1 + TAX_RATE) // multiply it by 1.13
); // returns a promise for the cost with tax

Since our code tends to leverage promises quite heavily, we expect R.pPipe() to do quite a lot of heavy lifting in our future code. Give it a try and let us know what you think!

Comments

  • Pingback: Dew Drop – December 17, 2014 (#1917) | Morning Dew()

  • Buzz de Cafe

    great article! One minor correction:

    “R.compose and R.pipe can take up to 10 arguments.”

    Not so: They are actually variadic, and will handle unlimited arguments. Consider:


    var a = R.add(1);
    var f = R.compose(a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a);
    var g = R.compose(a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a);
    f(1); //=> 23
    g(1); //=> 45

    Since `pipe` is simply `compose` with the arguments reversed, this holds there as well.

    Thanks for using Ramda, thanks for the promise-aware stuff, and thanks for publishing this!

    • Yuri Takhteyev

      Thanks! I think I misinterpreted the limitations on the length that you can set via arity. You can compose any number of functions but the first function to be called (and the resulting composition) one can take up to 10 arguments, right?

      • Buzz de Cafe

        there’s no limit on the number of args you pass to the input-end of the composition either. The implementation of `compose` is simply:


        function _compose(f, g) {
        return function() {
        return f.call(this, g.apply(this, arguments));
        };
        };

        The 10-args limit is from `R.nAry` and `R.arity`– although in the case of `arity` it will still pass through any extra args above what you asked for (whereas `nAry` will only pass in *at most* as many as you specify). And that limit is only there so we don’t have to use `eval` to support unlimited arities.

        And really, If you find yourself using a function of arity > 10 in this paradigm, something is almost certainly wrong …

  • mcarel

    In the “Composition” section, you wrote:

    f(x) = f(g(x))

    But I think you meant to write:

    h(x) = f(g(x))

    • Yuri Takhteyev

      Yes, you are right!

  • var function addOneToAll = R.map(R.add(1));
    That looks like a typo?

    • Yuri Takhteyev

      Yes, definitely a typo. Thanks for spotting it. We’ll try to correct this.

  • bobiblazeski

    How’s the speed compared to lodash?

    • Yuri Takhteyev

      For the tests I’ve tried with JSPerf, Ramda consistently comes ahead of Lodash on Chrome and sometimes ahead sometimes behind on Firefox. E.g., on this test it shows as faster than Lodash in both browsers:

      http://jsperf.com/ramda-vs-lodash/2

      Both are faster than native .map and .filter. Not surprisingly, a plain imperative code using a single for loop comes out faster than all other alternatives.

      • ScottSauyet

        Note that the tests are a little skewed for two reasons:

        First, on every call, the function is rebuilt. This is not how one would expect to use these code blocks.

        Second, one — just one — of the snippets has an embedded console.log statement. This unfairly slows that one down.

        A version of the test that fixes both of these is at http://jsperf.com/ramda-vs-lodash/3. Although there is nothing in these results to contradict the conclusions above, there are some differences in the results from the earlier version.

  • nateabele

    I think the perceived need for p*() functions is actually a bad smell: promises are a mechanism for facilitating communication across system or component boundaries, whereas functional composition is a mechanism for encapsulating pure computation. Having an explicit separation between the two is good design. For a more in-depth exposition on this, see Rich Hickey’s talk, “Simple Made Easy”: http://www.infoq.com/presentations/Simple-Made-Easy

  • At least in Ramda 0.13 the Promises aware compose and pipe are called composeP and pipeP (p in the end not beginning).

  • At least in Ramda 0.13 the Promises aware compose and pipe are called composeP and pipeP (p in the end not beginning).

  • Danny Korenblum

    thanks, stoked to try it.

    btw, it looks like it was renamed to “pipeP”, so you might want to edit to keep it current 🙂

    http://ramdajs.com/docs/#pipeP

  • Danny Korenblum

    thanks, stoked to try it.

    btw, it looks like it was renamed to “pipeP”, so you might want to edit to keep it current 🙂

    http://ramdajs.com/docs/#pipeP

  • Andrei Hognogi

    Wow, I can’t believe just how amazingly simple things get to be. This is what I call “elegance”

  • Pingback: 80: ‘Hardcore’ Functional Programming using Ramda with Andrew D’Amelio | PHP Podcasts()

  • Thanks for sharing this. Note, the two functions are now called pipeP and composeP – really helpful understanding the history behind them!