preface
Currization is a common concept in JS, so let’s try to write a curry function that takes a function and then returns the currization of that function
const join = (a, b, c) = > {
return `${a}_${b}_${c}`
}
const curriedJoin = curry(join)
curriedJoin(1.2.3) / / '1 _2_3'
curriedJoin(1) (2.3) / / '1 _2_3'
curriedJoin(1.2) (3) / / '1 _2_3'
Copy the code
implementation
/ / the answer 1
function curry(func) {
return function innerFunc(. args) {
//func is the original function,innerFunc is the new function
// If the new function has more or equal arguments than the original function, the original function is executed
if(args.length >= func.length){
returnfunc(... args) }else{
// add the new function arguments
return function(. next){
returninnerFunc(... args,... Next)}}}} or/ / the answer 2
function curry(func) {
return function innerFunc(. args) {
if(args.length >= func.length){
returnfunc(... args)//return func.apply(this,args)
}else{
// Bind inherits this argument, not changes it.
return innerFunc.bind(this. Args)}}} or// Answer 3 2022.1.21
function curry(fn) {
const l = fn.length;
let _curry = function (. rest) {
while (rest.length < l) {
return _curry.bind(this. rest); }return fn.apply(this,rest);
}
return _curry;
}
Copy the code
No matter how you write it, the core idea is to decide whether to return the result or continue collecting the parameters by judging the number of parameters.
supplement
Some of you may encounter an even more egregious need for your Curry to support placeholders.
const join = (a, b, c) = > {
return `${a}_${b}_${c}`
}
const curriedJoin = curry(join)
const _ = curry.placeholder
curriedJoin(1.2.3) / / '1 _2_3'
curriedJoin(_, 2) (1.3) / / '1 _2_3'
curriedJoin(_, _, _)(1) (_,3) (2) / / '1 _2_3'
Copy the code
SoEasy, let’s make it happen 👇
function curry(fn) {
return function innerFunc(. args){
// If all parameters are placeholder and the length is placeholder, execute fn
constcomplete=args.length >= fn.length && ! args.slice(0,fn.length).includes(curry.placeholder);
if(complete){
return fn.apply(this,args);
}else{
return function(. next){
// Replace the placeholder of the previous function
const res=args.map(arg= > arg === curry.placeholder && next.length ? next.shift() : arg);
returninnerFunc(... res, ... next); } } } } curry.placeholder =Symbol(a)Copy the code
add
I think after the curry function, we should be able to determine the number of remaining arguments by.length, as shown below
const join = (a, b, c) = > {
return `${a}_${b}_${c}`
}
const curriedJoin = curry(join)
curriedJoin(1.2.3).length / / 0
curriedJoin(1).length / / 2
curriedJoin(1.2).length / / 1
Copy the code
But the fact is that both answer 1 and answer 2 and 3 return 0 consistently. As for why, let’s take a look at how function.length works:
//length is an attribute of the function object. It is the number of parameters that must be passed to the function.
console.log(Function.length); / / 1
console.log((function() {}).length); / / 0
console.log((function(a) {}).length); / / 1
console.log((function(a, b) {}).length); / / 2
// The number of parameters does not include the number of remaining parameters, only the number of parameters before the first one has a default value.
console.log((function(. args) {}).length);
/ / 0,
// Remaining parameters (... The args) is 0
console.log((function(a, b = 1, c) {}).length);
/ / 1
//b=1 declares a default value. The number of arguments before the first one has a default value is 1
Copy the code
I think we’ve all figured it out: either answer number one or innerFunc or _curry is returned with the innerFunc or _curry. Args to collect the remaining parameters, so naturally the length is 0 at any stage.
Some people might mistake this for the bind method, as shown in the following example
const join = (a, b, c) = > {
return `${a}_${b}_${c}`
}
console.log(join.bind(this.1).length) / / 2
function join2(. args){
return `${args[0]}_${args[1]}_${args[2]}`
}
console.log(join2.bind(this.1).length) / / 0
// Bind maintains the length of Function. The difference between the two functions is that the original Function of bind uses (... Args), then bind with length 0
Copy the code
Finally, how do I get Curry to see the rest of the parameters through.length
function curry(func) {
return function innerFunc(. args) {
if(args.length >= func.length){
returnfunc(... args) }else{
var boundLength = func.length-args.length
var boundArgs = [];
for (var i = 0; i < boundLength; i++) {
boundArgs.push('$' + i);
}
var binder= function(. next){
returninnerFunc(... args,... next) }//Function creates a new Function by collecting parameters. This method is used to dynamically pass in the parameter boundArgs. Length.
//bound = function ($1,$2,... ,$x){return binder.apply(this, arguments); }
var bound = Function('binder'.'return function (' + boundArgs.join(', ') + '){ return binder.apply(this,arguments); } ')(binder);
return bound
}
}
}
Copy the code
We have a bound function that executes immediately. We have a bound function that executes immediately.
((binder) = >
// We need to put a boundargs. length argument in this function, but obviously this cannot be done.
function ( 'boundArgs.join('.') ' ) {
return binder.apply(this.arguments);
})(binder);
Copy the code
Reference:
function-bind
Function. The rules of length
Function method description