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