Function coerce of JavaScript

Quotation

Recently, when reading the technology blog in the community, I came across several words of function currification, and also asked to write js function currification. I thought what kind of advanced thing is currification? Never heard of it?

Then we set out with problems, went to study specially, and made some arrangement.

What is functional currification?

What is functional currification? Let's see how Wikipedia explains:

In computer science, curring, also translated as karenization or garization, 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 technique was named after the logician Haskell Gary by Christopher strech, although it was invented by Moses Sch ö nfinkel and gotlob Frege.

Intuitively, currierization claims that "if you fix certain parameters, you will get a function that accepts the remaining parameters.". So for the function y^x with two variables, if y=2 is fixed, then we get the function 2^x with one variable.

The concept of Currying is not complicated. In a simple way, it is only passed to a part of the function parameters to call it, and let it return a function to process the remaining parameters.

If the text explanation is still a little abstract, we will use the add function to make a simple implementation of function currification.

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

// After the add ition function is curried
var curryingAdd = function(x) {
  return function(y) {
    return x + y;
  };
};

// Function reuse
var increment = curryingAdd(1);
var addTen = curryingAdd(10);

increment(2);
// 3
addTen(2);
// 12

In fact, the X and Y parameters of the add function are changed to receive x with a function and then return a function to process y parameters. Now the idea should be clear, that is, only a part of the parameters passed to the function to call it, and let it return a function to process the remaining parameters.

Why do we need function currification?

After reading the above currification of the add function, the question is, what's the use of packing a layer with such great effort?

I. parameter reuse

In fact, the first currification example of add function just mentioned the convenience of function reuse brought by function currification. We have realized the increment function and addTen function quickly through add function currification. Let's take another example:

// Normal regular verification string reg.test(txt)

// After function encapsulation
function check(reg, txt) {
    return reg.test(txt)
}

check(/\d+/g, 'test')       //false
check(/[a-z]+/g, 'test')    //true

// After Currying
function curryingCheck(reg) {
    return function(txt) {
        return reg.test(txt)
    }
}

var hasNumber = curryingCheck(/\d+/g)
var hasLetter = curryingCheck(/[a-z]+/g)

hasNumber('test1')      // true
hasNumber('testtest')   // false
hasLetter('21212')      // false

The above example is a regular check. Normally, it's OK to call the check function directly. But if I have many places to check whether there is a number, I need to reuse the first parameter reg, so that other places can directly call functions such as hasNumber and hasLetter, so that the parameters can be reused and called more easily.

II. Advance confirmation
var on = function(element, event, handler) {
    if (document.addEventListener) {
        if (element && event && handler) {
            element.addEventListener(event, handler, false);
        }
    } else {
        if (element && event && handler) {
            element.attachEvent('on' + event, handler);
        }
    }
}

var on = (function() {
    if (document.addEventListener) {
        return function(element, event, handler) {
            if (element && event && handler) {
                element.addEventListener(event, handler, false);
            }
        };
    } else {
        return function(element, event, handler) {
            if (element && event && handler) {
                element.attachEvent('on' + event, handler);
            }
        };
    }
})();

Another way of writing might be easier to understand. The above is to determine the isSupport parameter first

var on = function(isSupport, element, event, handler) {
    isSupport = isSupport || document.addEventListener;
    if (isSupport) {
        return element.addEventListener(event, handler, false);
    } else {
        return element.attachEvent('on' + event, handler);
    }
}

In the process of project construction, it can be said that it is common to encapsulate some dom operations. However, the first writing method above is also quite common. But let's look at the second writing method, which is relative to the first one, which is self executing and then returning a new function. In fact, it is to determine which method will be taken before, so as to avoid judging every time.

III. delay calculation / operation
Function.prototype.bind = function (context) {
    var _this = this
    var args = Array.prototype.slice.call(arguments, 1)

    return function() {
        return _this.apply(context, args)
    }
}

Like bind, which is often used in js, the implementation mechanism is Currying

How to realize function currification?

General packaging method:

// Preliminary packaging
var currying = function(fn) {
    // args gets all the parameters in the first method
    var args = Array.prototype.slice.call(arguments, 1)
    return function() {
        // Merge all parameters in the following methods with args
        var newArgs = args.concat(Array.prototype.slice.call(arguments))
        // Take the merged parameter as the parameter of fn through apply and execute
        return fn.apply(this, newArgs)
    }
}

Here is the initial encapsulation. The initial parameters are saved through the closure, and then the remaining arguments are obtained for splicing. Finally, the functions that need to be currying are executed.

However, the above functions still have some defects. In fact, only one more parameter can be extended when returning. In the case of currying(a)(b)(c), it seems that it doesn't support (multi parameter calls are not supported). In general, it's thought of using recursion to encapsulate another layer.

// Support multi parameter transmission
function progressCurrying(fn, args) {

    var _this = this
    var len = fn.length;
    var args = args || [];

    return function() {
        var _args = Array.prototype.slice.call(arguments);
        Array.prototype.push.apply(args, _args);

        // If the number of parameters is less than the original fn.length, it is called recursively to continue collecting parameters
        if (_args.length < len) {
            return progressCurrying.call(_this, fn, _args);
        }

        // After parameter collection, execute fn
        return fn.apply(this, _args);
    }
}

In fact, on the preliminary basis, with recursive calls, as long as the number of parameters is less than the original fn.length, the recursion will continue.

What's the performance of function currification?

As for the performance of Currying, we should know the following points:

  • Accessing arguments objects is usually a little slower than accessing named arguments
  • Some older browsers are quite slow to implement arguments.length
  • Use fn.apply( )And fn.call( )It is usually better than calling fn( )Slow down a bit
  • Creating a large number of nested scopes and closure functions can be costly, both in memory and at speed

In fact, in most applications, the main performance bottleneck is to operate DOM nodes. The performance loss of js can be ignored, so curry can be used directly and safely.

Corellization interview questions

// An add method is implemented to make the calculation result meet the following expectations:
add(1)(2)(3) = 6;
add(1, 2, 3)(4) = 10;
add(1)(2)(3)(4)(5) = 15;

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

    // Declare a function internally, save ﹐ args and collect all parameter values with the characteristics of closure
    var _adder = function() {
        _args.push(...arguments);
        return _adder;
    };

    // Using the property of toString implicit conversion, when the last execution, implicit conversion is performed, and the final value return is calculated
    _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

summary

By simply passing a few parameters, practical new functions can be created dynamically, and an additional benefit is that mathematical function definitions are preserved, although there are more than one parameter.

Currying function is very handy to use. It's just a pleasure for me to use it every day. It can be called a necessary tool at hand, which can make functional programming less tedious and tedious.

Reference resources:

Recommended reading:
[Topic: the way to advanced JavaScript]
ES6 Promise
Deep understanding closure of JavaScript
ES6 tail call and tail recursion
Summary of Git common commands
On MVC and MVVM model

I am Cloudy, a young front-end siege lion. I love research, technology and sharing.
Personal notes are not easy to organize. Thank you for your attention, reading, praise and collection.
If you have any questions, you are welcome to point out, and also welcome to exchange front-end issues!

Keywords: Javascript less Programming git

Added by aldernon on Tue, 19 Nov 2019 13:29:27 +0200