It’s not uncommon for an interviewer in an interview to ask do you know anything about Corrification? Or touch higher order functions? Or could you write a Curry utility function that turns an ordinary function into a higher-order function? I don’t know if you answered all of these questions in a flash or if you didn’t know what to do, but it doesn’t matter. After reading this article, I’m sure you’ll have some knowledge and skills related to Corrification.
The article is divided into three parts. The first part is about the concept of Collerization, the second part is about the realization of Collerization, and the third part is about an example in a practical project to help people understand its advantages and use Collerization.
The concept of Currization
Concept: The concept of Currization is to call a function by passing it only one set of arguments and having it return a function to process the rest. Example: The following implements a function that takes an array of DOM elements and returns an array of the element’s children.
- normal
// Object.prototype.toString.call(elements) === "[object Array]"
function getChildren(elements){
return elements.map(i= > i.childNodes)
}
Copy the code
- curry
// Let's assume that we have implemented the Curryization function curry
// curry: (* → a) → (* → a)
//function curry(fn: (any)=> any):(any)=>any{}
//var map = curry(function(f, ary) {
// return ary.map(f);
/ /});
let getChildren = map(i= > i.childNodes)
Copy the code
The above two implementations have the same effect. The first method directly defines the functions that operate on the array. The second method does not directly define the functions that operate on the array, but instead calls the map function.
Kerrization implementation
The following two methods are introduced. Compared with the second method, the most obvious defect of the first method is that the number of parameters of the function cannot be obtained after Currization, for example:
function a(b1, b2){}
console.log(a.length) / / 2
// After currified
let curryA = curry(a)
console.log(curryA.length) // If the first implementation is used, length = 0
Copy the code
First implementation
function curry(fn){
return function f(){
const args = [].slice.call(arguments)
if(args.length < fn.length){
return function(){// Arguments are different from the arguments abovereturn f.apply(this, args.concat([].slice.call(arguments)))
}
}else{
return fn.apply(this, args)
}
}
}
Copy the code
Second realization
In order to compensate for the failure to obtain the length of function parameters, we need to use an auxiliary function, which wraps the target function. The following implementation refers to the ramda source code and removes the placeholder function. The parameters of this auxiliary function only apply to 0-10, more than 10 will report an error.
// n is the parameter to be received var _arity =function (n, fn) {
/* eslint-disable no-unused-vars */
switch (n) {
caseZero:return function() { return fn.apply(this, arguments); };
case 1: return function(a0) { return fn.apply(this, arguments); };
case 2: return function(a0, a1) { return fn.apply(this, arguments); };
case 3: return function(a0, a1, a2) { return fn.apply(this, arguments); };
case 4: return function(a0, a1, a2, a3) { return fn.apply(this, arguments); };
case 5: return function(a0, a1, a2, a3, a4) { return fn.apply(this, arguments); };
case 6: return function(a0, a1, a2, a3, a4, a5) { return fn.apply(this, arguments); };
case 7: return function(a0, a1, a2, a3, a4, a5, a6) { return fn.apply(this, arguments); };
case 8: return function(a0, a1, a2, a3, a4, a5, a6, a7) { return fn.apply(this, arguments); };
case 9: return function(a0, a1, a2, a3, a4, a5, a6, a7, a8) { return fn.apply(this, arguments); };
case 10: return function(a0, a1, a2, a3, a4, a5, a6, a7, a8, a9) { return fn.apply(this, arguments); };
default: throw new Error('First argument to _arity must be a non-negative integer no greater than ten'); }}Copy the code
Here is the implementation, which can be seen in the comments:
// Curry uses _curry1, receives fn, and calls curryN by default,
// curry = (fn) => curryN(fn.length, fn)
var curry = _curry1(function(fn) {
return curryN(fn.length, fn);
});
// If it is a single argument, use _curry1
var _curry1 = function (fn) {
return function f1(a) {
if (arguments.length === 0) {
return f1;
} else {
return fn.apply(this.arguments); }}; }// If the parameter is 2
_curry2 = function(fn) {
return function f2(a, b){
switch(arguments.length){
// Return itself
case 0:
return f2;
// return the result of _curry1,
case 1:
return _curry1(function(_b) { return fn(a, _b); })
// Call directly
case 2:
return fn(a, b)
}
}
}
// curryN is itself a Curryized function
var curryN = _curry2(function(n, fn){
if (length === 1) {
return _curry1(fn);
}
// use _artity to wrap the function _curryN. _curryN takes three arguments. See the implementation below
return _arity(length, _curryN(length, [], fn));
})
// The implementation of internal _curryN adds a wrapper function on top of the first method above
var _curryN = function (length, recived, fn) {
return function() {
// Get the parameters of the function call
var args = [].slice.call(arguments);
// Passed parameters
var combined = recived.concat(args);
if(combined.length < length ) {
return _arity(length - combined.length, _curryN(length, combined, fn));
} else {
return fn.apply(this, combined); }}}Copy the code
Cremation advantage
Advantages: Passing only a partial call to a function is usually called a partial application. This has the advantage of caching parameters and greatly reducing boilerplate code. (As can be seen in the following example)
In a real project, the interface we call the back end is usually a function. For an interface, the return data of the back end result is obtained by executing functions. The problem is that each function has the same content, that is, too much boilerplate code, as shown in the following figure:
Reference documentation
- For the basics, I refer to the JS functional programming guide here
- Ramda source code parsing, I refer to this nuggets article ramda.js in the Cremation implementation
- The source code for Ramda is curry.js