Introduction
Insert another intro about functional programming...
Composition
Composition is about creating small functions and creating bigger and more complete functions with them. Think of a function as a brick, composition is how you would make those bricks work together to build a wall or a house.
You might have encoutered composition in mathematics, written like so: f(g(x)). The function f is composed with the function g of x. Or f after g equals f of g of x. After because we evaluate the functions from right to left, from the inside to the outside:
f <-- g <-- x
The output of the precedent function becomes the input of the next. x is the input of g. The output of g(x) becomes the f input.
Examples?
Ok, let's code something then. Imagine that you are a company that is in charge of manipulating text. You receive a bunch of words, and your customers want them back in a certain way.
A client comes at you with a text and says:
I want the words shorter than 5 characters to be uppercased.
We create three functions to execute those actions. One function takes the text and return words in lowercase. The second function looks for short words and upper-case them. Finally, the third recreates the text from the array received.
function words( text ){
return String( text )
.toLowerCase()
.split(/\s/)
}
function shortUpper( words ){
return words.map( word => {
if( word.length < 5 ){
return word.toUpperCase()
} else {
return word
}
})
}
function createText( array ){
return array.join(' ')
}
The client sends in the text and we make our functions work:
const text = 'Hello! My name is Damien and I love Javascript. Make this exciting.'
const allWords = words(text)
const upperText = shortUpper( allWords )
const newText = createText( upperText )
//hello! MY NAME IS damien AND I LOVE javascript. MAKE THIS exciting.
Great! The client got what he wanted. The problem is: our workers have to manually take the output of the words and shortUpper functions, carry them to the next function, and turn on the function's engine on. That's a lot of work, can we automate this?
Cue dramatic music
Enter composition
We want the function's outputs to be sent to the next function without having to do it ourselves. Like so:
const newText = createText( shortUpper( words( text ) ) )
//hello! MY NAME IS damien AND I LOVE javascript. MAKE THIS exciting.
We read this from left to right, but, as I mentioned earlier, we execute from the inside to the outside:
createText <-- shortUpper <-- words <-- text
We even decide to create a function for this popular demand:
function upperLessThan5( text ){
return createText( shortUpper( words( text ) ) )
}
upperLessThan5( text )
//hello! MY NAME IS damien AND I LOVE javascript. MAKE THIS exciting.
Our company has another popular demand: replacing '.' by '!!!' while making the first character of each word uppercase. We have some functions to handle that:
function exclamationMarks( words ){
return words.map( word => word.replace('.', '!!!'))
}
function upperFirstChar( words ){
return words.map( word => {
return `${word[0].toUpperCase()}${word.substr(1)}`
})
}
function firstCharUpperExclamation( text ){
return createText( exclamationMarks( upperFirstChar( words( text ) ) ) )
}
firstCharUpperExclamation( text )
//Hello! My Name Is Damien And I Love Javascript!!! Make This Exciting!!!
Cool, we're able to compose functions by combining several smaller functions!
Going nuts!
The company's CEO couldn't be happier. The factory transforms text very fast thanks to composing. But he wants more!
What if we had a function that took all the functions as inputs and just made composition happened by itself? We could call it compose.
The engineers gather up and brainstorm. They decide to experiment with the two products they already have. They come up with this:
function composeThreeFunctions(fn3, fn2, fn1){
return function composed( firstValue ){
return fn3( fn2( fn1( firstValue ) ) )
}
}
function composeFourFunctions(fn4, fn3, fn2, fn1){
return function composed( firstValue ){
return fn4( fn3( fn2( fn1( firstValue ) ) ) )
}
}
const upperLessThan5 = composeThreeFunctions( createText, shortUpper, words )
upperLessThan5( text )
//hello! MY NAME IS damien AND I LOVE javascript. MAKE THIS exciting.
const exclamationFirstCharUpper = composeFourFunctions( createText, upperFirstChar, exclamationMarks, words)
exclamationFirstCharUpper( text )
//Hello! My Name Is Damien And I Love Javascript!!! Make This Exciting!!!
Our functions takes all the functions needed as parameters. It returns a function that takes the original value as parameters and return all the functions composed in the proper order. Be careful about the order! We execute from the inside to the outside. The last function you specified will be the first executed. How do the function remembers all the functions specified in the parameters? Closure!!!!
Now, we can compose whatever we want with three or four functions. But the CEO wants something generic.
What if we need to compose only two functions? or five? ten? I don't want an infinite amount of functions sitting in my factory. Can you create one function that just take an arbitrary number of functions and compose?
Finally, the engineers come up with the compose function:
function compose( ...fns ){
return function composed( value ) {
let listOfFunctions = fns.slice()
while( listOfFunctions.length > 0 ){
value = listOfFunctions.pop()( value )
}
return value
}
}
const upperLessThan5 = compose( createText, shortUpper, words )
upperLessThan5( text )
//hello! MY NAME IS damien AND I LOVE javascript. MAKE THIS exciting.
const exclamationFirstCharUpper = compose( createText, upperFirstChar, exclamationMarks, words )
exclamationFirstCharUpper( text )
//Hello! My Name Is Damien And I Love Javascript!!! Make This Exciting!!!
The compose function takes a list of functions as a parameter. We use the rest operator (...) to gather that as an array. We return a function with the original value as parameter. Inside of this function, we create a local copy of the functions array ( how? CLOSUUUUUURE ). Then we call the last function of the array with the output of the last function. pop() returns the last element of the array and removes it from the array. The output of the last listOfFunctions element becomes the input of the next one. When our array is empty, we return the final value.
I mean, this is just amazing. Now we can go absolutely crazy.
Moar examples!!!
I'm just playing around now. But the sky is the limit.
const upperLessThan5Exclamation = compose( createText, shortUpper, exclamationMarks, words )
// OOOOOORRRR
const exclamationUpperLessThan5 = compose( createText, exclamationMarks, shortUpper, words )
// Same thing, different order
upperLessThan5Exclamation( text )
exclamationUpperLessThan5( text )
//hello! MY NAME IS damien AND I LOVE javascript!!! MAKE THIS exciting!!!
//hello! MY NAME IS damien AND I LOVE javascript!!! MAKE THIS exciting!!!
function replaceAbyE( words ){
return words.map( word => {
return word.replace(/a/gi, 'e')
})
}
function replaceJavascriptWithPython( words ){
return words.map( word => {
if( word == /javascript/i ){
return 'Python'
} else {
return word
}
})
}
const everything = compose( createText, shortUpper, exclamationMarks, upperFirstChar, replaceAbyE, replaceJavascriptWithPython, words )
everything( text )
//Hello! MY NEME IS Demien END I LOVE Python MEKE THIS Exciting!!!
Well, I'll stop there. I want to see how librairies like Ramda implement composition, but this is really a fun way to write code. My implementation is of course only one possibility. You could create a different one. You could implement a pipe functionality ( from right to left )... I'll probably explore that in another article.
Love!