JavaScript function coritization

  javascript, node.js

What is Corellian?

Official statement

In computer science,Corellian(English:Currying), and translated intoKarenizationOrGallicization, is a technique that transforms a function that accepts multiple parameters into a function that accepts a single parameter (the first parameter of the original function), and returns a new function that accepts the remaining parameters and returns the result. This technology consists ofChristopher stracheyAs a logicianhaskell brooks curryNamed, although it isMoses SchönfinkelAndGottlob Frege invented it.

Intuitively, Corellian claimedIf you fix some parameters, you will get a function that accepts the remaining parameters..
In theoretical computer science, Corellian provides a simple theoretical model, such as one that accepts only a single parameterlambdaIn calculus, the method of studying functions with multiple parameters is studied.
The duality of the function coritization isUncurrying, a method of implementing multi-parameter functions using anonymous single-parameter functions.

Convenient understanding

The concept of Currying is actually very simple, passing only a part of the function’s parameters to call it, allowing it to return a function to process the remaining parameters.

If we need to implement a function that sums three numbers:

function add(x, y, z) {
  return x + y + z;
}
console.log(add(1, 2, 3)); // 6
var add = function(x) {
  return function(y) {
    return function(z) {
      return x + y + z;
    }
  }
}

var addOne = add(1);
var addOneAndTwo = addOne(2);
var addOneAndTwoAndThree = addOneAndTwo(3);

console.log(addOneAndTwoAndThree);

Here we define aaddFunction that takes a parameter and returns a new function. calladdAfter that, the returned function is remembered by closureaddThe first parameter of the. It is a bit tedious to call it once, but fortunately we can use a specialcurryHelp function (helper function) makes it easier to define and call such functions.

UseES6The arrow function, we can put the aboveaddThis is achieved as follows:

const add = x => y => z => x + y + z;

It seems much clearer to use the arrow function.

Partial function?

Let’s look at this function:

function ajax(url, data, callback) {
  // ..
}

There is a scenario in which we need to initiate for multiple different interfacesHTTPThere are two ways to request:

  • On callajax()Function, the global is passed inURLConstant.
  • Create an already presetURLA function reference to an argument.

Let’s create a new function that is still initiated internallyajax()Request, in addition, while waiting to receive the other two arguments, we manuallyajax()The first argument is set to what you care aboutAPIAddress.

For the first approach, we may generate the following call methods:

function ajaxTest1(data, callback) {
  ajax('http://www.test.com/test1', data, callback);
}

function ajaxTest2(data, callback) {
  ajax('http://www.test.com/test2', data, callback);
}

For these two similar functions, we can also extract the following patterns:

function beginTest(callback) {
  ajaxTest1({
    data: GLOBAL_TEST_1,
  }, callback);
}

I’m sure you’ve seen a pattern where we call functions in the field (function call-site), the argument is applied (apply) in formal parameters. As you can see, we applied only part of the arguments at the beginning-specifically, we applied the arguments toURLFormal parameters-the remaining arguments will be applied later.

The above concept isPartial functionThe definition of partial function is a process of reducing the number of function parameters. The number of parameters here refers to the number of formal parameters that you want to pass in. We passedajaxTest1()Put the original functionajax()The number of parameters for is from3This number has been reduced to2A.

We define apartial()Functions:

function partial(fn, ...presetArgs) {
  return function partiallyApplied(...laterArgs) {
    return fn(...presetArgs, ...laterArgs);
  }
}

partial()Function receptionfnParameter to represent the arguments that we are biased to apply (partially apply) function. And then,fnAfter the formal parameters,presetArgsThe array collects the arguments passed in later and saves them for later use.

We created andreturnA new internal function was created (for clarity, we named itpartiallyApplied(..)In this function,laterArgsThe array collected all the arguments.

The arrow function is more concise:

var partial =
  (fn, ...presetArgs) =>
    (...laterArgs) =>
      fn(...presetArgs, ...laterArgs);

Using this pattern of partial functions, we refactored the previous code:

function ajax(url, data, callback) {
  // ..
}

var ajaxTest1 = partial(ajax, 'http://www.test.com/test1');
var ajaxTest2 = partial(ajax, 'http://www.test.com/test1');

Think againbeginTest()Function, we use thepartial()How should we reconstruct it?

function ajax(url, data, callback) {
  // ..
}

// 版本1
var beginTest = partial(ajax, 'http://www.test.com/test1', {
  data: GLOBAL_TEST_1,
});

// 版本2
var ajaxTest1 = partial(ajax, 'http://www.test.com/test1');
var beginTest = partial(ajaxTest1, {
  data: GLOBAL_TEST_1,
});

Pass one at a time

I believe you have seen the advantages of version 2 over version 1 in the above examples. Yes, Corelliation is the process of converting a function with multiple parameters into one function at a time. Each time a function is called, it accepts only one parameter and returns a function until all parameters are passed.

The process of converting a function that takes multiple arguments into a function that takes them one at a time.

Each time the function is called it only accepts one argument and returns a function that takes one argument until all arguments are passed.

Let’s say we’ve already created a Corellian version of theajax()FunctioncurriedAjax()

curriedAjax('http://www.test.com/test1')
  ({
    data: GLOBAL_TEST_1,
  })
  (function callback(data) {
    // dosomething
  });

We will separate the three calls, which may help us understand the whole process:

var ajaxTest1 = curriedAjax('http://www.test.com/test1');

var beginTest = ajaxTest1({
  data: GLOBAL_TEST_1,
});

var ajaxCallback = beginTest(function callback(data) {
  // dosomething
});

The realization of Corellization

So, how do we implement an automatic Coriolis function?

var currying = function(fn) {
  var args = [];

  return function() {
    if (arguments.length === 0) {
      return fn.apply(this, args); // 没传参数时,调用这个函数
    } else {
      [].push.apply(args, arguments); // 传入了参数,把参数保存下来
      return arguments.callee; // 返回这个函数的引用
    }
  }
}

Call the abovecurrying()Functions:

var cost = (function() {
  var money = 0;
  return function() {
    for (var i = 0; i < arguments.length; i++) {
      money += arguments[i];
    }
    return money;
  }
})();

var cost = currying(cost);

cost(100); // 传入了参数,不真正求值
cost(200); // 传入了参数,不真正求值
cost(300); // 传入了参数,不真正求值

console.log(cost()); // 求值并且输出600

The above function is my previous oneJavaScript Design Patterns and Development Practice Closures and Higher Order Functions of Reading NotesWritten bycurryingVersion, now after careful consideration, we find that there are still some problems.

When we use kriging, we should pay attention to the case of parameters that are pre-passed for functions at the same time.

Therefore, change the above Coriolis function as follows:

var currying = function(fn) {
  var args = Array.prototype.slice.call(arguments, 1);

  return function() {
    if (arguments.length === 0) {
      return fn.apply(this, args); // 没传参数时,调用这个函数
    } else {
      [].push.apply(args, arguments); // 传入了参数,把参数保存下来
      return arguments.callee; // 返回这个函数的引用
    }
  }
}

Use examples:

var cost = (function() {
  var money = 0;
  return function() {
    for (var i = 0; i < arguments.length; i++) {
      money += arguments[i];
    }
    return money;
  }
})();

var cost = currying(cost, 100);
cost(200); // 传入了参数,不真正求值
cost(300); // 传入了参数,不真正求值

console.log(cost()); // 求值并且输出600

You may feel that you have to call without parameters at the end of each call.cost()Function is more troublesome, and in thecost()Functions to be usedargumentsThe parameters do not meet your expectations. We know that all functions have onelengthProperty, indicating the number of parameters the function expects to accept. Therefore, we can make full use of this characteristic of pre-transmitted parameters.

Learn frommqyqingfeng

function sub_curry(fn) {
  var args = [].slice.call(arguments, 1);
  return function() {
    return fn.apply(this, args.concat([].slice.call(arguments)));
  };
}

function curry(fn, length) {

  length = length || fn.length;

  var slice = Array.prototype.slice;

  return function() {
    if (arguments.length < length) {
      var combined = [fn].concat(slice.call(arguments));
      return curry(sub_curry.apply(this, combined), length - arguments.length);
    } else {
      return fn.apply(this, arguments);
    }
  };
}

In the above function, in currying’s return function, each time we putarguments.lengthAndfn.lengthFor comparison, oncearguments.lengthReachedfn.lengthThe number of, we will go to callfn(return fn.apply(this, arguments);)

Verification:

var fn = curry(function(a, b, c) {
  return [a, b, c];
});

fn("a", "b", "c") // ["a", "b", "c"]
fn("a", "b")("c") // ["a", "b", "c"]
fn("a")("b")("c") // ["a", "b", "c"]
fn("a")("b", "c") // ["a", "b", "c"]

Implementation of bind method

The use of coriolis can be easily borrowed.call()Or ..apply()Realizationbind()Method ofpolyfill.

Function.prototype.bind = Function.prototype.bind || function(context) {
  var me = this;
  var args = Array.prototype.slice.call(arguments, 1);
  return function() {
    var innerArgs = Array.prototype.slice.call(arguments);
    var finalArgs = args.concat(innerArgs);
    return me.apply(contenxt, finalArgs);
  }
}

Some of the problems with the above functions are that they are not compatible with constructors. We judge whether this function passes or not by judging the prototype attribute of the object pointed to by thisnewCalled as a constructor to make the abovebindMethods are compatible with constructors.

Function.prototype.bind() by MDNAs follows:

The binding function is suitable for using the new operator new to construct a new instance created by the objective function. When a binding function is used to construct a value, the originally provided this is ignored. However, those parameters originally provided will still be prepended to the constructor call.

This isJavaScript Web rich application development based on MVCThebind()Method implementation:

Function.prototype.bind = function(oThis) {
  if (typeof this !== "function") {
    throw new TypeError("Function.prototype.bind - what is trying to be bound is not callable");
  }

  var aArgs = Array.prototype.slice.call(arguments, 1),
    fToBind = this,
    fNOP = function() {},
    fBound = function() {
      return fToBind.apply(
        this instanceof fNOP && oThis ? this : oThis || window,
        aArgs.concat(Array.prototype.slice.call(arguments))
      );
    };

  fNOP.prototype = this.prototype;
  fBound.prototype = new fNOP();

  return fBound;
};

Uncurring

You may encounter a situation where you get a corialized function and want it to be a corialized version of the previous one, which is essentially a similar one.f(1)(2)(3)The function of returns to similarg(1,2,3)The function of.

The following is simpleuncurryingThe implementation of:

function uncurrying(fn) {
  return function(...args) {
    var ret = fn;

    for (let i = 0; i < args.length; i++) {
      ret = ret(args[i]); // 反复调用currying版本的函数
    }

    return ret; // 返回结果
  };
}

Note, don’t think that the functions after uncurrying are exactly the same as the functions before currying, they just behave similarly!

var currying = function(fn) {
  var args = Array.prototype.slice.call(arguments, 1);

  return function() {
    if (arguments.length === 0) {
      return fn.apply(this, args); // 没传参数时,调用这个函数
    } else {
      [].push.apply(args, arguments); // 传入了参数,把参数保存下来
      return arguments.callee; // 返回这个函数的引用
    }
  }
}

function uncurrying(fn) {
  return function(...args) {
    var ret = fn;

    for (let i = 0; i < args.length; i++) {
      ret = ret(args[i]); // 反复调用currying版本的函数
    }

    return ret; // 返回结果
  };
}

var cost = (function() {
  var money = 0;
  return function() {
    for (var i = 0; i < arguments.length; i++) {
      money += arguments[i];
    }
    return money;
  }
})();

var curryingCost = currying(cost);
var uncurryingCost = uncurrying(curryingCost);
console.log(uncurryingCost(100, 200, 300)()); // 600

What’s the use of coriolis or partial functions?

Whether it’s coriolis or partial application, we can pass values partially, while traditional function calls need to determine all arguments in advance. If you get only part of the arguments in one part of the code and then determine another part of the arguments in another part of the code, then corialization and partial application can come in handy.

Another thing that best embodies the Coriolis application is that when functions have only one formal parameter, we can easily combine them (Single responsibility principle)。 Therefore, if a function finally needs three arguments, it will become a function that needs three calls after it is coritized, and each call needs one argument. When we combine functions, this unit function form will make our processing simpler.

Summarized, mainly for the following common three purposes:

  • Delay calculation
  • Parameter reuse
  • Dynamically generated function