Spice up your JavaScript

Riccardo Odone
4 min readJun 16, 2018

--

Which one of the following equivalent implementation do you prefer?

[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
.filter(int => isEven(int))
.filter(int => isBiggerThan(3, int))
.map(int => int + 1)
.map(int => toChar(int))
.filter(char => !isVowel(char))
.join('')
// 'fhjl'

vs


[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
.filter(isEven)
.filter(isBiggerThan(3))
.map(plus(1))
.map(toChar)
.filter(not(isVowel))
.join('')
// 'fhjl'

I’d argue the second one is waaaay more readable. The secret is removing the arguments from the filters and maps.

Let’s see how it works. I promise, once you see how that works, you won’t be able to unsee it anymore.

A Simple Function

Let’s take a sum function

const sum = (a, b) => a + bsum(1, 2)
// 3

and rewrite it in a different way

const csum = a => b => a + bcsum(1)(2)
// 3

They work the same, the only difference is how you call them: sum accepts two parameters at once, csum accepts parameters one by one. In particular, let’s say you call csum only once with the term 1

csum(1)
// b => 1 + b

then you get back a function that accepts the second term and returns it incremented by one

const plusOne = csum(1)plusOne(2)
// 3

Operating on Arrays

In JavaScript arrays can be manipulated with various methods. For instance, map is used to apply the same function to each element of an array.

To increment each integer in an array

[1, 2, 3].map(x => x + 1)
// [2, 3, 4]

In other words, x => x + 1 takes an integer and returns its successor. Using plusOne from above the function can be rewritten to

[1, 2, 3].map(x => plusOne(x))
// [2, 3, 4]

But wait a sec, x => plusOne(x) and just plusOne are equivalent, in fact

const otherPlusOne = x => plusOne(x)
otherPlusOne(1)
// 2
plusOne(1)
// 2

For the same reason

[1, 2, 3].map(x => plusOne(x))
// [2, 3, 4]

is equivalent to

[1, 2, 3].map(plusOne)
// [2, 3, 4]

and since plusOne was defined above as const plusOne = csum(1)

[1, 2, 3].map(csum(1))
// [2, 3, 4]

Now your turn, apply the same process used for sum to isBiggerThan so that you don’t need to specify arguments in the filter (ie int =>)

const isBiggerThan = (threshold, int) => int > threshold[1, 2, 3, 4].filter(int => isBiggerThan(3, int))

Now the code from the intro shouldn’t have any more secrets to you.

[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
.filter(isEven)
.filter(isBiggerThan(3))
.map(plus(1))
.map(toChar)
.filter(not(isVowel))
.join('')
// 'fhjl'

Two Simple Rules

Rule 1

The following two are equivalent

[…].map(x => fnc(x))[…].map(fnc)

Rule 2

It’s always possible to rewrite a callback to remove arguments

const fnc = (x, y, z) => …
[…].map(x => fnc(x, y, z))
const fnc = (y, z) => x => …
[…].map(fnc(y, z))

You’ve prolly applied this transformation if you’ve worked on the isBiggerThan exercise. In fact, let’s say we want to keep integers bigger than 3

const isBiggerThan = (threshold, int) => int > threshold
[…].filter(int => isBiggerThan(3, int))

Now we can rewrite isBiggerThan to remove the int => part in the filter

const isBiggerThan = threshold => int => int > threshold
[…].map(isBiggerThan(3))

Go After It

Let’s say you have

const keepGreatestChar =
(char1, char2) => char1 > char2 ? char1 : char2
keepGreatestChar('b', 'f')
// 'f'
// because 'f' comes after 'b'

Rewrite keepGreatestCharBetweenBAnd to remove the char argument

const keepGreatestChar = 
(char1, char2) => char1 > char2 ? char1 : char2
const keepGreatestCharBetweenBAnd = char =>
keepGreatestChar('b', char)
keepGreatestCharBetweenBAnd('a')
// 'b'
// because 'b' comes after 'a'

Rewrite greatestCharInArray using keepGreatestChar to remove the arguments (ie (acc, char)) from inside reduce

const keepGreatestChar =
(char1, char2) => char1 > char2 ? char1 : char2
const greatestCharInArray =
array => array.reduce((acc, char) => acc > char ? acc : char, 'a')
greatestCharInArray(['a', 'b', 'c', 'd'])
// 'd'

Implement creduce so that greatestCharInArray can employ it and not need any arguments

const creduce = …
const greatestCharInArray =
array => array.reduce((acc, char) => acc > char ? acc : char, 'a')

creduce should be generic enough to be applied to any problems that require a reduce operation. In other words, it should receive callback, an init value and the array to operate upon.

Hint: you want to be able to write

const greatestCharInArray = creduce(keepGreatestChar, 'a')greatestCharInArray(['a', 'b', 'c', 'd'])
// 'd'

What the Hell is that `c` in `csum` and `creduce`?

c stands for curried and what you’ve seen above is how curried functions can make your code more readable. To be precise I haven’t used proper curried functions in this article but that’s good enough.

If you want to dig deeper the art and science of curried functions I recommend reading Chapter 4 of Mostly Adequate Guide to Functional Programming. And since you are there, trust me, read the book entirely.

Also, if you want to read more about functional programming in JavaScript, take a look at FP in vanilla JavaScript: a rookie’s intro.

Get the latest content via email from me personally. Reply with your thoughts. Let’s learn from each other. Subscribe to my PinkLetter!

--

--

Riccardo Odone

🏳️‍🌈 Pronoun.is/he 💣 Maverick & Leader @Lunar_Logic ✉️ PinkLetter Odone.io/#newsletter 🎓 Student & Teacher of Timeless Software Skills