Interviewer: what is function coritization? Can you do it by hand?

Posted by Stoker on Tue, 02 Nov 2021 05:48:45 +0100

catalogue

1 what is functional coriolism

In computer science, Currying is a technology 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 Curry.

what do you mean? In short, coriolism is a technique used to transform multi parameter functions. For example:

// This is a function that accepts three parameters
const add = function(x, y, z) {
  return x + y + z
}

By transforming it, we can get such a function:

// Receive a single parameter
const curryingAdd = function(x) {
  // And returns the function that accepts the remaining parameters
  return function(y, z) {
    return x + y + z
  }
}

What's the difference? Comparison from call:

// Call add
add(1, 2, 3)

// Call curryingAdd
curryingAdd(1)(2, 3)
// See more clearly, equivalent to the following
const fn = curryingAdd(1)
fn(2, 3)

It can be seen that the transformed function can accept parameters in batches. Remember this first, and we will talk about its usefulness below. Even fn (the function returned by curryingAdd) can continue to transform:

const curryingAdd = function(x) {
  return function(y) {
    return function(z) {
      return x + y + z
    }
  }
}
// call
curryingAdd(1)(2)(3)
// Namely
const fn = curryingAdd(1)
const fn1 = fn(2)
fn1(3)

The above two transformation processes are function coritization.

Simply put, a multi parameter function f is transformed into a function g that accepts some parameters, and this function g will return a function h, which is used to accept other parameters. Function h can continue to be coriolised. It's a process of dolls ~

So what's the use of trying so hard to corrilize the function?

2. Functions and characteristics of coritization

2.1 parameter reuse

Requirements encountered in work: check whether the telephone number, mailbox and ID card are legal through regular verification, etc

So we will encapsulate a verification function as follows:

/**
 * @description Pass regular check string
 * @param {RegExp} regExp Regular object
 * @param {String} str String to be verified
 * @return {Boolean} Whether it passes the verification
 */
function checkByRegExp(regExp, str) {
    return regExp.test(str)
}

If we want to verify many mobile phone numbers and mailboxes, we will call:

// Verify mobile phone number
checkByRegExp(/^1\d{10}$/, '15152525634'); 
checkByRegExp(/^1\d{10}$/, '13456574566'); 
checkByRegExp(/^1\d{10}$/, '18123787385'); 
// Check mailbox
checkByRegExp(/^(\w)+(\.\w+)*@(\w)+((\.\w+)+)$/, 'fsds@163.com'); 
checkByRegExp(/^(\w)+(\.\w+)*@(\w)+((\.\w+)+)$/, 'fdsf@qq.com'); 
checkByRegExp(/^(\w)+(\.\w+)*@(\w)+((\.\w+)+)$/, 'fjks@qq.com');

There seems to be no problem. In fact, there is room for improvement

  1. When checking the same type of data, we write the same regular many times.
  2. The code is not readable. Without comments, we can't see the role of regularity at a glance

We try to use function coritization to improve:

// Curry function
function checkByRegExp(regExp) {
    return function(str) {
        return regExp.test(str)
    }
}

So we pass in different regular objects to get functions with different functions:

// Verify mobile phone
const checkPhone = curryingCheckByRegExp(/^1\d{10}$/)
// Check mailbox
const checkEmail = curryingCheckByRegExp(/^(\w)+(\.\w+)*@(\w)+((\.\w+)+)$/)

Now the code for checking the mobile phone and mailbox is simple, and the readability is enhanced

// Verify mobile phone number
checkPhone('15152525634'); 
checkPhone('13456574566'); 
checkPhone('18123787385'); 
// Check mailbox
checkEmail('fsds@163.com'); 
checkEmail('fdsf@qq.com'); 
checkEmail('fjks@qq.com');

This is parameter reuse: we only need to reuse the first parameter regExp to directly call functions with specific functions

Common functions (such as checkByRegExp) solve the compatibility problem, but it will also bring inconvenience. For example, different application scenarios need to pass multiple different parameters to solve the problem

Sometimes the same rule may be used repeatedly (for example, verifying the parameters of the mobile phone), which leads to code duplication. Using coritization can eliminate the duplication and achieve the purpose of reusing parameters.

An important idea of coriolism: reduce the scope of application and improve the applicability

2.2 early return

In the JS DOM event listener, we use the addEventListener method to add event handlers for elements, but some browser versions do not support this method. We will use the attachEvent method instead.

At this time, we will write a code compatible with each browser version:

/**
 * @description: 
 * @param {object} element DOM Element object
 * @param {string} type Event type
 * @param {Function} fn Event handler
 * @param {boolean} isCapture Capture
 * @return {void}
 */
function addEvent(element, type, fn, isCapture) {
    if (window.addEventListener) {
        element.addEventListener(type, fn, isCapture)
    } else if (window.attachEvent) {
        element.attachEvent("on" + type, fn)
    }
}

We use addEvent to add event listening, but each time we call this method, we will make a judgment. In fact, after the browser version is determined, it is not necessary to make repeated judgment.

Coriolis treatment:

function curryingAddEvent() {
    if (window.addEventListener) {
        return function(element, type, fn, isCapture) {
            element.addEventListener(type, fn, isCapture)
        }
    } else if (window.attachEvent) {
        return function(element, type, fn) {
            element.attachEvent("on" + type, fn)
        }
    }
}
const addEvent = curryingAddEvent()

// You can also merge the above code with an immediate execution function
const addEvent = (function curryingAddEvent() {
 	...
})()

Now the addEvent we get is a function obtained after judgement, so we don't need to repeat it later.

This is called return in advance or confirmation in advance. After function coritization, you can process some tasks in advance and return a function to process other tasks

In addition, we can see that curryingAddEvent does not seem to accept parameters. This is because the conditions of the original function (that is, whether the browser version supports addEventListener) are obtained directly from the global. Logically, it can be changed to:

let mode = window.addEventListener ? 0 : 1;
function addEvent(mode, element, type, fn, isCapture) {
  if (mode === 0) {
    element.addEventListener(type, fn, isCapture);
  } else if (mode === 1) {
    element.attachEvent("on" + type, fn);
  }
}
// In this way, after coritization, you can accept a parameter first
function curryingAddEvent(mode) {
    if (mode === 0) {
        return function(element, type, fn, isCapture) {
            element.addEventListener(type, fn, isCapture)
        }
    } else if (mode === 1) {
        return function(element, type, fn) {
            element.attachEvent("on" + type, fn)
        }
    }
}

Of course, there is no need to change it like this ~

2.3 delayed execution

In fact, the above example of regular checksum event listening has reflected delayed execution.

After the curryingCheckByRegExp function is called, the checkPhone and checkEmail functions are returned

addEvent function returned after curringAddEvent function call

The returned functions are not executed immediately, but wait for the call.

3. Encapsulating general Coriolis tool functions

The corrilization of the function above is to manually modify the original function, changing add to curryingAdd, checkByRegExp to curryingCheckByRegExp, and addEvent to curryingAddEvent.

Do we have to manually modify the underlying function every time we curry the function? Of course not

We can encapsulate a general Coriolis tool function (interview handwritten code)

/**
 * @description: A tool function that corrilizes a function
 * @param {Function} fn Functions to be coriolised
 * @param {array} args List of parameters received
 * @return {Function}
 */
const currying = function(fn, ...args) {
    // fn number of parameters required
    const len = fn.length
    // Returns a function that receives the remaining parameters
    return function (...params) {
        // Splice the received and newly received parameter list
        let _args = [...args, ...params]
        // If the number of parameters received is not enough, continue to return a new function to receive the remaining parameters
        if (_args.length < len) {
            return currying.call(this, fn, ..._args)
        }
      	// After all parameters are received, call the original function
        return fn.apply(this, _args)
    }
}

This Coriolis tool function is used to receive some parameters, then return a new function, wait to receive the remaining parameters, recurse until all the required parameters are received, and then call the original function through apply.

Now we basically don't need to modify the original function manually to corrilize the function

// Directly use the tool function to return the function to verify the mobile phone and mailbox
const checkPhone = currying(checkByRegExp(/^1\d{10}$/))
const checkEmail = currying(checkByRegExp(/^(\w)+(\.\w+)*@(\w)+((\.\w+)+)$/))

However, in the above example of event monitoring, you can't use this tool function for currying. As mentioned earlier, because its conditions are directly obtained from the global, it is special. If you pass in conditions from the outside, you can use the tool function for currying. Of course, there is no need to directly modify the original function, which is more direct and readable

4 Summary and supplement

  1. Coritization highlights an important idea: reduce the scope of application and improve the applicability
  2. Three functions and characteristics of coritization: parameter reuse, early return and delayed execution
  3. Coriolism is a typical application of closures. Closures are used to form a scope saved in memory, and some received parameters are saved in this scope for subsequent use. And return a new function to receive the remaining parameters

Topics: Javascript Front-end