What is functional composition?
Functional composition is the technique of composing multiple functions into a complex function. In Mathematic definition,
f(x) = x + 1
g(x) = x * 2
h(x) = f(g(x)) = f(x * 2) = (x * 2) + 1
f(x) and g(x) are functions and h(x) is the composed function of f and g.
Functional composition is an efficient technique in processing list data and it is preferred over calling chained array methods to achieve the same result.
The code snippet below uses Array.filter, Array.map and Array.reduce to calculate the total, 105.
const multThree = (num: number) => num * 3
const addOne = (num: number) => num + 1
const isEven = (num: number) => num % 2 === 0
const combineSum = (acc: number, num: number) => acc + num
const data = [1,2,3,4,5,6,7,8,9,10]
const total = data.filter(isEven)
.map(addOne)
.map(multThree)
.reduce(combineSum)
The chained methods produces intermediate arrays before we calculate the total.
[1,2,3,4,5,6,7,8,9,10].filter(number => number % 2 === 0) => [2,4,6,8,10]
[2, 4, 6, 8, 10].map(num => num + 1) => [3,5,7,9,11]
[3,5,7,9,11].map(num => num * 3) => [9,15,21,27,33]
[9,15,21,27,33].reduce((acc, num) => acc + num, 0) => 105
Array method chaining has the following drawback:
- Produce the side effect of intermediate arrays and can lead to memory issue with big input array
- Developers have to trace each step to visualize the input and output of it
- Authors think in iterative approach and not functional approach
Functional composition can solve this problem because it combines functions to create a new function. When we feed list data to the new function, it manipulates the data once and combines the new result with the previous result.
In this post, we will see how lodash/fp offers compose, pipe and the counterpart of Array.methods to build a composed function. The composed function is capable of accepting input list and returning the final output by processing the list once.
Functional composition with compose
compose executes functions from right to left direction. If the functions are f(g(x)), we call compose(g(x), f(x)) in this manner.
Let’s rewrite the above function with high order function, compose.
import { compose, map, filter, reduce } from 'lodash/fp'
const composeFunction = compose(
reduce(combineSum),
map(multThree),
map(addOne),
filter(isEven)
)
const total2 = composeFunction(data)
console.log('with lodash/fp compose', total2)
First, we import compose, map, filter and reduce
from ‘lodash/fp’. Since compose
executes from right to left direction, reduce is the first argument of compose where as filter is the last one.
In the code, we use compose
to combine filter
, map
and reduce
to build a composed function called composeFunction
.
When we feed data to composedFunction
, composedFunction
traverses the list once to calculate the total of 105.
composedFunction([1,2,3,4,5,6,7,8,9,10])
=> 9 + 15 + 21, 27, 33
=> 105
The benefit of composedFunction
is the removal of the the side effect of producing intermediate arrays.
Functional composition with pipe
Next, we rewrite composedFunction with pipe
and called it pipeFunction
Similar to compose
, pipe
is a high order function that flows data through functions from left to right direction.
import { pipe, map, filter, reduce } from 'lodash/fp'
const pipeFunction = pipe(
filter(isEven),
map(addOne),
map(multThree),
reduce(combineSum),
)
const total4 = pipeFunction(data)
console.log('with lodash/fp pipe', total4)
The arguments of pipe
are reversed but pipeFunction should achieve the same result as composedFunction
.
pipeFunction([1,2,3,4,5,6,7,8,9,10])
=> 9 + 15 + 21, 27, 33
=> 105
Finally, you can run the Stackblitz demo to play with the examples of compose, pipe and native methods of Array.
Final thoughts
Function composition has these benefits:
- Remove the side effect of producing intermediate arrays
- Developers do not have to trace the steps to derive the output list that becomes the input list of the next chained array method
- Codes adopt functional approach and are point-free
In conclusion, I highly recommend developers to understand and practice functional programming in JavaScript and use the tool to replace array chaining to improve the efficiency of list processing.
Resources:
- Functiona light JS: https://github.com/getify/Functional-Light-JS/blob/master/manuscript/ch4.md/#chapter-4-composing-functions
- lodash/fp guide: https://github.com/lodash/lodash/wiki/FP-Guide
- ramdaJs: https://ramdajs.com/docs/#compose
- Stackblitz: https://stackblitz.com/edit/typescript-tmi9eg