Exploring Composition in Javascript

DamCosset
866 views

Open Source Your Knowledge, Become a Contributor

Technology knowledge has to be shared and made accessible for free. Join the movement.

Create Content

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:

1
22
23
24
25
26
27
// Our functions here{...}
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 )
console.log(newText)
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:

1
24
25
26
27
// Our functions here{...}
const newText = createText( shortUpper( words( text ) ) )
console.log(newText)
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:

1
24
25
26
27
28
29
30
// Our functions here{...}
function upperLessThan5( text ){
return createText( shortUpper( words( text ) ) )
}
const newText = upperLessThan5( text )
console.log(newText)
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:

1
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
// Functions here{...}
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 ) ) ) )
}
const text = 'Hello! My name is Damien and I love Javascript. Make this exciting.'
const newText = firstCharUpperExclamation( text )
console.log(newText)
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:

1
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
// Our functions here{...}
const text = 'Hello! My name is Damien and I love Javascript. Make this exciting.'
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 )
const newTextThreeFunctions = upperLessThan5( text )
const exclamationFirstCharUpper = composeFourFunctions( createText, upperFirstChar, exclamationMarks, words)
const newTextFourFunctions = exclamationFirstCharUpper( text )
console.log(newTextThreeFunctions)
console.log(newTextFourFunctions)
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:

1
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
// Our functions here{...}
function compose( ...fns ){
return function composed( value ) {
let listOfFunctions = fns.slice()
while( listOfFunctions.length > 0 ){
value = listOfFunctions.pop()( value )
}
return value
}
}
const text = 'Hello! My name is Damien and I love Javascript. Make this exciting.'
const upperLessThan5 = compose( createText, shortUpper, words )
const compose3 = upperLessThan5( text )
const exclamationFirstCharUpper = compose( createText, upperFirstChar, exclamationMarks, words )
const compose4 = exclamationFirstCharUpper( text )
console.log(compose3)
console.log(compose4)
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.

1
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
// Our functions here{...}
const text = 'Hello! My name is Damien and I love Javascript. Make this exciting.'
const upperLessThan5Exclamation = compose( createText, shortUpper, exclamationMarks, words )
// OOOOOORRRR
const exclamationUpperLessThan5 = compose( createText, exclamationMarks, shortUpper, words )
// Same thing, different order
const exampleOne = upperLessThan5Exclamation( text )
const exampleTwo = exclamationUpperLessThan5( text )
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 )
const allOfIt = everything( text )
console.log(exampleOne)
console.log(exampleTwo)
console.log(allOfIt)
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!

Open Source Your Knowledge: become a Contributor and help others learn. Create New Content