The basic concept

In computer science, Currying, also translated as carryization or callization, 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 above is baidu Encyclopedia and Wikipedia on the definition of physics and chemistry, pure from the literal above is very difficult to understand. Simply put, Currying is a way of dealing with functions of many variables. It produces a series of chained functions, each of which fixes some arguments and returns a new function for the function of returning the remaining arguments.

Below we through some examples, disassemble and explain the specific meaning of coriolism.

This is a normal ternary function that performs an operation to get the sum of three arguments.

function add(a,b,c){
    return a + b + c
}

add(1.2.3) / / 6
Copy the code

Add(a,b,c) => curriedAdd(a)(b)(c).

The implementation code is as follows:

function curriedAdd(a){
    return function(b){
        return function(c){
            return a + b + c
        }
    }
}
Copy the code

Perform validation

add(1.2.3) / / 6
curriedAdd(1) (2) (3) / / 6
Copy the code

The above is an implementation based on a minimalist scenario. The complete curriedAdd function should satisfy the following call scenario:

  1. Initially pass in a parameter curriedAdd(1)(2,3) or curriedAdd(1)(2)(3)

    • If the first curriedAdd is passed in only one argument, a function currying1 that takes the remaining two arguments is returned
    • If you continue currying1 with two arguments, the result is a+b+c; However, if you continue with currying1 and pass in an argument, a function currying2 that takes the third argument is returned
    • If curryring2 continues with an argument, the result a+b+ C is returned
  2. We’re going to start with two parameters, curriedAdd(1,2)(3)

    • If the first curriedAdd is executed and two arguments are passed in, a function curryring1 that takes the last argument is returned
    • If curryring1 continues with an argument, the result a+b+ C is returned
  3. We’re going to start with three parameters, curriedAdd(1,2,3)

    • If the first curriedAdd is passed three parameters, the execution result a+b+ C is returned

Convert the above logical process of disassembly analysis into a function:

function curriedAdd() {
    let args = [].slice.call(arguments)
    if (args.length >= 3) {
        return args[0] + args[1] + args[2]}else if (args.length === 2) {
        return function () {
            return args[0] + args[1] + arguments[0]}}else if (args.length === 1) {
        return function c() {
            let args1 = [].slice.call(arguments)
            if (args1.length >= 2) {
                return args[0] + args1[0] + args1[0]}else if(args1.length === 1) {
                return function () {
                    return args[0] + args1[0] + arguments[0]}}else{
                return c
            }
        }
    } else {
        return curriedAdd
    }
}
Copy the code

Perform validation


console.log(curriedAdd(1)) // [Function (anonymous)]
console.log(curriedAdd(1) (2)) // [Function (anonymous)]
console.log(curriedAdd(1) (2) (3)) / / 6

console.log(curriedAdd(1.2)) // [Function (anonymous)]
console.log(curriedAdd(1.2) (3)) / / 6

console.log(curriedAdd(1.2.3)) / / 6

Copy the code

Verify the running results of the example:

[Function (anonymous)]
[Function (anonymous)]
6
[Function (anonymous)]
6
6
Copy the code

In short, keratology is the process of converting a function of many variables into a series of fewer functions.

Realize the principle of

It is relatively simple to implement a binary or ternary kerochemical function, like the curriedAdd function above. The hard part is to implement a universal kerochemical function for any meta function. Based on the above analysis, the following conditions are required to realize a general kerochemical function: 1. First, a function FN is required as a parameter; Second, we can obtain the number of virtual parameters when fn is declared, which can be achieved through the fn.length property. 3. Finally, you can determine whether to return a new function that takes the remaining arguments, or to return fn(… Parameters), as well as caching the fixed parameters. This is done through fn.length, closures, and recursion.

function currying(fn) {
    return function curried() {
        var args = [].slice.call(arguments),
            context = this

        return args.length >= fn.length ?
            fn.apply(context, args) :
            function () {
                var rest = [].slice.call(arguments)
                return curried.apply(context, args.concat(rest))
            }
    }
}
Copy the code

Create the curriedAdd function above using currying, and perform verification to get the same result.

var curriedAdd = currying(add)

console.log(curriedAdd(1)) // [Function (anonymous)]
console.log(curriedAdd(1) (2)) // [Function (anonymous)]
console.log(curriedAdd(1) (2) (3)) / / 6

console.log(curriedAdd(1.2)) // [Function (anonymous)]
console.log(curriedAdd(1.2) (3)) / / 6

console.log(curriedAdd(1.2.3)) / / 6

Copy the code

In addition, there is another point that needs to be added. It is meaningless to perform an keratological transformation on a function whose parameters are not fixed. For example, the following sort function sorts an indefinite number of numbers. The number of arguments in a function declaration is uncertain. The number of virtual arguments obtained by sort.length is 0, and any number of arguments passed to curriedSort will be executed immediately.

function sort(){
    return [].slice.call(arguments).sort(function(a,b){
        return a-b
    })
}

sort(1.3.6.2) / / [5]

var curriedSort = currying(sort)
var currying1 = curriedSort(1.3.6.2) / / [5]

currying1(5) // TypeError: curriedSort(...) is not a function
Copy the code

Although the length attribute cannot be used to obtain the length of the uncertain parameter, it can be used to replace sort.length by specifying the length of the target parameter at the same time of the offset conversion. The following is a modification of currying for functions with variable parameters.

function currying(fn, len) {
    return function curried() {
        var args = [].slice.call(arguments),
            context = this
        var _len = fn.length || len

        return args.length >= _len ?
            fn.apply(context, args) :
            function () {
                var rest = [].slice.call(arguments)
                return curried.apply(context, args.concat(rest))
            }
    }
}

var curriedSort = currying(sort,5)
var currying1 = curriedSort(1.3.6.2) // [Function (anonymous)]

currying1(5) / /,2,3,5,6 [1]

Copy the code

The application practice

  • Solve the problem of repeated parameter passing and improve the applicability of the function

Currying is very common and widely used. For example, we send double 11 campaign emails in bulk, which we usually do

function sendEmail(from, content, to){
    console.log(`The ${from} send email to ${to}, content is ${content}`)
}

sendEmail('xx company'.'Double 11 discount 50% off'.'[email protected]')
sendEmail('xx company'.'Double 11 discount 50% off'.'[email protected]')
sendEmail('xx company'.'Double 11 offers get 60% off'.'[email protected]')
sendEmail('xx company'.'Double 11 offers get 60% off'.'[email protected]')

// ...
Copy the code

The sender of an email is fixed, the content of the email is relatively fixed, the only difference is the recipient of the email. This is exactly the way that rying can fix some parameters and return a new function that takes the rest. Keshi creates two temporary and more applicable functions, sendEmailToS5 and sendEmailToS6, to send the specified type of mail to the target group.

var sendEmailContent = currying(sendEmail)('xx company')
var sendEmailToS5 = sendEmailContent('Double 11 discount 50% off')
var sendEmailToS5 = sendEmailContent('Double 11 offers get 60% off')

// 50% off group
sendEmailToS5('[email protected]')
sendEmailToS5('[email protected]')

// ...

// Group with 60% off
sendEmailToS6('[email protected]')
sendEmailToS6('[email protected]')

// ...

Copy the code

Therefore, currying can solve the problem of repeated arguments and improve the applicability of functions.

  • Reduce the function parameter dimension, suitable for application

In general, when creating utility functions, we try to make them more abstract to improve their generality. However, the disadvantages of doing so are also obvious, which will reduce its applicability. For example, let’s create a function getObjKeys(obj,keys) that gets the object’s target properties.

function getObjKeys(keys, obj){
    var o = {}
    keys.forEach(function(k){
        o[k] = obj[k]
    })
    return o
}

var person = {
    name:'zhangsan'.age: 20.work: 'worker'.tel: '13699887766'
}

getObjKeys(['name'.'tel'], person) // {name:'zhangsan',tel:'13699887766'}
Copy the code

Suppose, in another scenario, we need to query the workshop, so the name, age and phone number of the worker. We can do that

workers.map(function(worker){
    return getObjKeys(worker,['name'.'age'.'tel'])})Copy the code

In addition, using keratology, we can fix the getObjKeys function keys argument and get a function that takes another argument obj. This function can be used directly as a callback to the map function.

var callback = currying(getObjKeys)(['name'.'age'.'tel'])
workers.map(callback) // [{name,age,tel},...]

Copy the code

The resources

Baike.baidu.com/item/%E6%9F… Github.com/shfshanyue/… Juejin. Cn/post / 684490… Developer.mozilla.org/zh-CN/docs/… www.yuque.com/webqiang/qn… Github.com/mqyqingfeng…