Take a look at the following code snippets that solve the same problem, and think about which one you like best.
Here is the first: | Here is the second: |
[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'
| [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] .filter(isEven) .filter(isBiggerThan(3)) .map(plus(1)) .map(toChar) .filter(not(isVowel)) .join('') |
“I bet that the second version has a much better readability than the first,” says the author of the material, the translation of which we are publishing today. According to him, it’s all about the arguments of the
filter()
and
map()
methods.

Today we will talk about how to recycle code like the first example, so that it looks like the code from the second. The author of the article promises that after you understand how it works, you will relate to your programs in a new way and will not be able to ignore what used to seem quite normal and not requiring improvement.
Simple function
Consider a simple
sum()
function that adds the numbers passed to it:
const sum = (a, b) => a + b sum(1, 2)
Rewrite it, giving the new function the name
csum()
:
const csum = a => b => a + b csum(1)(2)
It works its new version in the same way as the original, the only difference is in how this new function is called. Namely, the function
sum()
takes two parameters at once, and
csum()
takes the same parameters one by one. In fact, when accessing
csum()
, two functions are called. In particular, consider the situation when
csum()
called by passing it the number 1 and nothing else:
csum(1)
Such a call to
csum()
leads to the fact that it returns a function that can take the second numeric argument passed to
csum()
in its usual call, and returns the result of adding one to this argument.
plusOne()
call this function
plusOne()
:
const plusOne = csum(1) plusOne(2)
Work with arrays
In JavaScript, you can work with arrays using a variety of special methods. Let's say the
map()
method is used to apply the function passed to it to each element of the array.
For example, in order to increase by 1 each element of an integer array (more precisely, to form a new array containing elements of the original, increased by 1), you can use the following construction:
[1, 2, 3].map(x => x + 1)
In other words, what is happening can be described as follows: the function
x => x + 1
takes an integer and returns the number that follows it in the series of integers. If we use the above
plusOne()
function, this example can be rewritten as:
[1, 2, 3].map(x => plusOne(x))
There is a moment to slow down and think about what is happening. If this is done, then it can be noted that in the considered case the constructions
x => plusOne(x)
and
plusOne
(note that in this situation there are no brackets after the function name) are equivalent. In order to better deal with this, consider the function
otherPlusOne()
:
const otherPlusOne = x => plusOne(x) otherPlusOne(1)
The result of this function will be the same as that obtained by simply calling
plusOne()
already known to us:
plusOne(1)
For the same reason, we can talk about the equivalence of the following two constructions. Here is the first we have already seen:
[1, 2, 3].map(x => plusOne(x))
Here is the second:
[1, 2, 3].map(plusOne)
In addition, recall how the
plusOne()
function was created:
const plusOne = csum(1)
This allows us to rewrite our construction with
map()
as follows:
[1, 2, 3].map(csum(1))
Let's create now, using the same technique, the
isBiggerThan()
function. If you want, try to do it yourself, and then continue reading. This will eliminate the use of unnecessary constructions when using the
filter()
method. First, we give the code to this form:
const isBiggerThan = (threshold, int) => int > threshold [1, 2, 3, 4].filter(int => isBiggerThan(3, int))
Then, getting rid of all the excess, we get the code that you have already seen at the very beginning of this material:
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10] .filter(isEven) .filter(isBiggerThan(3)) .map(plus(1)) .map(toChar) .filter(not(isVowel)) .join('')
We now consider two simple rules that allow you to write code in the style considered here.
Rule number 1
The following two constructions are equivalent:
[…].map(x => fnc(x)) […].map(fnc)
Rule number 2
Callback can always be rewritten to reduce the number of arguments used when it is called:
const fnc = (x, y, z) => … […].map(x => fnc(x, y, z)) const fnc = (y, z) => x => … […].map(fnc(y, z))
If you yourself wrote the
isBiggerThan()
function, then you probably already resorted to a similar transformation. Suppose we need to pass through the filter numbers that are greater than 3. This can be done as follows:
const isBiggerThan = (threshold, int) => int > threshold […].filter(int => isBiggerThan(3, int))
Now we will rewrite the
isBiggerThan()
function so that it could be used in the
filter()
method and not use the
int=>
construct:
const isBiggerThan = threshold => int => int > threshold […].map(isBiggerThan(3))
Exercise
Suppose we have the following code snippet:
const keepGreatestChar = (char1, char2) => char1 > char2 ? char1 : char2 keepGreatestChar('b', 'f') // 'f' // 'f' 'b'
Now, based on the
keepGreatestChar()
function, create the
keepGreatestCharBetweenBAnd()
function. We need to call it, it would be possible to pass to it only one argument, while it will compare the character passed to it with the symbol
b
. This function may look like this:
const keepGreatestChar = (char1, char2) => char1 > char2 ? char1 : char2 const keepGreatestCharBetweenBAnd = char => keepGreatestChar('b', char) keepGreatestCharBetweenBAnd('a')
Now write the function
greatestCharInArray()
, which, using the
keepGreatestChar()
function in the
reduce()
array method, allows you to search for the “largest” character and does not need arguments. Let's start with this code:
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'])
To solve this problem, implement the function
creduce()
, which can be used in the
greatestCharInArray()
function, which, in the practical application of this function, does not give it anything other than an array in which you need to find the symbol with the largest code.
The
creduce()
function must be sufficiently universal so that it can be used to solve any task that requires using the capabilities of the standard
reduce()
array method. In other words, the function must accept a callback, an initial value, and an array to work with. As a result, you should have a function with which the following code fragment will work:
const greatestCharInArray = creduce(keepGreatestChar, 'a') greatestCharInArray(['a', 'b', 'c', 'd'])
Results
Perhaps now you have a question about why the methods, processed in accordance with the method presented here, have names beginning with the character
c
. The symbol
c
is an abbreviation for the word curried — and above we talked about how curried functions help improve code readability. It should be noted that we did not strive here to strictly observe the principles of functional programming, but we believe that the practical application of what was discussed here allows us to improve the code. If the topic of currying in JavaScript is of interest to you, it is recommended to read chapter 4 of
this book about functional programming, and, in general, since you have reached this place - read the whole book. In addition, if you are new to functional programming, pay attention to
this material for beginners.
Dear readers! Do you use currying functions in JavaScript development?
