Back
Close

Exploring Composition in Javascript

DamCosset
2,126 views

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:

// Our functions here{ autofold
function words( text ){
return String( text )
.toLowerCase()
.split(/\s/)
}
function shortUpper( words ){
return words.map( word => {
if( word.length < 5 ){
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

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:

// Our functions here{ autofold
function words( text ){
return String( text )
.toLowerCase()
.split(/\s/)
}
function shortUpper( words ){
return words.map( word => {
if( word.length < 5 ){
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

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:

// Our functions here{ autofold
function words( text ){
return String( text )
.toLowerCase()
.split(/\s/)
}
function shortUpper( words ){
return words.map( word => {
if( word.length < 5 ){
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

Our company has another popular demand: replacing '.' by '!!!' while making the first character of each word uppercase. We have some functions to handle that:

// Functions here{ autofold
function createText( array ){
return array.join(' ')
}
function words( text ){
return String( text )
.toLowerCase()
.split(/\s/)
}
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

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:

// Our functions here{ autofold
function exclamationMarks( words ){
return words.map( word => word.replace('.', '!!!'))
}
function upperFirstChar( words ){
return words.map( word => {
return `${word[0].toUpperCase()}${word.substr(1)}`
})
}
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

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:

// Our functions here{ autofold
function exclamationMarks( words ){
return words.map( word => word.replace('.', '!!!'))
}
function upperFirstChar( words ){
return words.map( word => {
return `${word[0].toUpperCase()}${word.substr(1)}`
})
}
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

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.

// Our functions here{ autofold
function compose( ...fns ){
return function composed( value ) {
let listOfFunctions = fns.slice()
while( listOfFunctions.length > 0 ){
value = listOfFunctions.pop()( value )
}
return value
}
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

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!

Create your playground on Tech.io
This playground was created on Tech.io, our hands-on, knowledge-sharing platform for developers.
Go to tech.io
codingame x discord
Join the CodinGame community on Discord to chat about puzzle contributions, challenges, streams, blog articles - all that good stuff!
JOIN US ON DISCORD
Online Participants