JavaScript Currying

Original author: Chidume Nnamdi
  • Transfer
Functional programming is a style of program development in which some specific features for working with functions are widely used. It is, in particular, the transfer of functions to other functions as arguments and the return of functions from other functions. The concept of “pure functions” also belongs to the functional programming style. The output of pure functions depends only on the input, they, when executed, do not affect the state of the program.

Principles of functional programming supports many languages. Among them are JavaScript, Haskell, Clojure, Erlang. The use of functional programming mechanisms implies knowledge, among other things, of such concepts as pure functions, currying functions, higher order functions.



The material, the translation of which we are publishing today, is dedicated to currying. We will talk about how currying works, and how knowledge of this mechanism can be useful to a JS developer.

What is currying?


Currying or currying (currying) in functional programming is the conversion of a function with multiple arguments to a set of nested functions with one argument. When you call a curried function with one argument passed to it, it returns a new function that is waiting for the next argument to arrive. New functions that are waiting for the next argument are returned each time the curried function is called — until the function receives all the arguments it needs. The arguments obtained earlier, due to the closure mechanism, are waiting for the moment when the function gets everything it needs to perform the calculations. After receiving the last argument, the function performs the calculations and returns the result.

Speaking of curryingIt can be said that this is the process of turning a function with several arguments into a function with less arity.

Arity is the number of function arguments. For example - here is the declaration of a pair of functions:

functionfn(a, b) {
    //...
}
function_fn(a, b, c) {
    //...
}

The function fntakes two arguments (it is a binary or 2-ary function), the function _fntakes three arguments (a ternary, 3-ary function).

Let's talk about the situation when, during currying, a function with several arguments is converted into a set of functions, each of which takes one argument.

Consider an example. We have the following function:

functionmultiply(a, b, c) {
    return a * b * c;
}

It takes three arguments and returns their product:

multiply(1,2,3); // 6

Now we will think about how to convert it to a set of functions, each of which takes one argument. Create a curried version of this function and look at how to get the same result during a call to several functions:

functionmultiply(a) {
    return(b) => {
        return(c) => {
            return a * b * c
        }
    }
}
log(multiply(1)(2)(3)) // 6

As you can see, here we 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 a new construction, each function, except the last one, which returns the result of the calculations, takes an argument and returns another function, which is also able to accept the argument and return another function. If the construction of the form multiply(1)(2)(3)does not seem to be very clear to you, let’s look at it in this form in order to better understand this:

const mul1 = multiply(1);
const mul2 = mul1(2);
const result = mul2(3);
log(result); // 6

Now line by line we analyze what is happening here.

First we pass the 1function argument multiply:

const mul1 = multiply(1);

When this function works, the following construction works:

return(b) => {
        return(c) => {
            return a * b * c
        }
    }

Now mul1there is a link to the function that takes an argument b. Call the function mul1, passing it to 2:

const mul2 = mul1(2);

As a result of this call, the following code will be executed:

return (c) => {
            return a * b * c
        }

The constant mul2will contain a reference to a function that might be in it, for example, as a result of the following operation:

mul2 = (c) => {
            return a * b * c
        }

If we now call the function mul2, passing it 3, then the function will perform the necessary calculations using the arguments aand b:

const result = mul2(3);

The result of these calculations will be 6:

log(result); // 6

The function mul2with the highest level of nesting has access to areas of visibility, to the closures formed by the functions multiplyand mul1. That is why in the function mul2it is possible to perform calculations with variables declared in functions that have already been completed, which have already returned some values ​​and are processed by the garbage collector.

Above we have considered an abstract example, but, in essence, the same function that is intended for calculating the volume of a rectangular parallelepiped.

functionvolume(l,w,h){
    return l * w * h;
}
const vol = volume(100,20,90) // 180000

Here is what its curried version looks like:

functionvolume(l) {
    return(w) => {
        return(h) => {
            return l * w * h
        }
    }
}
const vol = volume(100)(20)(90) // 180000

So, currying is based on the following idea: on the basis of a certain function, they create another function that returns a specialized function.

Currying and partial application of functions


Now, perhaps, there is a feeling that the number of nested functions, when presenting a function as a set of nested functions, depends on the number of function arguments. And, if we are talking about currying, then it is.

A special variant of the function for calculating the volume, which we have already seen, can be done like this:

functionvolume(l) {
    return(w, h) => {
        return l * w * h
    }
}

Here ideas are applied that are very similar to those discussed above. You can use this feature as follows:

const hV = volume(70);
hV(203,142);
hV(220,122);
hV(120,123);

And it is possible and so:

volume(70)(90,30);
volume(70)(390,320);
volume(70)(940,340);

In fact, here you can see how we, by team volume(70), created a specialized function for calculating the volume of bodies, one of whose dimensions (namely, length, l), is fixed. The function volumeexpects 3 arguments and contains 2 nested functions, in contrast to the previous version of this function, the curried version of which contained 3 nested functions.

The function that came out after the call volume(70)implements the concept of partial function application. Currying and partial application of functions are very similar to each other, but the concepts are different.

When partially applied, the function is converted to another function with a smaller number of arguments (less arity). Some arguments of such a function are fixed (default values ​​are set for them).

For example, there is such a function:

functionacidityRatio(x, y, z) {
    return performOp(x,y,z)
}

It can be converted to this:

functionacidityRatio(x) {
    return(y,z) => {
        return performOp(x,y,z)
    }
}

The implementation of the function performOp()is not given here, since it does not affect the concepts under consideration.

The function that can be obtained by calling a new function acidityRatio()with an argument whose value is to be fixed is the original function, one of whose arguments is fixed, and this function itself takes one less argument than the original one.

The curried version of the function will look like this:

functionacidityRatio(x) {
    return (y) = > {
        return (z) = > {
            return performOp(x,y,z)
        }
    }
}

As you can see, during currying, the number of nested functions is equal to the number of arguments of the original function. Each of these functions expects its own argument. It is clear that if the function does not take arguments, or takes only one argument, then it cannot be curried.

In a situation where the function has two arguments, the results of its currying and partial application can be said to be the same. For example, we have the following function:

functiondiv(x,y) {
    return x/y;
}

Suppose we need to rewrite it so that, by fixing the first argument, we obtain a function that performs the calculations when passing only the second argument to it, that is, we need to partially apply this function. It will look like this:

functiondiv(x) {
    return(y) => {
        return x/y;
    }
}

The result of currying it will look the same.

On the practical application of the concepts of currying and partial application of functions


Currying and partial application of functions may be useful in various situations. For example - when developing small modules suitable for reuse.

Partial application of functions facilitates the use of universal modules. For example, we have an online store, in the code of which there is a function, which is used to calculate the amount payable taking into account the discount.

functiondiscount(price, discount) {
    return price * discount
}

There is a certain category of customers, let's call them "favorite customers", which we give a discount of 10%. For example, if such a customer buys something for $ 500, we give him a $ 50 discount:

const price = discount(500,0.10); // $50 
// $500 - $50 = $450

It is easy to notice that with this approach, we constantly have to call this function with two arguments:

const price = discount(1500,0.10); // $150
// $1,500 - $150 = $1,350
const price = discount(2000,0.10); // $200
// $2,000 - $200 = $1,800
const price = discount(50,0.10); // $5
// $50 - $5 = $45
const price = discount(5000,0.10); // $500
// $5,000 - $500 = $4,500
const price = discount(300,0.10); // $30
// $300 - $30 = $270

The original function can be brought to such a form that would allow to receive new functions with a predetermined level of discounts, when called, it is enough for them to transfer the purchase amount. The function discount()in our example has two arguments. Here is what we will transform it into:

functiondiscount(discount) {
    return(price) => {
        return price * discount;
    }
}
const tenPercentDiscount = discount(0.1);

A function tenPercentDiscount()is the result of a partial application of a function discount(). When calling tenPercentDiscount()this function, it is enough to transfer the price, and a discount of 10%, that is, the argument discount, will already be set:

tenPercentDiscount(500); // $50
// $500 - $50 = $450

If there are customers in our store who have decided to give a discount of 20%, then you can get the appropriate function to work with them like this:

const twentyPercentDiscount = discount(0.2);

Now the function twentyPercentDiscount()can be called to calculate the cost of goods with a discount of 20%:

twentyPercentDiscount(500); // 100
// $500 - $100 = $400
twentyPercentDiscount(5000); // 1000
// $5,000 - $1,000 = $4,000
twentyPercentDiscount(1000000); // 200000
// $1,000,000 - $200,000 = $600,000

Universal function for partial use of other functions.


We develop a function that accepts any function and returns its version, which is a function, some of the arguments of which are already set. Here is the code that allows you to do this (you, if you set yourself a goal to develop a similar function, it is quite possible that you will get something else as a result):

functionpartial(fn, ...args){
    return (..._arg) => {
        return fn(...args, ..._arg);
    }
}

The function partial()accepts the function fnthat we want to convert to a partially applied function, and a variable number of parameters (...args). The operator is restused to put all the parameters going after fn, in args.

This function returns another function that also accepts a variable number of parameters ( _arg). This function, in turn, calls the original function fn, pass it the parameters ...argsand ..._arg(using the operator spread). The function performs the calculations and returns the result.

Let's use this function to create a variant of a function already familiar to you volume, designed to calculate the volume of rectangular parallelepipeds, one of the sides of which is fixed:

functionvolume(l,h,w){
    return l * h * w
}
const hV = partial(volume,100);
hV(200,900); // 18000000
hV(70,60); // 420000

Here you can find an example of a universal function for currying other functions.

Results


In this article we talked about currying and partial application of functions. These methods of function conversion are implemented in JavaScript due to the closures and because the functions in JS are first class objects (they can be passed as arguments to other functions, returned from them, assigned to variables).

Dear readers! Do you use techniques of currying and partial application of functions in your projects?


Also popular now: