JavaScript Function Currying

JavaScript Function Currying

1 What is function currying?

In computer science, Currying is a technique for transforming a function that accepts multiple arguments into a function that accepts a single argument (the first argument of the original function) and returns a new function that accepts the remaining arguments and returns the result. The technique is named after logician Haskell Curry .

What's the meaning? Simply put, currying is a technique used to transform functions with multiple parameters.

for example:

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


If we transform it, we can get a function like this:

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


What difference does this make? Compare from the call:

// Call add
add(1, 2, 3)
 
// Call curryingAdd
curryingAdd(1)(2, 3)
// Let's see it more clearly. This is equivalent to const fn = curryingAdd(1)
fn(2, 3)

As you can see, the transformed function can accept parameters in batches. Keep this in mind first, as its usefulness will be discussed below. Even fn (the function returned curryingAdd ) can be further transformed

as follows:

const curryingAdd = function(x) {
  return function(y) {
    return function(z) {
      return x + y + z
    }
  }
}
// Call curryingAdd(1)(2)(3)
// i.e. const fn = curryingAdd(1)
const fn1 = fn(2)
fn1(3)

The above two transformation processes are function currying.

Simply put, it transforms a multi-parameter function f into a function g that accepts some parameters, and this function g returns a function h , which is used to accept other parameters. Function h can be further curried. It's a nesting doll process.

So what's the point of going to all that trouble to curry a function?

2 The role and characteristics of currying

2.1 Parameter reuse

Requirements encountered at work: Check the legality of phone numbers, email addresses, ID cards, etc. through regular expressions

So we will encapsulate a verification function as follows:

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

If we want to verify many mobile phone numbers and email addresses, we will call it like this:

// Check the phone number checkByRegExp(/^1\d{10}$/, '15152525634'); 
checkByRegExp(/^1\d{10}$/, '13456574566'); 
checkByRegExp(/^1\d{10}$/, '18123787385'); 
// Check email checkByRegExp(/^(\w)+(\.\w+)*@(\w)+((\.\w+)+)$/, '[email protected]'); 
checkByRegExp(/^(\w)+(\.\w+)*@(\w)+((\.\w+)+)$/, '[email protected]'); 
checkByRegExp(/^(\w)+(\.\w+)*@(\w)+((\.\w+)+)$/, '[email protected]');

There seems to be no problem, but there is still room for improvement

  • When validating the same type of data, we write the same regular expression many times.
  • The code is not very readable. Without comments, we cannot immediately see the role of regular expressions.

Let's try to improve this using function currying:

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


So if we pass in different regular objects, we can get functions with different functions:

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


Now the code for verifying mobile phones and email addresses is simpler and more readable.

// Check phone number checkPhone('15152525634'); 
checkPhone('13456574566'); 
checkPhone('18123787385'); 
// Check email checkEmail('[email protected]'); 
checkEmail('[email protected]'); 
checkEmail('[email protected]');

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

Universal functions (such as checkByRegExp ) solve compatibility issues, but they also bring inconveniences. For example, different application scenarios require passing multiple different parameters to solve the problem.

Sometimes the same rule may be used repeatedly (such as verifying the parameters of a mobile phone), which causes code duplication. Currying can eliminate duplication and achieve the purpose of reusing parameters.

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

2.2 Early Return

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

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

/**
 * @description: 
 * @param {object} element DOM element object* @param {string} type event type* @param {Function} fn event processing function* @param {boolean} isCapture whether to 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 listeners, but each time this method is called, a judgment is made. In fact, after the browser version is determined, there is no need to make repeated judgments.

Currying:

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 use the immediate execution function to merge the above code const addEvent = (function curryingAddEvent() {
  ...
})()

Now addEvent we get is a function obtained after judgment, and there is no need to repeat the judgment in future calls.

This is early return or early confirmation. After currying, the function can process some tasks in advance and return a function to process other tasks.

In addition, we can see curryingAddEvent does not seem to accept any parameters. This is because the condition of the original function (i.e. whether the browser version supports addEventListener ) is 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);
  }
}
// This way, after currying, 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 this.

2.3 Delayed Execution

In fact, delayed execution has already been reflected in the above regular validation and event listening examples.

The curryingCheckByRegExp function returns checkPhone and checkEmail functions after calling

addEvent function is returned after the curringAddEvent function is called

The returned function will not be executed immediately, but will wait for the call.

3 Encapsulating general currying utility functions#

In the above, we manually modified the original functions for currying, changing add curryingAdd , checkByRegExp to curryingCheckByRegExp , and addEvent to curryingAddEvent .

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

We can encapsulate a general currying utility function (handwritten code for the interview)

/**
 * @description: A tool function for currying functions* @param {Function} fn The function to be curried* @param {array} args The list of arguments already received* @return {Function}
 */
const currying = function(fn, ...args) {
    // The number of parameters required by fn const len ​​= fn.length
    // Return a function to receive the remaining parameters return function (...params) {
        // Concatenate the received and newly received parameter lists 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 receiving all the parameters, call the original function return fn.apply(this, _args)
    }
}

This currying utility function is used to receive some parameters, then return a new function to wait for the remaining parameters, recursively until all the required parameters are received, and then call the original function through apply .

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

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


However, the event monitoring example above cannot be curried using this utility function. The reason is as mentioned above. Because its conditions are directly obtained from the global context, it is quite special. If we change it to pass the conditions in from the outside, we can use the utility function currying. Of course, this is not necessary. It is more direct and readable to modify the original function directly.

4 Summary and Supplement

  • Currying highlights an important idea: reducing the scope of application and improving applicability
  • Three functions and characteristics of currying: parameter reuse, early return, and delayed execution
  • Currying is a typical application of closures, which uses closures to form a scope stored in memory, and saves some of the received parameters in this scope for subsequent use. And return a new function to receive the remaining parameters

This is the end of this article about JavaScript function currying. For more information about function currying, please search 123WORDPRESS.COM’s previous articles or continue to browse the following related articles. I hope you will support 123WORDPRESS.COM in the future!

You may also be interested in:
  • In-depth explanation of currying of JS functions
  • A brief analysis of JavaScript function currying
  • Analysis of the methods and examples of the use of js function currying
  • JavaScript Function Currying Explained
  • JavaScript Function Currying Explained

<<:  Some basic instructions of docker

>>:  Implementation of MySQL's MVCC multi-version concurrency control

Recommend

HTML head tag detailed introduction

There are many tags and elements in the HTML head ...

Quickly solve the problem that the mysql57 service suddenly disappeared

one, G:\MySQL\MySQL Server 5.7\bin> mysqld --i...

How to solve the error of connecting to the database when ServerManager starts

Servermanager startup connection database error R...

Implementation of Nginx operation response header information

Prerequisite: You need to compile the ngx_http_he...

How to use wangEditor in vue and how to get focus by echoing data

Rich text editors are often used when doing backg...

Detailed explanation of built-in methods of javascript array

Table of contents 1. Array.at() 2. Array.copyWith...

HTML table tag tutorial (24): horizontal alignment attribute of the row ALIGN

In the horizontal direction, you can set the row ...

Parsing Apache Avro Data in One Article

Abstract: This article will demonstrate how to se...

How to use tcpdump to capture packets in Linux system

Let me look at the example code first: 1. Common ...

MySQL Series 8 MySQL Server Variables

Tutorial Series MySQL series: Basic concepts of M...

Detailed instructions for installing mysql5.7 database under centos7.2

The mysql on the server is installed with version...