Functional Programming in JS, part I - Composition (Currying, Lodash and Ramda)

by Mateusz Podlasin

Oct 23 2020 - 16 min. read

This article was originally published on Mateusz's dev.to profile.

Mateusz says about himself: "I write in-depth articles about JavaScript, React and functional programming."

In this series of articles, we will go through a soft introduction to functional programming in JavaScript.

Each article will be devoted to a different aspect of functional programming. After the theoretical introduction, we will see how those concepts are then used in actual, real world JavaScript libraries.

This mix of theory and practice will ensure that you get a deep understanding of all the concepts while being able to use them effortlessly in practice in your day to day work.

Please be aware that this series assumes that you already have some proficiency in writing code with arrays' methods such as map, filter, and reduce. If they still confuse you, let me know, and I will write an article explaining them in-depth.

Ready? Let's get started!

Composition

If I had to name in one word what this first article will focus on, it would be composition or composability.

More specifically, I mean here the art of composing your code from small, reusable functions, almost like composing a lego set from smaller pieces.

It turns out that a properly written functional code is very composable. What does it mean? It means that it is extremely easy to take a small piece of that code and reuse it in a completely different situation.

Take a look at this code, written in traditional style:

let result = [];

for (let i = 0, i < data.length, i++) {
    const num = parseInt(data[i], 10);

    if (num < 5) {
        result.push(num);
    }
}

and now compare it to:

const stringToInt = str => parseInt(str, 10);
const lessThan = compareTo => num => num < compareTo;

const result = data
    .map(stringToInt)
    .filter(lessThan(5));

Those two snippets do the same thing. We first take the data array, which is filled with some strings. We then transform those strings into integers. And finally, we store only those integers that are strictly smaller than 5 in a new array. We keep that array under the result variable.

So if we got an ["1", "6", "3"] array, we would return [1, 3] as a result.

Depending on which style you are more accustomed to, you will find one of the two above snippets more readable. I believe that the second one is more readable because - not taking into the account little helper functions that we defined - it reads almost like English:

Take data, mapEach, stringToInt and then filter only those values that are lessThan(5).

However, if you are not used to functional style, this second snippet will seem awkward and needlessly convoluted. Are there any objective benefits of writing the code in that style?

Of course! And that benefit is exactly the composability. Note that we went out of our way to define even the simplest pieces of our code as functions. Thanks to that, we can now use those snippets in entirely new situations without ever writing the same code twice.

Of course, those reusable stringToInt and lessThan functions are extremely simple, to the point where it arguably is not worth reusing them like that. But keep in mind that this example only serves as a motivation for the whole approach.

In more complex applications, those functions would be getting more and more complicated. The approach of reusing the most amount of code possible and composing new code from previously written functions will have much more apparent benefits in a bigger codebase.

Note also that apart from the simplest possible reusability - simply using stringToInt and lessThan functions in different contexts - we also see examples of using higher-order array functions - map and filter. It is key to note that they possess an immense power - they allow you to use functions defined for singular values (for example, strings) on whole arrays of those values (for instance, on arrays of strings).

This is the first moment when you can see the power of that approach. You wrote two functions - stringToInt and lessThan - that are not supposed to be used on arrays. And yet, by wrapping them in only a few more characters - .map(stringToInt), .filter(lessThan(5)) - you suddenly possess the power to use those functions on whole arrays of values.

This is precisely what we meant at the beginning. The functional approach allows you to use the same code in entirely different contexts - in fact, here, the same code is even used on completely different types of values! A function that was meant to work only on strings can now work on arrays of strings! That's pretty cool.

Currying

Perhaps you have already asked yourself - "wait, what is this weird definition of lessThan about?".

If I asked you to write a lessThan function, you would probably do it like that:

const lessThan = (num, compareTo) => num < compareTo;

And yet we did it like that:

const lessThan = compareTo => num => num < compareTo;

Not only arguments are switched, but also the syntax of a function definition is different. Is this some new, exotic addition to the JavaScript standard?

In fact, no. What we did here is that we wrote a function that returns another function.

Function that we are returning is:

num => num < compareTo;

And then we wrap it in another function, that finally provides compareTo variable for it:

compareTo => (num => num < compareTo);

This time we wrapped the returned function in parentheses for better readability.

Note that we used here the fact that in an arrow function, we can provide returned value directly, instead of the function body. If we wanted to write the body, we might rewrite the above example like so:

compareTo => {
    return num => num < compareTo;
};

In fact, this pattern doesn't really rely on ES6 arrow function syntax. Me might have as well written it in old school function syntax:

function(compareTo) {
    return function(num) {
        return num < compareTo;
    };
}

What ES6 arrow syntax does, however, is that it makes that monstrous code look much nicer:

compareTo => num => num < compareTo;

That pattern is called currying.

If you take a function taking some number of parameters:

const someFunction = (a, b, c) => {
    // some code here
};

you can "curry" it (or produce its "curried" version), which looks like that:

const someFunction = a => b => c => {
    // some code here
};

In this case, the original function accepts three parameters.

After currying it, we get a function that accepts one parameter a, returns a function that takes one parameter b, then returns a function that accepts one parameter c and finally executes the original function's body.

Ok, we explained how that mechanism works, but we didn't explain why we even decided to write our functions like that.

Frankly, the answer is extremely simple. The only reason is so that we could later use the lessThan function like so:

.filter(lessThan(5))

Note that if we used our first definition of that function:

const lessThan = (num, compareTo) => num < compareTo;

then applying it in the filter method wouldn't be nearly as nice. We would have to write that code like so:

.filter(num => lessThan(num, 5))

So again, you see that we wrote our function in a way that makes it compose nicely with methods such as filter.

It also composes nicely with a map. Writing code like this:

numbers.map(lessThan(5))

would return an array of booleans saying if the number on a given place in the array is smaller than 5. For example, running that code on an array [5, 1, 4], would return an array [false, true, true].

So you can see that lessThan function composes now much nicer with other, higher-order functions.

On top of that, assume we noticed that we use lessThan very often with a number 5 specifically. Maybe that's a very important number, let's say a number of the servers we have in the company.

This number now appears in several places in our code. But having it hard-coded like that is a very bad practice. What if that number changes at some point, for example, to a 6? We would have to search for all those appearances of 5 and change them to 6 manually. This would be both too cumbersome and error-prone.

The first solution that comes to mind is to store that number in a variable, a constant with some semantic name that describes what this number means:

const NUMBER_OF_SERVERS = 5;

Now we can use the constant instead of the number:

.filter(lessThan(NUMBER_OF_SERVERS))

If that number changes (for example, our company buys more servers), we can update it in one place, where that constant is defined.

This is certainly nicer and very readable, but it's still a tiny bit cumbersome to import two distinct values (lessThan and NUMBER_OF_SERVERS) even though we always want to use them together.

However, the way we defined the lessThan function allows us to fix that. We can store the returned function in another variable!

const lessThanNumberOfServers = lessThan(NUMBER_OF_SERVERS);

Now, whenever we want to use that function with that specific value, we can import it once and use it directly:

.filter(lessThanNumberOfServers)

Our function is more composable with other functions, but it also allows us to define new functions in a very easy manner.

Very often certain values in our functions are only some kind of configuration. Those values do not change very often. In fact, you will often find yourself hard-coding those values inside your functions:

const someFunction = (...someArguments) => {
   const SOME_VALUE_THAT_WILL_PROBABLY_NOT_CHANGE = 5;

   // some code here
};

It's sometimes a good idea to put such value as an argument of a curried function and simply create a new function, with this value already set to a value we expect to be the most common:

const someBiggerFunction = (someValueThatWillProbablyNotChange) => (...someArguments) => {
    // some code here
}

const someFunction = someBiggerFunction(5);

This pattern is handy because it ultimately gives you the same result - a function with a value hard-coded inside. But at the same time, you get much bigger flexibility. When it turns out it is necessary to set that variable to some other value, you can do it easily, without any refactoring, by merely running someBiggerFunction with another argument.

So, as we have seen, using curried versions of functions gives us bigger composability, allowing for easier use of those functions in other compositions and composing brand new functions with ease.

Lodash and Ramda

I hope that it is clear by now that you don't need any external libraries to code in such functional style.

Everything you need is already baked into the JavaScript itself (most notably an arrow function syntax).

If, however, you decide to write your code in that style, perhaps it is not a bad idea to use one of the popular functional programming utility libraries.

After all, one of the benefits of writing composable code was supposed to be reusability. This means it would be kind of pointless to write from scratch code that was already written and carefully tested by someone else.

Also, as we have seen, writing JavaScript in functional style promotes making your functions as general as possible. Again, it would be innefective to write a completely new function to solve a particular problem if you can compose that function from two or three existing functions.

So let's take a look at Lodash and Ramda and see what do they have to offer for programmers coding in a functional style.

In the case of Lodash, we will be talking particularity about lodash/fp package, which is a version of the library more geared for functional programming.

On the other hand, Ramda supports functional style out of the box.

Curried APIs

We have spent so much time describing currying because it is a powerful tool in programming with functions. So powerful that it was built-in both into Ramda and Lodash libraries.

Take a look at Ramdas splitWhen function, which allows you to split an array, using a function that, by returning true for a chosen parameter, will decide where the split will happen.

For example, given an array of numbers, we might want to split it at the first occurrence of number 5. So we first construct a function that detects the number 5, given an arbitrary element from the array.

Sounds complicated? It's not:

x => x === 5

Now we can use that function in Ramdas splitWhen function. When we run this code:

import { splitWhen } from 'ramda';

splitWhen(x => x === 5, [1, 2, 5, 6]);

the result will be an array consisting of two arrays:

[[1, 2], [5, 6]]

So we see that the original array was split at 5, as we wanted.

Note that we executed the splitWhen function in a traditional manner, passing two arguments and getting some results.

But it turns out that functions from Ramda can also behave like curried functions. This means that we can create a new function, like so:

const splitAtFive = splitWhen(x => x === 5);

Note that this time we did not pass both arguments to splitWhen at once. We created a new function that waits for an array to be provided. Running splitAtFive([1, 2, 5, 6]) will return exactly the same result as before: [[1, 2], [5, 6]].

So we see that Ramda supports currying out of the box! That's really great for people who love to code in a functional style.

And while we are at it, we can mention that Ramda has an equals method, that is basically a wrapper for an === operator.

This might seem pointless (after all equals(2, 3) is a bit less readable than 2 === 3) but because all Ramda functions support currying, and equals is no exception, we can refactor our splitAtFive function like so:

const splitAtFive = splitWhen(equals(5));

This reads basically like English! That's the beauty of functional programming.

That last example works because splitWhen can accept only a one-argument function. equals requires two arguments, but thanks to currying, we can provide one argument earlier, while the second will be provided by the splitWhen itself.

This is exactly the same trick as our previously created lessThan function.

Curry your functions

We mentioned that it is incredibly easy to write curried functions in modern JavaScript with the use of arrow syntax. For example we could implement equals utility function as so:

const equals = a => b => a === b;

But this approach has a particular drawback. If you defined a function as curried, now you can only use it in its curried form. Meaning, writing equals(5, 4) will not work now.

That's because even though you passed two arguments to it, our equals function only expects one. The second argument gets ignored, and the function returns another function, to which we could apply the second argument.

So, in the end, we would have to use this function by writing equals(5)(4), which maybe is not tragic but looks a bit awkward.

Luckily both Ramda and Lodash provide us with a handy curry helper function, which can be used to produce functions that work both in curried and uncurried forms.

So, using the Ramda library, we could define our equals function like so:

import { curry } from 'ramda';

const equals = curry((a, b) => a === b);

And now we can use this function in a traditional way, by calling equals(5, 4), but we can also utilize its curried form by - for example - passing only one argument to it in the filter method:

.filter(equals(5))

This versatility is built-in in many functional programming languages. With the curry helper function, we can easily achieve the same effect in JavaScript.

Functional wrappers for JS methods

The last thing that I would like to mention is that Ramda and Lodash libraries have wrappers for native JavaScript functions and methods.

We have already seen that things which are already available and easy in the language (like equality checks) have their corresponding wrappers (equals function), in order to make functional programming with them easier.

The same thing applies to other methods. For example, popular array methods map, filter, and reduce all have their corresponding functions in Ramda and Lodash.

Why would that be useful?

As we mentioned, again and again, the whole point of functional programming is easy composability. Creating a function that has a new behavior should be easy and preferably would be a composition of other functions.

Let's take our stringToInt function and say that we want to create a version of that function that works on arrays of strings. The obvious solutions is a code like this:

const stringsToInts = strings => strings.map(stringToInt);

The above is not the worst, but is there a way to write that even cleaner?

First thing that we have to notice is that the map method accepts two arguments and not one, as it might seem at the beginning. It accepts the first parameter - an array of strings - in a method syntax, before the dot, and the second parameter - a function - inside regular function brackets:

firstArgument.map(secondArgument);

This object-oriented syntax makes things a bit more confusing. Let's imagine that map is a regular function, not a method. Then we would rewrite our code like so:

const stringsToInts = strings => map(strings, stringToInt);

But wait. Now we can notice something. Could we maybe use a curried version of map to write that code? Before we try that, let's reverse in what order strings and stringToInt arguments are accepted:

const stringsToInts = strings => map(stringToInt, strings);

We have a function that accepts an array and returns an array. But that's exactly what curried version of map would do! Let's see:

const stringsToInts = map(stringToInt);

Whoa, whoa! What exactly happened here? Let's go through that example again, step by step.

map is a function that accepts two parameters, an array, a function, and returns a new array. If map was curried, we could provide it only one parameter - the function.

What would we get as a result? Well, the curried function returns another function that waits for the second argument. In this case, a second argument is an array because we passed only the function so far.

So, as a result, we get... a function that accepts an array and returns an array (after applying stringToInt function to each parameter, of course).

But that's exactly what we wanted!

Indeed, those two functions:

const stringsToInts = strings => strings.map(stringToInt);

const stringsToInts = map(stringToInt);

behave the same way! After running them on ["1", "2", "3"] we get [1, 2, 3].

Again, which code looks cleaner to you depends entirely on your past experiences, but you cannot argue that using a curried version of the map at the very least gives you more flexibility in how you write your code.

Note that we had to make three changes to the map:

  • We had to make it a function (instead of a method).
  • We had to reverse the order of arguments.
  • We had to make the function curried.

That's how Ramdas and Lodash array methods differ from their native implementations.

You can use those (and much more) wrapper functions when writing functional code with native JavaScript implementations seems awkward and convoluted.

Conclusion

The theme of this article was composability. I attempted to show you how you can make your codebase more composable, by utilizing functional programming patterns, most notably by currying your functions.

I then presented how some functional programming utility libraries, like Ramda and lodash, make it a bit easier to write code of that style in JavaScript.

I would highly encourage you to write some code entirely in the functional style. I wouldn't do that for production applications because the most readable JavaScript is a mix of functional and object-oriented approaches. However, it is still a great exercise to familiarize yourself deeply with the concepts described in that article.

The practice is key here. If you do that, soon, even the most confusing looking functional code will actually seem simpler and nicer to you than its traditional alternative.

If you enjoyed this article, considered following me on Twitter, where I am regularly posting articles on JavaScript programming.

Thanks for reading!

(Cover Photo by La-Rel Easter on Unsplash)

Written by
Mateusz Podlasin
Software Engineer at 11Sigma
11sigma logo
2020 © All rights reserved. 11Sigma Sp. z o.o.