Home > Article > Web Front-end > How to implement automatic currying in JS
I will give you a detailed analysis of the exquisite automatic currying method in JS and analyze the process and principles through code examples. Please refer to it for reference.
The following content analyzes the sophisticated automatic currying implementation method in JS through code explanations and examples, and analyzes the basic usage and knowledge of the currying function. Let’s learn about it.
What is currying?
In computer science, currying is to transform a function that accepts multiple parameters into a function that accepts a single parameter (the first parameter of the original function), and returns the remaining parameters. The technique of a new function that takes arguments and returns a result. This technique was named by Christopher Strachey after logician Haskell Curry, although it was invented by Moses Schnfinkel and Gottlob Frege.
The theory seems overwhelming? It doesn’t matter, let’s take a look at the code first:
Curriization application
Suppose we need to implement a function that performs certain processing on list elements. For example, if you add one to each element in the list, it is easy to think:
const list = [0, 1, 2, 3]; list.map(elem => elem + 1);
It’s very simple, right? What if we want to add 2 more?
const list = [0, 1, 2, 3]; list.map(elem => elem + 1); list.map(elem => elem + 2);
It seems that the efficiency is a bit low. Can the processing function be encapsulated?
But the callback function of map only accepts the current element elem as a parameter. It seems that there is no way to encapsulate it...
You may think: If you can get a partially configured function Just fine, for example:
// plus返回部分配置好的函数 const plus1 = plus(1); const plus2 = plus(2); plus1(5); // => 6 plus2(7); // => 9
Pass a function like this into map:
const list = [0, 1, 2, 3]; list.map(plus1); // => [1, 2, 3, 4] list.map(plus2); // => [2, 3, 4, 5]
Isn’t it great? In this way, no matter how much you add, you only need list.map(plus(x)), which perfectly implements encapsulation and greatly improves readability!
But here comes the question: How to implement such a plus function?
This is where currying comes in handy:
currying function
// 原始的加法函数 function origPlus(a, b) { return a + b; } // 柯里化后的plus函数 function plus(a) { return function(b) { return a + b; } } // ES6写法 const plus = a => b => a + b;
You can see , the curried plus function first accepts a parameter a, and then returns a function that accepts a parameter b. Due to closure, the returned function can access the parameter a of the parent function, so for example: const plus2 = plus (2) can be equivalent to function plus2(b) { return 2 b; }, thus achieving partial configuration.
In layman's terms, currying is a process of partially configuring a multi-parameter function, with each step returning a partially configured function that accepts a single parameter. In some extreme cases, you may need to partially configure a function many times, such as multiple additions:
multiPlus(1)(2)(3); // => 6
This way of writing seems strange, doesn’t it? But if you fall into the big pit of JS functional programming, this will be the norm.
Exquisite implementation of automatic currying in JS
Currying is a very important part of functional programming. Many functional languages (eg. Haskell) automatically curry functions by default. However, JS does not do this, so we need to implement the automatic currying function ourselves.
First enter the code:
// ES5 function curry(fn) { function _c(restNum, argsList) { return restNum === 0 ? fn.apply(null, argsList) : function(x) { return _c(restNum - 1, argsList.concat(x)); }; } return _c(fn.length, []); } // ES6 const curry = fn => { const _c = (restNum, argsList) => restNum === 0 ? fn(...argsList) : x => _c(restNum - 1, [...argsList, x]); return _c(fn.length, []); } /***************** 使用 *********************/ var plus = curry(function(a, b) { return a + b; }); // ES6 const plus = curry((a, b) => a + b); plus(2)(4); // => 6
This achieves automatic currying!
If you can understand what happened, then congratulations! The boss everyone calls you is you! , leave a like and start your functional career (funny
If you don’t understand what’s going on, don’t worry, I’ll start to help you sort out your ideas now.
Requirements Analysis
We need a curry function, which accepts a function to be curried as a parameter, returns a function for receiving a parameter, and puts the received parameters into a list , when the number of parameters is sufficient, execute the original function and return the result.
Implementation method
Simple thinking can tell that the number of steps of the currying part of the configuration function is equal to fn The number of parameters, that is to say, the plus function with two parameters needs to be partially configured in two steps. The number of parameters of the function can be obtained through fn.length.
The general idea is that each time a parameter is passed, Put the parameter into a parameter list argsList. If there are no parameters to be passed, then call fn.apply(null, argsList) to execute the original function. To achieve this, we need an internal judgment function _c(restNum, argsList), the function accepts two parameters, one is the number of remaining parameters restNum, and the other is the list of obtained parameters argsList; the function of _c is to determine whether there are any parameters that have not been passed in. When restNum is At zero, it is time to execute the original function through fn.apply(null, argsList) and return the result. If there are still parameters that need to be passed, that is to say, when restNum is not zero, a single parameter function needs to be returned
function(x) { return _c(restNum - 1, argsList.concat(x)); }
to continue receiving parameters. A tail recursion is formed here. After the function accepts a parameter, the number of remaining required parameters restNum is reduced by one, and the new parameter x is added to argsList and passed to _c for recursive calling. The result is that when When the number of parameters is insufficient, the single-parameter function responsible for receiving new parameters is returned. When the parameters are enough, the original function is called and returned.
Now let’s see:
function curry(fn) { function _c(restNum, argsList) { return restNum === 0 ? fn.apply(null, argsList) : function(x) { return _c(restNum - 1, argsList.concat(x)); }; } return _c(fn.length, []); // 递归开始 }
Is it starting to become clear? Already?
The ES6 writing method looks much simpler due to the use of syntax sugar such as array destructuring and arrow functions, but the ideas are the same~
// ES6 const curry = fn => { const _c = (restNum, argsList) => restNum === 0 ? fn(...argsList) : x => _c(restNum - 1, [...argsList, x]); return _c(fn.length, []); }
Comparison with other methods
There is another commonly used method:
function curry(fn) { const len = fn.length; return function judge(...args1) { return args1.length >= len ? fn(...args1): function(...args2) { return judge(...[...args1, ...args2]); } } } // 使用箭头函数 const curry = fn => { const len = fn.length; const judge = (...args1) => args1.length >= len ? fn(...args1) : (...args2) => judge(...[...args1, ...args2]); return judge; }
与本篇文章先前提到的方法对比的话,发现这种方法有两个问题:
依赖ES6的解构(函数参数中的 ...args1 与 ...args2);
性能稍差一点。
性能问题
做个测试:
console.time("curry"); const plus = curry((a, b, c, d, e) => a + b + c + d + e); plus(1)(2)(3)(4)(5); console.timeEnd("curry");
在我的电脑(Manjaro Linux,Intel Xeon E5 2665,32GB DDR3 四通道1333Mhz,Node.js 9.2.0)上:
本篇提到的方法耗时约 0.325ms
其他方法的耗时约 0.345ms
上面是我整理给大家的,希望今后会对大家有帮助。
相关文章:
The above is the detailed content of How to implement automatic currying in JS. For more information, please follow other related articles on the PHP Chinese website!