In this series of articles we will go through a soft introduction to functional programming in JavaScript.
Each article will be devoted to different aspect of functional programming. After the theoretical introduction we will see how those concepts are then put to use 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 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 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 exactly 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 result
variable.
So if we got a ["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
, map
each stringToInt
and then filter
only those values that are lessThan(5)
.
But if you are not used to functional style however, 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 as functions even the simplest pieces of our code. Thanks to that, we can now use those snippets in completely 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 example on arrays of strings).
This is the first moment when you can actually 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 exactly what we meant at the beginning. Functional approach allows you to use the same code in completely different contexts - in fact here the same code is even used on a completely different types of values! A function that was meant to work only on strings can now work on an 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 lessThen
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 function definition is different. Is this some new, exotic addition to JavaScript standard?
In fact, no. What we simply did here, is that we wrote a function that returns an 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 parantheses, for better readability.
Note that we used here the fact that in an arrow function we can provide returned value direcly, instead of function body. If we really wanted to write the body, we might rewrite 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, there original function accepts three parameters.
After currying it, we get a function that accepts one parameter a
, returns a function that accepts one parameter b
, then returns a function that accepts one parameter c
and finally executes the body of the original function.
Ok, we explained how that mechanism works, but we didn't explain why did we even decide to write our functions like that.
Frankly, the answer is extremely simple. The only reason is so that we could later use 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 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
.
In fact, it also composes nicely with 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 lessThen
function composes now much nicer with other, higher-order functions.
On top of that, assume we noticed that we use lessThen
very often with a number 5 specifically. Maybe that's a very important number, let's say a number of the servers that 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 extremely 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 really means:
const NUMBER_OF_SERVERS = 5;
Now we can use the constant, instead of the number:
.filter(lessThan(NUMBER_OF_SERVERS))
Iff that number changes (for example our company buys more servers), we can simply 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 separate values (lessThan
and NUMBER_OF_SERVERS
) even though we always want to use them together.
However, the way we defined lessThan
function allows us to fix that. We can simply store the returned function in an another variable!
const lessThanNumberOfServers = lessThan(NUMBER_OF_SERVERS);
Now whenever we want to use that function with that specific value, we can simply import it once and use it directly:
.filter(lessThanNumberOfServers)
So not only 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 a much bigger flexibility. When it turns out it is actually necessary to set that variable to some other value, you can do it easily, without any refactoring, simply by running someBiggerFunction
with another argument.
So, as we have seen, using curried versions of functions, gives us bigger composability, allowing for both easier use of those functions in other compositions, as well as composing brand new functions with ease.
Lodash and Ramda
I hope that it is clear by now that in order to use this aspect of functional programming, you don't need any external libraries.
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 popular functional programming utility libraries.
After all, one of the benefits or writing composable code was supposed to be reusability. This means it would be kind of pointless to write from scratch a 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. So, again, it would be dumb to write a completely new function to solve a particular problem, if you can simply compose that function from a two or three already existing functions.
So let's take a look at Lodash and Ramda and see what do they have to offer for programmers coding in functional style.
It's important to mention that in the case of Lodash we will be talking particuraliry 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 really 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 splitWhen
function in a traditional manner, passing to it two arguments and getting some result.
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 which 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 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 own 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 certain drawback. If you defined a function as curried, now you can only use it in it's 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. Second argument gets ignored and the function returns another function, to which just now 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 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 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 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 in relation to Ramda and Lodash libraries are wrappers for native JavaScript functions and methods.
We have already seen that things that 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 realy easy and preferably would be a composition of other functions.
Let's take our stringToInt
function and say that now 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);
This is not the worst, but is there a way to write that even cleaner?
First thing that we have to notice is that map
method accepts two arguments and not one, as it might seem at the beginning. It accepts first parameter - an array of strings - in a method syntax, before the dot, and 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 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 and 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, 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 in exactly 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 curried version of map
at the very least gives you more flexibility in how you write your code.
Note that we had to make three changes to map: we had to make it a function (instead of method), we had to reverse the order of arguments and we had to make the function curried.
That's exactly 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 fully in functional style. I wouldn't do that for production applications, because I believe that the most readable JavaScript is a mix between functional and object oriented approaches, but it is still a great exercise for you to familiarize yourself deeply with 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)
Top comments (2)
Checkout this new VS Code Extension: marketplace.visualstudio.com/items...
This extensions will allow you to have Ramda Js REPL/Playground & Documentation right in your VS Code Editor.
Follow for more on my Youtube Channel: youtube.com/channel/UCVhh3gpOXCb-r...
Great article! I love functional programming.