Function Composition and Pointfree Coding Style
What is Pointfree?
A coding style that emphasizes composing functions rather than handling specific data, defining functions without explicitly mentioning their parameters, and expressing data flow through function composition and higher-order functions.
Sum of Squares
sumOfSquares([1,2,3]) // (1 * 1) + (2 * 2) + (3 * 3) = 14
Pointful Example
- Accept an array of numbers
- Iterate through each number to get its square
- Sum the squared numbers to get the total
- Return the total
function sumOfSquares(arr) { let sum = 0 for (let i = 0; i < arr.length; i++) { sum += arr[i] * arr[i] } return sum}
Pointfree Example
Achieving a functional solution by combining JS built-in higher-order functions map
and reduce
:
const square = x => x * x;const add = (a, b) => a + b;
const sumOfSquares = arr => arr.map(square).reduce(add, 0);
However, strictly speaking, this is still not fully Pointfree, as we are still directly manipulating arr
within the function. We can abstract “data manipulation” into the composition of functions, which can be achieved through compose
:
const compose = (...fns) => x => fns.reduceRight((v, f) => f(v), x);const map = fn => arr => arr.map(fn);const reduce = (fn, init) => arr => arr.reduce(fn, init);
const square = x => x * x;const add = (a, b) => a + b;
const sumOfSquares = compose( reduce(add, 0), map(square));
sumOfSquares([1, 2, 3]);
Extracting Fields
Suppose we have a group of user data, and we want to extract all the names of users:
const users = [ { id: 1, name: "Tom" }, { id: 2, name: "Mary" }, { id: 3, name: "John" }];
Pointful Example
- Accept an array of users
- Create a new empty array
- Iterate through each user object, extract the name, and add it to the new array
- Return the new array
function getNames(users) { const names = [] for (let i = 0; i < users.length; i++) { names.push(users[i].name) } return names}
getNames(users) // ["Tom", "Mary", "John"]
Pointfree Example
Similarly, we can use built-in higher-order functions to make the code more concise and avoid explicit manipulation of parameters:
const prop = key => obj => obj[key];const getNames = users => users.map(prop("name"));getNames(users) // ["Tom", "Mary", "John"]
Here, we further wrap map
into a composable version, allowing it to directly take a function and return a new processing function.
const map = fn => arr => arr.map(fn);const prop = key => obj => obj[key];const getNames = map(prop("name"));getNames([ { name: "Tom" }, { name: "Mary" }, { name: "John" },]); // ["Tom", "Mary", "John"]
Summary
- Pointful: Functions explicitly receive and manipulate data.
- Pointfree: Express data flow through function composition without directly mentioning the data itself.
Benefits
- Reduces unnecessary naming, keeping code concise.
- Achieves better generic composability.
Drawbacks
- Requires additional understanding of function composition and currying concepts, which may not be intuitive for beginners.