Functional programming is a style of writing code in which functions are passed as arguments (callbacks) and also functions are passed without side effects (changes in the application state).
Many languages have adopted this style of working with code. The most popular ones are: JavaScript, Haskell, Clojure, Erlang, and Scala.
The ability to pass and return functions has given rise to many other concepts:
- Pure function
- Currying
- Higher-order functions
Now, we will consider currying.
What is Currying?
Currying is the conversion of a function with multiple arguments to a set of nested functions with a single argument. When a curried function is called with one argument passed to it, it returns a new function that waits for the next argument to arrive. New functions waiting for the next argument are returned each time the curried function is called, until the function gets all the arguments it needs. Previously received arguments, thanks to the closure mechanism, wait for the moment when the function gets everything it needs to perform calculations. After receiving the last argument, the function performs calculations and returns the result.
We can say that this is the process of turning a function with multiple arguments into a function with less arity. Arity is the number of arguments to a function.
Note that the term arity refers directly to the number of arguments that the function takes. For an example,
function fn(a, b) {
//…
}
function _fn(a, b, c)
{
//…
}
The fn function has two arguments (it is a binary or 2-ary function), the _fn function has three arguments (a ternary, 3-ary function).
So currying transforms a function with multiple arguments into a sequential series of related functions, each of which takes one of the desired arguments.
Let’s talk about a situation when, during currying, a function with several arguments is converted to a set of functions, each of which takes one argument. We have the following function:
function multiply(a, b, c) {
return a * b * c;
}
It takes three arguments and returns their product:
multiply(1,2,3); // 6
Now let’s think about how to convert it to a set of functions, each of which takes one argument. Let’s create a curried version of this function and look at how to get the same result when calling multiple functions:
function multiply(a) {
return (b) => {
return (c) => {
return a * b * c
}
}
}
log(multiply(1)(2)(3)) // 6
As you can see, here we have converted a call to a single function with three arguments-multiply(1,2,3) to a call to three functions-multiply(1)(2)(3).
It turns out that one function has turned into several functions. When using the new construction, every function except the last one that returns the result of calculations takes an argument and returns another function that can also take an argument and return another function. If the construction of the form multiply (1)(2)(3) does not seem very clear to you, let’s write it in this form to better understand it:
const mul1 = multiply(1);
const
mul2 = mul1(2);
const
result = mul2(3);
log
(result); // 6
Now let’s analyze what is happening here line by line.
First we pass argument 1 to the multiply function:
const mul1 = multiply(1);
When this function works, the following construction is triggered:
return (b) => {
return (c) => {
return a * b * c
}
}
Mul1 now has a reference to a function that accepts the b argument. Call the mul1 function, passing it 2:
const mul2 = mul1(2);
This call will result in the following code:
return (c) => {
return a * b * c
}
The mul2 constant will contain a reference to a function that might end up in it, for example, as a result of performing the following operation:
mul2 = (c) => {
return a * b * c
}
If you now call the mul2 function by passing it 3, the function will perform the necessary calculations using the arguments a and b:
const result = mul2(3);
The result of these calculations will be 6:
log(result); // 6
The mul2 function, which has the highest nesting level, has access to scopes, to closures formed by the multiply and mul1 functions. This is why in the mul2 function, you can perform calculations with variables declared in functions that have already been completed, that have already returned certain values, and that have been processed by the garbage collector.
Conclusion
As you can see, currying gives us fantastic opportunities to use functional programming in Javascript. This is a very tricky business at first, given closures and functional programming. But with time and constant practice, you will fully understand it and see its full value.
Read more about funny JavaScript libraries here.