JavaScript is a programming language that supports functional programming. This means that various templates and techniques are available. One of them is currying.
Currying is a useful technique in functional programming using which we can transform a function with multiple arguments into a sequence of nesting functions. The initial function takes the first argument and performs certain actions. As a result a new function is returning, that takes the second argument and returns a new function which takes the third one, and so forth, until all arguments are over.
At the end the function that receives the last argument returns the expected result. In other words, a function, instead of taking all arguments at one time, takes it in turn. The currying is called a higher order function because it accepts and returns a function.
Syntax
The concept of currying is considered not as a new one, but very useful. All in all, the currying function helps to avoid passing the same variable many times or can be useful in event handling. With currying your code can become more pure, readable and less prone to errors.
For example we have the usual addition function:
const sum = ( x, y ) => x + y;
To add two numbers you need to call this function like this:
sum(x, y);
Let’s curry this function:
function curryFunction(x)
{
return function(y)
{
return x + y;
}
}
Now to add two numbers you should write:
curryFunction(x)(y);
Currying divides the initial function that takes multiple arguments into several functions that each take only one.
We see that carrying is not a reduction of the number of arguments of a function, but only the construction of a sequence of functions with one argument, each of which returns the next.
Practically for this addition such a chain of nested functions is created. In reality, the program does not create intermediate constants. But the actions are repeated just as gradually:
const sum = (a) => (b) => a + b;
const result1 = sum(5); // result1 = b => 5 + b
const result = result1(4); // result = 5 + 4
console.log(result); // => 9
The initial function is divided into several functions depending on the arity. The arity or number of arguments of this function is two.
Closure
Currying functions are characterized by a hierarchical construction in reverse order. Each subsequent argument function has access to all previous arguments and functions because they are related to the area of visibility of that function. From the previous example, the function with argument y has access to x.
The closure is created when we are exposing one function from another function. All inner functions will always hold access to the argument of their parent function. Closure makes currying possible in programming.
Currying is available in all programming languages that support closure. Despite the fact that non-currying functions are calculated faster, as they do not require partial application and the creation of a closure.
Currying with helper function
Let’s create a wrapper function that can accept a function such as a sum as a parameter and return a currying version of the function.
To generalize, we can set the arity of a function N = func.length
, and thus create a recursive set of functions that will be repeated N
times. You can also create logic to recursively return internal functions for N
times.
Well, curryWrapper
- the main wrapper function, func
- functions to be curry (sum, mul, sum1), N
- its arity or number of arguments. The number of calls to the currying is equal to the number of received arguments sum, mul, sum1.
InnerFunc
function monitors the variable N
and, depending on its value, returns another function, or terminates (if n <= 1) the process by calling the current function with all the accumulated arguments. It accumulates all the arguments passed separately to the checkered function and encloses them in a cumulative array [... args, a], and then calls the initial function.
const sum = (x, y, z) => x + y + z;
function curryWrapper(func)
{
const N = func.length;
function innerFunc(n, args)
{
return function actualInnerFunc(a)
{
if(n <= 1)
{
return func(...args, a);
}
return innerFunc(n - 1, [...args, a]);
}
}
return innerFunc(N, [])
}
const sumRes = curryWrapper(sum);
console.log(sumRes(1)(2)(3)); // 6 as a result
This wrapper function can now be used for other functions with a different number of arguments. For example, let's try to implement multiplication:
const mul = (x, y, z) => x y z;
const mulRes = curryWrapper(mul);
console.log(mulRes(1)(2)(5)); // 10
Or use the wrapper function to add more than 3 arguments:
const sum1 = (x, y, z, m, n, l) => x + y + z + m + n + l;
const sum1Res = curryWrapper(sum1);
console.log(sum1Res(2)(5)(3)(0)(10)(1)); // 21
Someone may consider that the arity and number of nested functions of a curried function are the same number. But in the example above we can see that it is a wrong prediction. Because the number of nested functions here is constant, only the loop can be executed a different number of times depending on N, but the number of arguments we pass is different: (1)(2)(3) or (2)(5)(3)(0)(10)(1).
Infinity currying
In general the currying needs the function to have a fixed number of arguments. But what to do if we don't know how many arguments we will pass to the function? Writing many identical functions for different numbers of arguments is definitely not the best idea.
Here you can get a little creative to tell the program that the list of arguments is over. Otherwise the program will not be able to exit the innerFunc
.
For example, we can define a breakpoint and teach actualInnerFunc
to stop at that place and start counting the result. A breakpoint can be any sign, number or word.
Let’s in our example zero will indicate that we have already gone through all the arguments and want to get a result (if (x == 0)):
const curryInfinite = func =>
{
const next = (...args) =>
{
return x =>
{
if (x == 0)
{
return args.reduce((acc, a) =>
{
return func.call(func, acc, a)
}, 0);
}
else return next(...args, x);
};
};
return next();
};
const sumRes = curryInfinite((x, y) => x + y);
console.log(sumRes(1)(2)(3)(0)); // 6
console.log(sumRes(1)(2)(3)(4)(5)(0)); // 15
console.log(sumRes(1)(2)(3)(4)(5)(6)(7)(8)(9)(0)); // 45
The reduce()
method is additionally used here. In fact, it calculates the sum of the elements in the array.
{ return func.call(func, acc, a) }, 0);
Note, a zero in line is not a breakpoint but the initial value to which we add all the elements of the array.
In this example, we provided arrays with different lengths, initializing zero at the end of each. The program correctly calculated their amounts. In addition, if you specify zero not at the end of the array, but inside then errors may occur. So this method can still be slightly improved.
Currying with a variable number of arguments
In a more advanced version of the currying, we can allow actualInnerFunc
to have a variable number of arguments, not just a fixed number. Thus we will be able to process the records sumRes(1, 2)(3)
, sumRes(1)(2, 3)
, sumRes(1)(2)(3)
, sumRes(1, 2, 3)
using only one method.
For example, if the function is called as sum(1)(2,3)
, then we will take into account the length of arguments 2 and 3, because calls (2) and (3) go together. The initial function will be called until the number of arguments N is reached.
const sum = (x, y, z) => x + y + z;
const curryWrapper = func =>
{
const innerFunc = (N, args) =>
{
return (...x) =>
{
if (N <= x.length)
{
return func(...args, ...x);
}
return innerFunc(N - x.length, [...args, ...x]);
};
};
return innerFunc(func.length, []);
};
const sumRes = curryWrapper(sum);
console.log(sumRes(1, 2)(3)); // 6
console.log(sumRes(1)(2, 3)); // 6
console.log(sumRes(1)(2)(3)); // 6
console.log(sumRes(1, 2, 3)); // 6
Build-in curry
Some popular JavaScript libraries have a fairly advanced method of currying, for example _.curry
from the lodash
library. The method returns a wrapper that allows the function to be run both normally and partially and this is very convenient.
const sum = (x,y) => x + y;
let curryLodashSum = .curry(sum); // .curry from the lodash library
console.log(curryLodashSum(3, 7)); // normally
console.log(curryLodashSum(3)(7)); // partially
Partial application and currying
Partial application and currying are often confused. These methods are similar a little because of closure, but they have different concepts.
Partial application transforms a function into another one with smaller arity. Currying generates nested functions depending on the number of arguments in the function. Each function is given a parameter. So if there is no argument there is no currying.