Note: Check the theoretical introduction of functional programming in JavaScript before moving on to coding. Feel free to take the code examples below and play with them yourself. You can use JS Bin for that.
Point-free style
This is something which you’ve probably been doing already, just not knowing the name.
Let’s say we have an array of numbers. For some reason, we need the number to be multiplied by 100, so let’s use the map function (a built-in higher-order function that returns a new array by iterating over the provided one and changing its elements) for that:
const numbers = [1, 50, 20, 100];
const numbersTimes100 = numbers.map(number => number * 100);
Pretty easy. Now let’s say we wanted to extract the mapping function into a standalone utility function, to be reusable in the future:
const multiplyBy100 = number => number * 100;
How would we use it inside a map function? Probably a most intuitive solution would be the following one:
const numbersTimes100 = numbers.map(number => multiplyBy100(number));
Works fine, but don’t you feel that something’s a bit odd here? We’ve just created a utility function just for this purpose, but now we’re creating another anonymous function that just calls our utility function. So how about passing our utility function directly into map?
const numbersTimes100 = numbers.map(multiplyBy100);
If we run the code we will see that it still works. Why? Instead of creating an inline anonymous function, we’ve passed the reference to multiplyBy100 directly into map. Function signatures are matching, the number parameter gets passed into our function automatically, removing the need for the inline function. Let’s take a quick look at some other simple ways we could use point-free style, to better grasp the concept:
const roundedNumbers = [1.5, 2.33, 10.625].map(Math.round); // [2, 2, 11]
['John', 'Mary'].forEach(console.log); // 'John', 0, ['John', 'Mary']
// 'Mary', 1, ['John', 'Mary']
The first one is fairly obvious, but what about the second example? Why did we get three items in our console.log? Well, the thing is that map’s callback has three parameters (current value, index and the array it was called upon), and all of them got passed into the console.log.
Let’s take a look at one last example for now. Consider an array of strings with some values, for example in pixels. Let’s say we want to extract the numbers to perform some mathematical operation on them. We’ll use parseInt for that:
const margins = ['8px', '16px', '32px', '48px'];
const marginsParsedWithInlineFn = margins.map(margin => parseInt(margin));
const marginsParsedPointFree = margins.map(parseInt);
console.log(marginsParsedWithInlineFn); // [8, 16, 32, 48]
console.log(marginsParsedPointFree); // [8, NaN, NaN, NaN]
Okay, what did just happen? Why did we get a correct number for 8, but NaNs for the other numbers, while everything is fine with our inline solution? Does it mean that point-free style is somehow bugged?
Well, no. Actually, everything worked exactly as it should.
There are two things at play here:
- parseInt takes a radix as a second argument (default: 10);
- the second parameter to map’s callback is index.
In the first example everything is as we’d expect, because we don’t explicitly pass radix into the function, so a value of 10 is assumed.
In the second example, the index gets passed as the radix, so we could write out all the parseInt calls like this:
parseInt('8px', 0); // 8
parseInt('16px', 1); // NaN
parseInt('32px', 2); // NaN
parseInt('48px', 3); // NaN
All the NaNs start making sense now. But why did we get the correct answer for 8? If you’re curious, see the MDN docs for an answer :).
Does this mean we are stuck with one-parameter functions, or the ones that exactly match the signature we want? Not at all. We will learn some ways of dealing with such situations in just a moment. But first, let’s try to fix this by re-implementing map. We could come up with a simple definition such as this:
const map = (fn, array) => {
const newArray = [];
array.forEach(item => {
newArray.push(fn(item));
});
return newArray;
};
Now let’s use it with the parseInt function:
const margins = ['8px', '16px', '32px', '48px'];
const marginsParsedWithCustomMap = map(parseInt, margins); console.log(marginsParsedWithCustomMap); // [8, 16, 32, 48]
Works as expected. Notice how we put the array we want to map over as the second argument. This is how most FP libraries define functions like map or reduce, and for a good reason. Functional programmers tend to put the more general arguments first, and more specific ones after. We will see why this is important and how it can be useful in the next parts of the series.
Before talking about argument adapters, let’s quickly introduce a new term - function arity. Put simply, arity is the number of the inputs of a function. So, a function with one input (unary) has an arity of one, function with two inputs (binary) - arity of two, and so on.
Argument adapters
What are argument adapters? The idea is pretty simple - an argument adapter is a higher-order function (check out the previous article if you don’t know what that is yet) that takes another function and returns it with a changed signature. The signature may be modified in many ways, for example by changing the arity or the order of arguments.
With that in mind, let’s take a look at our parseInt example once again. How could we fix it? One way would be to adapt the parseInt function itself to be unary and let the radix be the default value. Let’s define a utility function for this:
function unary(fn) {
return function(arg) {
return fn(arg);
}
}
// ES6 way:
const unary = fn => arg => fn(arg);
Quick note: I will be using the ES6 arrow functions most of the time. If using the function keyword is more readable to you, feel free to just rewrite the functions with it - be sure to remember about returning the inner functions!
How does it work? The unary function is a higher-order function that takes the original function (parseInt) and returns another one that takes only the first parameter and calls the original function with this it. Let’s see if this works:
const margins = ['8px', '16px', '32px', '48px'];const unary = fn => arg => fn(arg);
const unaryParseInt = unary(parseInt);const marginsParsedWithInlineFn = margins.map(margin => unaryParseInt(margin));
const marginsParsedPointFree = margins.map(unaryParseInt)console.log(marginsParsedWithInlineFn); // [8, 16, 32, 48]
console.log(marginsParsedPointFree); // [8, 16, 32, 48]
That’s what we wanted! Notice how we passed the reference to parseInt into the unary utility, effectively creating a slightly modified version of it. We will make heavy use of this pattern throughout our JavaScript functional programming series, so get familiar with it.
Let’s play with this a bit and take a look at some other utilities that might be useful later on.
Changing the way arguments are passed in
What if we had an array of values that we want to pass as parameters to a function? Or what if we had individual values that we wanted to pass in as an array?
Consider the following utility functions:
// receives args as an array,
// passes them to fn as individual parameters
const apply = fn => args => fn(...args);
// receives individual arguments,
// passes them to fn as an array
const unapply = fn => (...args) => fn(args);
Pretty simple, right? The names apply and unapply are the common names used by the community for these functions.
Changing the number of arguments
We’ve already implemented a utility that adapts the number of arguments - the unary function. Let’s try to implement a more general utility, where we can pass in the number of arguments we’d like to receive:
const nAry = (n, fn) => (...args) => fn(...args.slice(0, n));
There’s something similar to what we’ve done before here. In fact, we could implement nAry using our apply function from the previous section:
const nAry = (n, fn) => (...args) => apply(fn)(args.slice(0, n));
Notice the fn(arg1)(arg2) pattern here. This is called currying and we will learn about it in the next part of the series.
Now, we could also reuse the logic and define unary, binary etc. with the nAry function:
const unary = fn => nAry(1, fn);
const binary = fn => nAry(2, fn);
Changing the order of arguments
In some cases, we might need to change the order of the arguments of a function, but we might not be able to change its signature by altering the function itself - maybe it’s a third-party package, or maybe we need the different signature in on particular use case and refactoring is just not worth it. In such situations, utilities like these might come in handy:
// reverses the order of arguments
const reverseArgs = fn => (...args) => fn(...args.reverse());// reverses the order of the first two arguments
const flipArgs = fn => (...args) => fn(
...args.slice(0, 2).reverse(),
...args.slice(2),
);
We could come up with all sorts of utilities like these. It’s a great exercise to get more familiar with higher-order functions. But when using such utilities in production, I would advise to go with some tried and tested solutions like Ramda. We will be using it from now on in some of our examples. I know that there’s a ton of utilities there if you take a look at the docs, and it can feel a bit intimidating at first. Don’t get discouraged though - we will not use all of them. In fact, by using just a few most popular ones, we can greatly improve the quality of our code and leverage FP patterns to our advantage.
How Point-free and argument adapters help in JavaScript Functional Programming?
What did we achieve by going point-free? We’ve made our code more readable by removing some unnecessary verbosity. By passing just a reference to the function (and naming functions properly!) we have now shifted even more towards the declarative style.
What about function adapters? We now know that we can alter the signatures of the functions to our needs, without modifying or reimplementing existing functions! That’s a huge advantage and a really powerful tool, which will come in handy many, many times.
The next parts will cover topics like partial application, currying, and composition amongst others. In the meantime, make sure to get comfortable with the things we learned today, as we will make heavy use of them. Stay tuned.
Like what our developers do? Join our frontend team! We’re looking for software developers - check our job offers!
Navigate the changing IT landscape
Some highlighted content that we want to draw attention to to link to our other resources. It usually contains a link .