Functional Programming — Composition

In the last blog, we were talking about curried functions, they are often constructed with composition together. So what is composition? In mathematics, function composition is an operation that takes two functions f and g and produces a function h such that h(x) = g(f(x).
For example:
const f = n => n * 2;
const g = n => n - 1;
const h = x => f(g(x));
h(21); //=> 40
In short, compose()
creates a pipeline of functions with the output of one function piped into the input of the next one.
const compose = (...fns) => x => fns.reduceRight((y, f) => f(y), x);
This version takes any number of functions and returns a function which takes the initial value, and then uses reduceRight()
to iterate right-to-left over each function, f
, in fns
, and apply it in turn to the accumulated value, y
. What we're accumulating with the accumulator, y
in this function is the return value for the function returned by compose()
.
There’s a very similar one called pipe
. The difference is the order of the composition. Compose is a right-to-left while Pipe is a left-to-right composition.
const pipe = (...functions) => args => functions.reduce((arg, fn) => fn(arg), args);
And in a way to make it simple, composition is dot chaining:
const doStuff = _.compose(
join(''),
_.filter(x => x.length > 3),
reverse,
_.map(trim),
split(' '),
toLowerCase
)// the same thing without compositionconst doStuff = str => {
const lower = str.toLowerCase()
const words = lower.split(' ')
words.reverse()
for(let i in words) {
words[i] = words[i].trim()
}
let keepers = []
for(let i in words) {
if(words[i].length > 3) {
keepers.push(words[i])
}
}
return keepers.join('')
}
Finally let’s look at some examples.
const _ = R;const { formatMoney } = accounting;const CARS = [
{ name: “Ferrari FF”, horsepower: 660, dollar_value: 700000, in_stock: true },
{name: “Spyker C12 Zagato”,horsepower: 650,dollar_value: 648000,
in_stock: false,}
];
=================
// use _.compose(), _.prop() and _.head() to retrieve the name of the first car
const nameOfFirstCar = _.pipe(_.head, _.prop("name"));
====================
// Use the helper function _average to refactor averageDollarValue as a composition
const _average = function (xs) {
return _.reduce(_.add, 0, xs) / xs.length;
}; // <- leave be
// var averageDollarValue = function(cars) {
// var dollar_values = _.map(function(c) { return c.dollar_value; }, cars);
// return _average(dollar_values);
// };
// var averageDollarValue = _.compose(_.average, _.map, //_.prop("dollar_value"))
var averageDollarValue = _.compose(_average, _.map(_.prop("dollar_value")));
==================
// Write a function: sanitizeNames() using compose that returns a list of lowercase and underscored names: e.g: sanitizeNames(["Hello World"]) //=> ["hello_world"].
const _underscore = _.replace(/\W+/g, "_"); //<-- leave this alone and use to sanitize
// const sanitizeNames = _.compose(_.map(_.toLower), //_.map(_underscore), _.map(_.prop("name"))) the same as
const sanitizeNames = _.map(_.compose(_.toLower, _underscore, _.prop("name")));
============
// Refactor availablePrices with compose.
// const availablePrices = function(cars) {
// const available_cars = _.filter(_.prop('in_stock'), cars);
// return available_cars.map(x => formatMoney(x.dollar_value)).join(', ');
// }
const availablePrices = _.compose(
_.join(", "),
_.map((x) => formatMoney(x.dollar_value)),
_.filter(_.prop("in_stock"))
);
=======================
// Refactor to pointfree.
// const fastestCar = function(cars) {
// const sorted = _.sortBy(car => car.horsepower, cars);
// const fastest = _.last(sorted);
// return fastest.name + ' is the fastest';
// }
const fastestCar = _.compose(
_.concat(" is the fastest"),
_.prop("name"),
_.last,
_.sortBy(_.prop("horsepower"))
);