Currying function, what is currying, why currying, and the implementation of advanced currying functions

Posted by eazyefolife on Tue, 08 Mar 2022 10:57:41 +0100

Currying

Currying It is a high-order technology about function. It is used not only in JavaScript, but also in other programming languages.

Coriolism is a function conversion, which refers to converting a function from callable f(a, b, c) to callable f(a)(b)(c).

Corellization does not call functions. It just converts functions.

Let's take a look at an example to better understand what we are talking about, and then carry out a practical application.

We will create an auxiliary function curry(f), which will curry the function f with two parameters. In other words, executing curry(f) on a two parameter function f(a, b) converts it into a function running in the form of f(a)(b):

function curry(f) { // curry(f) performs curry transformation
  return function(a) {
    return function(b) {
      return f(a, b);
    };
  };
}

// usage
function sum(a, b) {
  return a + b;
}

let curriedSum = curry(sum);

alert( curriedSum(1)(2) ); // 3

As you can see, the implementation is very simple: there are only two wrapper s.

  • The result of curry(func) is a wrapper function(a).
  • When it is called like curriedSum(1), its parameters are saved in the lexical environment, and then a new wrapper function(b) is returned.
  • The wrapper is then called with a parameter of 2, and it passes the call to the original sum function.

More advanced implementations of coritization, such as the lodash Library _.curry , a wrapper will be returned, which allows the function to be called normally or in the form of partial function:

function sum(a, b) {
  return a + b;
}

let curriedSum = _.curry(sum); // Use. From lodash library curry

alert( curriedSum(1, 2) ); // 3. It can still be called normally
alert( curriedSum(1)(2) ); // 3. Call by partial function

Corey? What is the purpose?

To understand its benefits, we need a practical example.

For example, we have a logging function log(date, importance, message) for formatting and outputting information. In actual projects, such functions have many useful functions, such as sending logs through the network. Here, we only use alert:

function log(date, importance, message) {
  alert(`[${date.getHours()}:${date.getMinutes()}] [${importance}] ${message}`);
}

Let's coriify it!

log = _.curry(log);

After coritization, the log still runs normally:

log(new Date(), "DEBUG", "some debug"); // log(a, b, c)

... but it can also be run in Coriolis form:

log(new Date())("DEBUG")("some debug"); // log(a)(b)(c)

Now we can easily create convenient functions for the current log:

// logNow will be the partial function of the log with a fixed first parameter
let logNow = log(new Date());

// Use it
logNow("INFO", "message"); // [HH:mm] INFO message

Now, logNow is a log with a fixed first parameter, in other words, a shorter "partially applied function" or "partial".

We can go further and provide convenient functions for the current debug log:

let debugNow = logNow("DEBUG");

debugNow("message"); // [HH:mm] DEBUG message

So:

  1. After coritization, we haven't lost anything: log can still be called normally.
  2. We can easily generate partial functions, such as those used to generate today's logs.

Advanced coritization implementation

If you want more details, here is an "advanced" Coriolis implementation for multi parameter functions, which we can also use in the above example.

It's very short:

function curry(func) {

  return function curried(...args) {
    if (args.length >= func.length) {
      return func.apply(this, args);
    } else {
      return function(...args2) {
        return curried.apply(this, args.concat(args2));
      }
    }
  };

}

Use case:

function sum(a, b, c) {
  return a + b + c;
}

let curriedSum = curry(sum);

alert( curriedSum(1, 2, 3) ); // 6. It can still be called normally
alert( curriedSum(1)(2,3) ); // 6. Coritization of the first parameter
alert( curriedSum(1)(2)(3) ); // 6. Full coritization

The new curry may seem a little complicated, but it's easy to understand.

The result of the curry(func) call is the wrapper curred as follows:

// func is the function to be converted
function curried(...args) {
  if (args.length >= func.length) { // (1)
    return func.apply(this, args);
  } else {
    return function(...args2) { // (2)
      return curried.apply(this, args.concat(args2));
    }
  }
};

When we run it, there are two if branches:

  1. If the length of the args passed in is the same as or longer than that defined by the original function (func.length), you only need to use func Apply passes the call to it.
  2. Otherwise, get a partial function: we haven't called func yet. Instead, return another wrapper pass, which will reapply curred, passing in the previously passed in parameters along with the new ones.

Then, if we call it again, we will get a new partial function (if there are not enough parameters), or the final result.

Only functions that determine parameter length are allowed

Coriolism requires the function to have a fixed number of parameters.

Functions that use rest parameters, such as f(...args), cannot be coriolised in this way.

A little more than Coriolis

By definition, Coriolis should convert sum(a, b, c) to sum(a)(b)(c).

However, as mentioned earlier, most curry implementations in JavaScript are advanced: they enable functions to be called by multi parameter variants.

summary

Coriolism is a transformation that converts f(a,b,c) into a form that can be called in the form of f(a)(b)(c). JavaScript implementations usually keep the function called normally, and return partial functions if the number of parameters is insufficient.

Coriolism makes it easier for us to obtain partial functions. As we can see in the logging example, after the ordinary function log(date, importance, message) is cored, when we call it and pass in a parameter (such as log(date)) or two parameters (log(date, importance)), it will return a partial function.

Topics: Javascript Front-end