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
- When checking the same type of data, we write the same regular many times.
- 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
- Coritization highlights an important idea: reduce the scope of application and improve the applicability
- Three functions and characteristics of coritization: parameter reuse, early return and delayed execution
- 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