Functional programming is about developing small simple functions and combining them for the purposes of executing more complex tasks.
As the program’s logic is divided into small functions under the functional approach, they have to be assembled into larger functions for the application to work. This process is called composition.
Function composition (or function superposition) is a pinpoint application of one function to the result of another one, or in other words, the use of one function’s result as the argument of another one.
For example:
function1(function2(value))
Each time, the result of the right-hand function in the chain is passed as a parameter to the function located in the left-hand side. Therefore, all the functions in the chain can have only one parameter, except for the rightmost one which is executed first.
Function composition has the following advantages:
There is no сompose
function in the native JavaScript or React. It can be obtained with the aid of third-party libraries. For example, it is present in Redux.
The syntax of its application looks like this:
compose(function1, function2)(function3);
Here, function3
will be executed first. And then one by one, from right to left, its result will be accepted and processed by funсtion2
and function1
.
To obtain a more universal solution, we can use the reduceRight
method:
const compose = (...fns) => (initialVal) =>
fns.reduceRight((val, fn) => fn(val), initialVal);
Here are a few examples of how they can be used:
const add2 = (n) => n + 2;
const times2 = (n) => n * 2;
const times2add2 = compose(add2, times2);
const add2times2 = compose(times2,add2);
const add6 = compose(add2, add2, add2);
times2add2(2); // 6
add2tiems2(2); // 8
add6(2); // 8
You might think that it has no relation to front-end, but it is also helpful in SPA (single page application). For example, you can add behavior to the React component using higher-order functions:
function logProps(InputComponent) {
InputComponent.prototype.componentWillReceiveProps = function (nextProps) {
console.log('Current props: ', this.props);
console.log('Next props: ', nextProps);
};
return InputComponent;
}
// EnhancedComponent will log whenever props are received
const EnhancedComponent = logProps(InputComponent);
It is customary to use the abstraction tool called pipe()
for composition. With the aid of pipe
we obtain a declarative code, that is, the programmer describes the result and not the implementation.
Let’s consider the difference between the declarative and imperative approaches in more detail. Let’s assume there are 2 simple functions:
const g = n => n + 1;
const f = n => n * 2;
We want to combine them into one function, so that first one would be added to the number, and then the obtained result would be multiplied by 2.
With the imperative approach, the function’s implementation would look like this:
const doImperative = x => {
const afterG = g(x);
return f(afterG);
};
doImperative(20); // 42
The declarative approach suggests that the auxiliary function pipe()
should be used. It is implemented by many popular libraries, such as lodash
, but in actual fact its primitive implementation is quite simple and can fit into one line of code:
const pipe = (...fns) => x => fns.reduce((y, f) => f(y), x);
Now, with the pipe()
function, the example above can be rewritten using the declarative approach:
const doDeclarative = pipe(g, f)
doDeclarative(20); // 42
The difference between the approaches becomes even more obvious when the task gets more complicated. Let’s add logging after each step with the aid of the function:
const trace = message => value => {
console.log(`${message}: ${value}`);
return value;
}
The imperative style:
const doImperative = x => {
const afterG = g(x);
trace('after g')(afterG);
const afterF = f(afterG);
trace('after f')(afterF);
return afterF;
};
doImperative(20);
The declarative style:
const doDeclarative = pipe(
g,
trace('after g'),
f,
trace('after f')
);
doDeclarative(20);
The doDeclarative()
method doesn’t store the intermediary result in the variables, the result of the previous function is passed to the next one instead. The pipe
function is responsible for that.
It is interesting to note that the chain of Promises is a composition too:
Promise.resolve(20)
.then(g)
.then(trace('after g'))
.then(f)
.then(trace('after f'));
Each subsequent then
block accepts the previous one’s result and passes it to the next one. Looks very much like the previously considered variant of pipe
, doesn’t it?
Function composition improves code readability. Instead of function nesting, you can link the functions into a chain and create higher-order functions with meaningful names.
The implementation of compose
is present in many JavaScript utility libraries (lodash
, ramda
and so on).