JavaScript function currying

JavaScript function currying

1. Definition:

Currying is a technique 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 a result.

Explain with a simple example:

function  add (a, b) {
     return  a + b
}

add( 1 , 2 ); // 3

Convert function add into curried function _add :

function _add (a){
    return function (b){
        return a + b
    }
}

_add ( 1 )( 2 );   // 3

Function add and function _add are equivalent.

_add is able to handle all remaining parameters of add , so currying is also called partial evaluation .

In fact, the two parameters a and b of the add function are changed into using a function to receive a and then returning a function to process the b parameter.

Now the idea should be clearer: only pass a part of the parameters to the function to call, and let it return a function to handle the remaining parameters.

2. The role of currying function

1. Parameter reuse

Case: splicing address

Follow common ideas to splice an address

// Splicing address 
function  getUrl ( protocol, hostname, pathname ) {
     return  ` ${protocol} ${hostname} ${pathname} ` ;
}

const url1 = getUrl ( 'https://' , 'www.baidu.com' , '/hasa' );
 const url2 = getUrl ( 'https://' , 'www.zhihu.com' , '/saandsa' );
 const url3 = getUrl ( 'https://' , 'www.segmentfault.com' , '/hasak' );

console .log ( url1 , url2, url3)

Each time you call the getUrl parameter, you must repeatedly pass in the parameter ‘https://’.

After currying and encapsulating:

function  curry ( protocol ) {
     return  function ( hostname, pathname ) {
         return  ` ${protocol} ${hostname} ${pathname} ` ;
    }
}

const url_curry = curry ( 'https://' );

const url1 = url_curry ( 'www.baidu.com' , '/hasa' );
 const url2 = url_curry ( 'www.zhihu.com' , '/saandsa' );
 const url3 = url_curry ( 'www.segmentfault.com' , '/hasak' );

console .log ( url1 , url2, url3)

It is obvious that after currying and encapsulation, when the address is spliced ​​later, the number of parameters is reduced and the code duplication rate is reduced.

2. Confirm in advance/return in advance

Case: Listening method compatible with IE browser events ( IE is dead )

traditional method:

/*
* @param     element Object DOM element object
* @param     type String event type
* @param     listener Function event handling function
* @param     useCapture Boolean whether to capture
*/ 
var addEvent = function  (element, type, listener, useCapture)  {
     if (window.addEventListener) {
        element.addEventListener(type, function  (e)  {
            listener.call(element, e)
        }, useCapture)
    } else {
        element.attachEvent( 'on' + type, function  (e)  {
            listener.call(element, e)
        })
    }
}

The flaw is that you need to make a new judgment every time you bind an event to a DOM element. In fact, as soon as the browser has determined the event monitoring web page, you can know which listening method the browser needs. So we can make the judgment only executed once.

After currying and encapsulating:

var addEvent = ( function () {
    if (window.addEventListener) {
        return  function (element, type , listener, useCapture) { // return function
            element.addEventListener( type , function () {
                listener.call(element)
            }, useCapture)
        }
    } else {
        return  function (element, type , listener) {
            element.attachEvent( 'on ' + type , function () {
                listener.call(element)
            })
        }
    }
})()

addEvent(element, "click", listener)

Execute the function immediately, and even if multiple events are triggered, only one if conditional judgment will be triggered.

3. Delayed execution

Case: fishing statistics weight

traditional method:

let fishWeight = 0 ;
 const addWeight = function ( weight ) {
    fishWeight += weight;
};

addWeight ( 2.3 );
 addWeight ( 6.5 );
 addWeight ( 1.2 );
 addWeight ( 2.5 );

console .log ( fishWeight);  

Each time the addWeight method is executed, the weights of the fish are added.

After currying and encapsulating:

function  curryWeight ( fn ) {
     let _fishWeight = [];
     return  function () {
         if ( arguments . length === 0 ) {
             return fn. apply ( null , _fishWeight);
        } else {
            _fishWeight = _fishWeight. concat ([... arguments ]);
        }
    }
}

function  addWeight () {
     let fishWeight = 0 ;
     for ( let i = 0 , len = arguments . length ; i < len; i++) {
        fishWeight += arguments [i];
    }
    return fishWeight;
}

const _addWeight = curryWeight (addWeight);
 _addWeight ( 6.5 );
 _addWeight ( 1.2 );
 _addWeight ( 2.3 );
 _addWeight ( 2.5 );
 console . log ( _addWeight ())

When the _addWeight method is executed , the weights of the fish are not added, and then the sum is only done when _addWeight() is executed for the last time. The effect of delayed execution of the addWeight method is achieved .

—– Extension: Sort the fish by weight —–

function  curryWeight ( fn ) {
     let _fishWeight = [];
     return  function () {
         if ( arguments . length === 0 ) {
             return fn. apply ( null , _fishWeight);
        } else {
            _fishWeight = _fishWeight. concat ([... arguments ]);
        }
    }
}

function  sortWeight () {
     return [... arguments ]. sort ( ( a, b ) => a - b);
}

const _addWeight = curryWeight (sortWeight);
 _addWeight ( 6.5 );
 _addWeight ( 1.2 );
 _addWeight ( 2.3 );
 _addWeight ( 2.5 );
 console . log ( _addWeight ())

It’s very simple, just modify the addWeight function.

3. Implementation of Currying Function

Done1:
step1: Implement a function add(1)(2)

function  add ( num ){
    let sum = num;
     return  function ( num ){
       return  sum +num
    }
  }
console .log (add( 1 ) ( 2 ));

step2: How to implement add(1)(2)(3)?
It cannot be nested infinitely, so return a function name and determine whether there are any parameters passed in inside the function.

 function  add ( num ){
    let sum =num;
     return adds = function ( num1 ){
     
      if (num1) {
         sum = sum +num1;
         return adds
      }
      return  sum
    }
  }
console .log (add( 1 ) ( 2 )( 3 )());

step3: What if you implement an add(1,2,3)(2,1)(3)(2,1,3)?
Because the parameters passed in each call are now of variable length. Therefore, the formal parameters cannot be written directly.

function  add () {
     // Collect parameters 
    let params = [... arguments ];

    const getParams = function () {
         // When the parameter passed in is empty, traverse and sum the parameter array. 
        if ([... arguments ]. length === 0 ) return params. reduce ( ( pre, next ) => pre + next, 0 );
         // Collect parameters 
        params. push (... arguments );
         return getParams ;
    }

    // Repeat the call until the incoming parameters are empty 
    return getParams;
}
let a = add ( 1 , 2 )( 2 , 1 )( 3 )();
 console . log (a);

Done2:

function  curry () {
     let args = [... arguments ];
     let inner = function () {
        args. push (... arguments );
         return inner;
    }
    
    // Core content: implicit conversion, calling the internal toString 
    inner. toString = function () {
         return args. reduce ( function ( pre, next ) {
             return pre + next;
        })
    }
    return inner;
}

const result = curry ( 1 )( 2 );
 console . log (+result);

Regarding implicit conversion
1. In JavaScript, both the toString() method and the valueOf() method can be rewritten, and will be automatically called if used to operate the JavaScript parser. Therefore, rewriting toString at the end of the currying function is just enough to execute it after collecting all parameters.
2. Posts about JavaScript implicit calls

4. Summary of Currying

The core idea of ​​function currying is to collect parameters , which need to rely on parameters and recursion. By splitting parameters, a multi-parameter function method is called to reduce code redundancy and increase readability.

advantage:

  • After currying, we have not lost any parameters: log can still be called normally;
  • We can easily generate partial functions, such as those used to generate today’s log;
  • Single entrance;
  • Easy to test and reuse.

shortcoming

  • There are many nested functions;
  • Occupies memory and may cause memory leaks (because it is essentially implemented with closures);
  • Poor efficiency (because of the use of recursion);
  • Variable access is slow and accessibility is poor (because of the use of arguments);

Application scenarios:

  1. Reduce repeated transmission of unchanged parameters;
  2. Pass curried callback parameters to other functions.