Front End Engine: From bind to curry

Posted by jim.davidson on Mon, 22 Jul 2019 05:34:47 +0200

Preface

It has not been updated for a long time. On the one hand, I am busy with my work, on the other hand, I am in the process of learning. Next, it should gradually return to a stable state of renewal, sharing some interesting points of knowledge and my personal thinking. Interested friends can pay attention to it!

If there is something wrong, please correct it. I will update it in time. Thank you.

Blog Address 🍹🍰 fe-code

bind

The bind() method creates a new function. When the bind() is called, this of the new function is specified by the first parameter of the bind, and the rest of the parameters are used as parameters of the new function. —— MDN

The bind method, which is usually used to bind this, must be familiar to everyone. It returns a new function. But there is another point that we tend to overlook: the rest of the parameters will be used as parameters of the new function for invocation.

Chestnut:

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

const addBind = add.bind(null, 1);
addBind(2); // 3

Obviously, the parameters passed in during the bind call are passed in together with the new parameters when the addBind call is made.

Polyfill

So how does this work? Let's take a brief look at the Polyfill version of bind. There are many similar implementations on the network.

Function.prototype.mybind = function(context, ...args) {
    const f = this;
    const fpro = function() {};
    const fBound = function(..._arg) {
        // Fpro. prototype. isPrototype Of (this) determines whether it is the fBound of the new call
        return f.apply(fpro.prototype.isPrototypeOf(this)
                ? this
                : context,
                [...args, ..._arg]);
    };
    if (this.prototype) {
        fpro.prototype = this.prototype;
    }
    fBound.prototype = new fpro();
    return fBound;
};

As you can see, what bind actually returns is a function similar to fBound. Let's simplify it.

// Delete part of the code
// f 
// args
const fBound = function(..._arg) {
    return f.apply(null, [...args, ..._arg]);
};

In fact, the closure is used to save the parameters above, and finally pass them to the objective function for use. In this way, without considering this and using bind, we can directly change the add function to this:

function add(a) {
    return function (b) {
        return a + b;
    }
}
add(1)(2); // 2

Are you familiar with a very common interview question? That's what we're going to talk about.

curry

In computer science, Currying, also translated as Carrier or Calcification, is the technique of transforming 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. —— Wikipedia

curry is actually a very important idea in functional programming. Through simple local calls, functions can be pre-loaded and boilerplate code can be reduced.

Because we have such requirements as add(1)(2), but each manual implementation is cumbersome, we can use a special curry function to help us meet the requirements (in some libraries like lodash, interested students can see the source code).

Simple implementation

function curry(fn) {
    const len = fn.length;
    return function bindfn() {
        if(arguments.length < len) {
            return bindfn.bind(null, ...arguments); 
            // Key: Save the parameters and pass in bindfn with the following parameters at call time
        } else {
            return fn.call(null, ...arguments);
        }
    }
}

This implementation takes advantage of the bind feature we mentioned above, so we can do this when we want to implement the add requirement:

// longhand
function add(a, b) {
    return a + b
}
const curryAdd = curry(add);
curryAdd(1)(2); // 3

Other versions

How do we implement a curry without bind?

function _curry(fn) {
    const len = fn.length;
    function warp (..._arguments) {
        let _arg = _arguments;
        // Allow all parameters to be passed at once, although this adds (1, 2) as curry does;
        if (_arg.length >= len) {
            return fn(..._arg);
        }
        function fnc(...args) {
            _arg = [..._arg, ...args];
            if (_arg.length < len) { // Insufficient parameters to continue returning functions
                return fnc;
            } else { // Enough parameters to execute the original function
                return fn(..._arg);
            }
        }
        return fnc;
    }
    return warp;
}

But the demand is always changeable. In the above two schemes, we use the number of parameters and parameters to determine whether the function needs to be executed, but what if this demand?

add(1)(2)() // 3, add(1)(2)(3)() // 6, the parameters are uncertain and the function will be executed only if () is called manually. It's easy to think of the need to change the judgment conditions returned by the judgment function.

function _curry1(fn) {
    function warp (..._arguments) {
        let _arg = _arguments;
        function fnc(...args) {
            _arg = [..._arg, ...args];
            if (args.length > 0) { // Function return is determined by determining whether the current parameter is passed in
                return fnc;
            } else {
                return fn(..._arg);
            }
        }
        return fnc;
    }
    return warp;
}

Of course, the add function itself has to be adjusted to meet the need for any parameter accumulation.

function addInfinity(...args) {
    return args.reduce((pre, cur) => pre + cur);
}

application

Of course, it's not for the purpose of this article. And what else can add(1)(2) do besides being able to handle interviews? What we really need is to apply it to business.

  • request

I believe everyone has written or read similar code.

function request(url, params) {
    return new Promise((resolve, reject) => {
        setTimeout(() => resolve(url), 1000);
    })
}
//
function getData(params) {
    return request('/api/getData', params);
}
function setData(params) {
    return request('/api/setData', params);
}
//...

This is very suitable for curry to do processing, reduce boilerplate code.

const getData = _curry(request, '/api/getData'); // Default entry
const setData = _curry(request, '/api/setData');

You may find that curry functions written previously do not support this approach. Yes, but just a little bit of it. I won't do it here. You can try it on your own.

  • map

The map functions of arrays have been used by all of us. Let's take a brief look at a scenario.

[1,2,3].map(x => 2 * x);

What if you want this to be a universal function? It's easy to think of that.

function map(fn, arr) {
    return arr.map(fn);
}
function multiply2(x) {
    return 2 * x;
}
// map(multiply2, arr);

It looks good, simple and easy to use. Let's take a look at curry instead.

const mapMultiply2 = curry(map, multiply2);
// mapMultiply2(arr);

As you can see, curry is more conducive to extracting functions, combining functions, let's focus more on multiply2.

summary

At this point, the article is over. I believe you also have some understanding of curry, for example, through local calls, so that functions have preloading capabilities and reduce the template code. Of course, there are still a lot of things about Curitization, which I'm going to talk about here is quite simple, and I'll have a chance to share with you in the future.

Reference Articles

  • JS Functional Programming Guide

Communication Group

qq front-end communication group: 960807765, welcome all kinds of technical exchanges, look forward to your joining;

Wechat Group: Students in need can add my friends (q1324210213), I will pull you into the group.

Postscript

If you see here and this article is of some help to you, I hope you can do something to support the author. Thank you. If there are any mistakes in the article, you are also welcome to point out that we should encourage each other. Okay, it's time for you again. Thank you for reading. See you next time!

Interested students can pay attention to the front-end engine of my public number, which is interesting and interesting.

Topics: Javascript REST Programming network Vue