define

Wikipedia defines Currying as:

In mathematics and computer science, currying is the technique of translating the evaluation of a function that takes multiple arguments (or a tuple of arguments) into evaluating a sequence of functions, each with a single argument.

Translated into Chinese:

In mathematics and computer science, Currization is the technique of converting a function that takes multiple arguments into a series of functions that take one argument.

Here’s an example:

function add(a, b) { return a + b; // add(1, 2); // add(1, 2); addCurry(1)(2) // 3Copy the code

use

We’re going to talk about how to write this Curry function, and we’re going to make it very powerful, but before we write it, we need to know what does it really do?

Here’s an example:

Function ajax(type, url, data) {var XHR = new XMLHttpRequest(); xhr.open(type, url, true); xhr.send(data); } // Although ajax is a very generic function, Ajax ('POST', 'www.test.com', "name= Kevin ") Ajax ('POST', 'www.test2.com', "name= Kevin ") 'www.test3.com', 'name= Kevin') // curry var ajaxCurry = curry; Var POST = ajaxCurry('POST'); post('www.test.com', "name=kevin"); Var postFromTest = POST ('www.test.com'); postFromTest("name=kevin");Copy the code

Think about how jQuery has common methods like.ajax, but it also has common methods like.ajax, but it also has syntactic sugar like.get and $.post. (OF course, I have no research on whether jQuery does this at the bottom.)

This use of Curry can be understood as parameter reuse. In essence, it is to reduce generality and improve applicability.

But even so, does it still feel useless?

If we just pass in the parameters one by one, it might not make much sense, but what if we pass the Currified function to some other function like map?

Here’s an example:

For example, we have the following data:

var person = [{name: 'kevin'}, {name: 'daisy'}]
Copy the code

If we wanted to get all the name values, we could do this:

var name = person.map(function (item) {
    return item.name;
})
Copy the code

But if we have the Curry function:

var prop = curry(function (key, obj) {
    return obj[key]
});

var name = person.map(prop('name'))
Copy the code

We also need to write a prop function to get the name property. Isn’t that more trouble?

But note that the prop function can be used many times after being written once. In fact, the code has been reduced from three lines to one line.

Person.map (prop(‘name’)) just tells you: The Person object iterates (map) to get the (prop) name property.

Does it feel a little interesting?

The first edition

We’ll see more applications of Curryification in the future, but that’s for the future. Now it’s time to write the Curry function.

A common implementation of the Curry function is:

Function (fn) {var args = [].slice.call(arguments, 1); return function() { var newArgs = args.concat([].slice.call(arguments)); return fn.apply(this, newArgs); }; };Copy the code

We can use it like this:

function add(a, b) { return a + b; } var addCurry = curry(add, 1, 2); Var addCurry = curry(add, 1); AddCurry (2) // 3 // var addCurry = curry(add); addCurry(1, 2) // 3Copy the code

It’s already a little curryish, but it’s not quite there yet, but we can use this function as a helper to help us write the real Curry function.

The second edition

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); }}; }Copy the code

Let’s verify this function:

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"]
Copy the code

This has worked as expected, but the implementation of the Curry function is pretty hard to understand…

To give you a better understanding of the curry function, let me write a very simple version of the code:

function sub_curry(fn){
    return function(){
        return fn()
    }
}

function curry(fn, length){
    length = length || 4;
    return function(){
        if (length > 1) {
            return curry(sub_curry(fn), --length)
        }
        else {
            return fn()
        }
    }
}

var fn0 = function(){
    console.log(1)
}

var fn1 = curry(fn0)

fn1()()()() // 1
Copy the code

So let’s start by understanding the Curry function.

When fn1() is executed, the function returns:

Curry (function(){return fn0()})Copy the code

When fn1()() is executed, the function returns:

Curry (sub_curry(function(){return fn0()})) // curry(function(){ return fn0() })Copy the code

When fn1()()() is executed, the function returns:

Curry (function(){return fn0()})Copy the code

When fn1()()()() ()(), fn() is false:

Function (){return fn0()})() {return fn0()})(Copy the code

To get back to the real Curry function, let’s use the following example:

var fn0 = function(a, b, c, d) {
    return [a, b, c, d];
}

var fn1 = curry(fn0);

fn1("a", "b")("c")("d")
Copy the code

Fn1 (“a”, “b”) :

Fn1 (" a ", "b") / / equivalent to curry (fn0) (" a ", "b") / / equivalent to curry (sub_curry (fn0, "a", "b")) / / is equivalent to / / attention to... Curry (function(...)) { return fn0("a", "b", ...) })Copy the code

When fn1(“a”, “b”)(“c”) is executed, the function returns:

curry(sub_curry(function(...) { return fn0("a", "b", ...) }), "c") // equivalent to curry(function(...) { return (function(...) {return fn0("a", "b", ...) })("c")}) { return fn0("a", "b", "c", ...) })Copy the code

Fn1 (“a”, “b”)(“c”)(“d”); fn(arguments) < length false;

(function(...) { return fn0("a", "b", "c", ...) })("d") // equivalent to fn0("a", "b", "c", "d")Copy the code

The function completes.

So, in fact, the whole code is easy to understand:

Sub_curry wraps the original function with a function and passes the arguments to the original function. When fn0(… (…). Then call sub_curry to wrap the original function again. Then mix the new arguments with the old ones and pass them to the original function until the number of arguments reaches the desired number.

If you want to understand how the Curry function works, write it down and try to analyze how it works.

A more understandable implementation

Of course, if you still don’t understand, you can use the following implementation to achieve the same effect:

function curry(fn, args) {
    var length = fn.length;

    args = args || [];

    return function() {

        var _args = args.slice(0),

            arg, i;

        for (i = 0; i < arguments.length; i++) {

            arg = arguments[i];

            _args.push(arg);

        }
        if (_args.length < length) {
            return curry.call(this, fn, _args);
        }
        else {
            return fn.apply(this, _args);
        }
    }
}

var fn = curry(function(a, b, c) {
    console.log([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"]
Copy the code

And maybe you think this is a better way to understand it, but it does the same thing, so why don’t you just do it this way?

Because I want to introduce various implementation methods, we can not just because it is difficult to understand not to introduce to you

The third edition

The curry function is as good as it gets here, but notice that the function must pass parameters from left to right, in order of the parameters. What if I don’t want to pass parameters in that order?

We can create a placeholder like this:

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

fn("a", _, "c")("b") // ["a", "b", "c"]
Copy the code

Let’s go straight to version 3:

Function curry(fn, args, holes) {length = fn. Length; args = args || []; holes = holes || []; return function() { var _args = args.slice(0), _holes = holes.slice(0), argsLen = args.length, holesLen = holes.length, arg, i, index = 0; for (i = 0; i < arguments.length; i++) { arg = arguments[i]; // fn(1, _, _, 4)(_, 3) If (arg === _ && holesLen) {index++ if (index > holesLen) {_args.push(arg); _holes. Push (argsLen -1 + index - holesLen)}} else if (arg === _) {_args. Push (arg); _holes.push(argsLen + i); } else if (holesLen) {fn(_, 2)(_, 3) if (index >= holesLen) {_args.push(arg); } // fn(_, 2)(1) replace placeholder else {_args. Splice (_holes[index], 1, arg); _holes.splice(index, 1) } } else { _args.push(arg); } } if (_holes.length || _args.length < length) { return curry.call(this, fn, _args, _holes); } else { return fn.apply(this, _args); } } } var _ = {}; var fn = curry(function(a, b, c, d, e) { console.log([a, b, c, d, e]); }); // all outputs are [1, 2, 3, 4, 5] fn(1, 2, 3, 4, 5); fn(_, 2, 3, 4, 5)(1); fn(1, _, 3, 4, 5)(2); fn(1, _, 3)(_, 4)(2)(5); fn(1, _, _, 4)(_, 3)(2)(5); fn(_, 2)(_, _, 4)(1)(3)(5)Copy the code

From Ali’s big man github.com/mqyqingfeng…

What happens when you manipulate a primitive array in a WEBGL JS loop? React ShoudComponentUpdate React ShoudComponentUpdate Stop rendering vue why don’t you use 11 methods for deep copy shallow copy Vue vue $nextTick Deep parsing Deep Revealing Promise microtask registration and execution Process How does the V8 engine execute a section of JS code? Http3.0 2.0 1.0 compare macro tasks and micro tasks in detail