The basic concept

Currying, also known as Partial Evaluation, is a technique of converting a function that takes multiple arguments into a function that takes a single argument (the first argument of the original function), and returning a new function that takes the remaining arguments and returns a result.

The core idea is to split the multi-parameter function into a single-parameter (or partial) function, and then return to call the next single-parameter (or partial) function, and process the remaining parameters in turn.

implementation

A simple example:

// The normal add function
function add(x, y) {
    return x + y
}

/ / after Currying
function curryingAdd(x) {
    return function (y) {
        return x + y
    }
}

add(1.2)           / / 3
curryingAdd(1) (2)   / / 3
Copy the code

You’re essentially changing the x and y arguments of add to a function that accepts x and then returns a function that handles y arguments. Now the idea should be clear: call a function with just a few arguments and let it return a function that handles the rest.

A generic implementation
function currying(fn, ... rest1) {
  return function(. rest2) {
    return fn.apply(null, rest1.concat(rest2))
  }
}
Copy the code

Note that concat accepts that a non-array element parameter will be passed as an element of the caller

Use it to currify a sayHello function:

function sayHello(name, age, fruit) {
  console.log(console.log(I call `${name}, I${age}I'm old and I like to eat${fruit}`))}const curryingShowMsg1 = currying(sayHello, 'Ming')
curryingShowMsg1(22.'apple')            // My name is Xiao Ming. I'm 22 years old. I like apples

const curryingShowMsg2 = currying(sayHello, 'small failure'.20)
curryingShowMsg2('watermelon')               // My name is Xiao Shuai. I'm 20 years old. I like watermelon
Copy the code
Higher order Coriolization functions

The above mentioned functions can be used for normal use, but if you want to use multiple levels of currying, you cannot nest a currying function that can only pass one or more arguments at a time.

function curryingHelper(fn, len) {
  const length = len || fn.length  Fn (fn); fn (fn); fn (fn)
  return function(. rest) {
    return rest.length >= length    // Check that sufficient parameters for fn are passed in
        ? fn.apply(this, rest)
        : curryingHelper(currying.apply(this, [fn].concat(rest)), length - rest.length)        // On the basis of generic currying functions}}function sayHello(name, age, fruit) { console.log(I call `${name}, I${age}I'm old and I like to eat${fruit}`)}const betterShowMsg = curryingHelper(sayHello)
betterShowMsg('small failure'.20.'watermelon')      // My name is Xiao Shuai. I'm 20 years old. I like watermelon
betterShowMsg('pig') (25.'pumpkin')      // My name is Piggy. I'm 25 years old. I like to eat pumpkin
betterShowMsg('Ming'.22) ('squash')      // My name is Xiao Ming. I'm 22 years old. I like eating Chinese pumpkins
betterShowMsg('small drag') (28) ('white gourd')      // My name is Xiao Ye. I'm 28 years old. I like to eat wax gourd
Copy the code
Crazy Currization function

Although currified functions are great, they require you to be careful about the order in which you define the function’s arguments. In some functional programming languages, a special “placeholder variable” is defined. An underscore is usually specified to do this, and if passed as an argument to a function, it indicates that this is “skipped” and is an argument to be specified. Such as:

var sendAjax = function (url, data, options) { / *... * / }
var sendPost = function (url, data) {                    // Sure
    return sendAjax(url, data, { type: "POST".contentType: "application/json"})}An underscore can also be used to specify unspecified arguments
var sendPost = sendAjax( _ , _ , { type: "POST".contentType: "application/json" })        
Copy the code

JS does not have native support for this. You can use a global placeholder variable const _ = {} and use === to determine if it is a placeholder, although you can use other symbols if you use Lodash. So you can modify the Kerrization function like this:

const _ = {}
function crazyCurryingHelper(fn, length, args, holes) {
  length = length || fn.length    // The first time is the number of parameters required by fn, then is
  args = args || []
  holes = holes || []
  
  return function(. rest) {
    let _args = args.slice(),
        _holes = holes.slice(),
        argLength = _args.length,        // Store the received length of args and holes
        holeLength = _holes.length,
        arg, i = 0
    for (; i < rest.length; i++) {
      arg = rest[i]
      if (arg === _ && holeLength) {
        holeLength--                      // Loop the position of _holes
        _holes.push(_holes.shift())      // _holes move from last to first
      } else if (arg === _) {
        _holes.push(argLength + i)          // Store _hole is the location of _
      } else if (holeLength) {              // Are there any unfilled holes
        holeLength--
        _args.splice(_holes.shift(), 0, arg)           // Insert the current parameter at the hole specified in the parameter list
      } else {
        _args.push(arg)            // Don't need to fill hole, directly add to the parameter list}}return _args.length >= length                          // Currize recursively
        ? fn.apply(this, _args)
        : crazyCurryingHelper.call(this, fn, length, _args, _holes)
  }
}

function sayHello(name, age, fruit) { console.log(I call `${name}, I${age}I'm old and I like to eat${fruit}`)}const betterShowMsg = crazyCurryingHelper(sayHello)
betterShowMsg(_, 20) ('small failure', _, 'watermelon')          // My name is Xiao Shuai. I'm 20 years old. I like watermelon
betterShowMsg(_, _, 'pumpkin') ('pig') (25)          // My name is Piggy. I'm 25 years old. I like to eat pumpkin
betterShowMsg('Ming') (_,22) (_, _,'squash')          // My name is Xiao Ming. I'm 22 years old. I like eating Chinese pumpkins
betterShowMsg('small drag') (28) ('white gourd')          // My name is Xiao Ye. I'm 28 years old. I like to eat wax gourd
Copy the code

Common use of currification

Parameters of reuse

Through the Currization method, the parameters are cached to the internal parameters of the closure, and then the cached parameters are combined with the incoming parameters inside the function and then applied /bind/call to the function for execution, so as to realize the reuse of parameters, reduce the scope of application and improve the applicability.

The getWife method eliminates the need to add additional legal wives…

var currying = function(fn) {
  var args = [].slice.call(arguments.1)      // Fn refers to the means by which officials digest their wives, and args refers to the legitimate wife
  return function(. rest) {
    varnewArgs = args.concat(... rest)// Existing wives and new wives are integrated into one, easy to control
    return fn.apply(null, newArgs)        // The wives use fn to digest and use it to complete the feat of Trinket's predecessors and return}}var getWife = currying(function() {
  console.log([...arguments].join('; '))          // Allwife is all wives, including those who sneak in
}, The legitimate Wife)

getWife('the wife 1'.'the wife 2'.'the wife 3')      // Legal wife; The wife 1; The wife 2; The wife 3
getWife('Beyond Trinket's wife')             // Legal wife; Beyond Trinket's wife
getWife('Super Wife')                    // Legal wife; Super wife
Copy the code
Improved applicability (early return?)

General-purpose functions solve compatibility problems, but they can also be used inconveniently. Different application scenarios require many parameters to be passed in order to solve specific problems. In some applications, the same rule may be used over and over again, which can lead to code repetition.

// Before currization
function square(i) { return i * i; }
function dubble(i) { return i * 2; }
function map(handler, list) { return list.map(handler); }

map(square, [1.2.3.4.5]);        // Square each term of the array
map(square, [6.7.8.9.10]);
map(dubble, [1.2.3.4.5]);        // Double each item in the array
map(dubble, [6.7.8.9.10]);
Copy the code

Using the same rule over and over again leads to code repetition, so you can modify it with the generic Currification implementation above:

// After currified
function square(i) { return i * i; }
function dubble(i) { return i * 2; }
function map(handler, ... list) { return list.map(handler); }

var mapSQ = currying(map, square);
mapSQ([1.2.3.4.5]);
mapSQ([6.7.8.9.10]);

var mapDB = currying(map, dubble);
mapDB([1.2.3.4.5]);
mapDB([6.7.8.9.10]);
Copy the code

You can see that the Use of the Currie method here is similar to that of partial functions, and by the way, the partial function ~

A partial function is a function that creates a call to another part (a function with prefabricated parameters or variables). The function can generate a function that actually executes from the parameters passed in. Such as:

const isType = function(type) {
  return function(obj) {
    return Object.prototype.toString.call(obj) === `[object ${type}] `}}const isString = isType('String')
const isFunction = isType('Function')
Copy the code

This uses partial functions to quickly create a set of methods for determining object types

A partial function fixes a part of a function and accepts the remaining arguments by passing in a new function as an argument or by returning a method. The number of arguments may be one or more. Corrification turns a function with n arguments into n functions with one argument. Add = (x, y, z) => x + y + z→curryAdd = x => y => z => x + y + z when the partial function takes one argument and returns a function that takes only one argument, The concepts are similar to those of two functions, curry()(), that take one argument. (I don’t know if it is correct)

Delay the

Another application of Cremation is deferred execution. Continuous currification, accumulating incoming parameters, and finally execution. For example, summing:

const curryAdd = function(. rest) {
  const _args = rest
  return function cb(. rest) {
    if (rest.length === 0) {
      return _args.reduce((sum, single) = > sum += single)
    } else{ _args.push(... rest)return cb
    }
  }
}()                        // To save the added number, a closure is returned
curryAdd(1)
curryAdd(2)
curryAdd(3)
curryAdd(4)
curryAdd()               // Finally compute output :10
Copy the code

A more general way to write this is to extract the handler function:

const curry = function(fn) {
  const _args = []
  return function cb(. rest) {
    if (rest.length === 0) {
      return fn.apply(this, _args) } _args.push(... rest)return cb
  }
}

const curryAdd = curry((. T) = > 
  T.reduce((sum, single) = > sum += single)
)
curryAdd(1)
curryAdd(2)
curryAdd(3)
curryAdd(4)
curryAdd()               // Finally compute output :10
Copy the code

The function.prototype. bind method is also a Coriolization application

Unlike call/apply, which executes directly, bind sets the first argument to the context in which the function executes, passes the other arguments to the calling method in turn (the body of the function itself is not executed, which can be viewed as delayed execution), and dynamically creates and returns a new function, which is currified.

var foo = {x: 888};
var bar = function () {
    console.log(this.x);
}.bind(foo);              / / binding
bar();                    / / 888
Copy the code

Here is a simulation of the bind function, where testBind creates and returns a new function that delays execution by binding the actual function to the context in which the argument is passed.

Function.prototype.testBind = function(scope) {
  return () = > this.apply(scope)
}
var foo = { x: 888 }
var bar = function() {
  console.log(this.x)
}.testBind(foo)              / / binding
bar()                    / / 888
Copy the code

The performance of the curry

  • accessargumentsObjects are generally slower than accessing named parameters
  • Some older versions of browsers are availablearguments.lengthIs quite slow to implement
  • useFn. Apply (...).Fn. Call (...).Usually more than direct callsFn (...).A little bit slowly
  • Creating lots of nested scopes and closures can be costly, both in memory and speed

In most applications, the main performance bottleneck is the DOM node manipulation, the performance loss of JS is basically negligible, so Curry can be directly trusted to use.

A classic interview question

// Implement an add method that satisfies the following expectations:
add(1) (2) (3) = 6;
add(1.2.3) (4) = 10;
add(1) (2) (3) (4) (5) = 15;

function add() {
    // On the first execution, an array is defined to store all the parameters
    var _args = Array.prototype.slice.call(arguments);

    // Internally declare a function that uses closure properties to hold _args and collect all parameter values
    var _adder = function() { _args.push(... arguments);return _adder;
    };

    // Take advantage of the toString implicit conversion feature to implicitly convert when finally executed and calculate the final value returned
    _adder.toString = function () {
        return _args.reduce(function (a, b) {
            return a + b;
        });
    }
    return _adder;
}

add(1) (2) (3)                / / 6
add(1.2.3) (4)             / / 10
add(1) (2) (3) (4) (5)          / / 15
add(2.6) (1)                / / 9
Copy the code

The article reprinted