Function currying and decurrying

Currying and de-currying

Currying ( Currying) and decurrying ( Uncurrying) in JavaScript are two very useful techniques that can help us write more elegant and versatile functions.
This article will first introduce the concepts, implementation principles, and application scenarios of currying and decurrying, and help readers deeply understand the uses of these two technologies through a large number of code examples.

Currying in JavaScript

concept

Currying ( 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. This technique was named by mathematician Haskell Curry.

Simply put, currying converts a function that takes multiple arguments into a series of functions that takes one argument. For example:

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

// 柯里化后
function curriedAdd(a) {
    
    
  return function(b) {
    
    
    return a + b;
  }
}

Implementation principle

The key to implementing currying is to save function parameters through closures. The following is the general pattern for curried functions:

function curry(fn) {
    
    
  return function curried(...args) {
    
    
    if (args.length >= fn.length) {
    
    
      return fn.apply(this, args);
    } else {
    
    
      return function(...args2) {
    
    
        return curried.apply(this, args.concat(args2));
      }
    }
  }
}

curryThe function accepts a fn function as a parameter and returns a curriedfunction. curriedThe function checks whether the number of parameters received args.lengthmeets the number of parameters required by the fn function fn.length. If it is satisfied, the fn function is called directly; if it is not satisfied, it continues to return to curriedthe function and waits to receive the remaining parameters.

In this way, the parameters received each time are saved through the closure until the total number of parameters reaches the fnrequired number of parameters, and then all the saved parameters applyare fnexecuted.

This pattern makes it easy to curry ordinary functions:

// 普通函数
function add(a, b) {
    
    
  return a + b;
} 

// 柯里化后
let curriedAdd = curry(add); 
curriedAdd(1)(2); // 3

Application scenarios

  • Parameter reuse
    Currying allows us to easily reuse parameters. For example:
function discounts(price, discount) {
    
    
  return price * discount;
}

// 柯里化后
const tenPercentDiscount = discounts(0.1); 
tenPercentDiscount(500); // 50
tenPercentDiscount(200); // 20
  • Returning a copy of a function in advance
    Sometimes we need to return a copy of a function in advance for use by other modules. In this case, currying can be used.
// 模块A
function ajax(type, url, data) {
    
    
  // 发送ajax请求
}

// 柯里化后
export const getJSON = curry(ajax)('GET');

// 模块B
import {
    
     getJSON } from './moduleA'; 

getJSON('/users', {
    
    name: 'John'});
  • Delayed execution
    The curried function will not be executed immediately when called. Instead, it will return a function and wait for complete parameters before executing. This allows us to more flexibly control the execution timing of functions.
let log = curry(console.log);

log('Hello'); // 不会立即执行

setTimeout(() => {
    
    
  log('Hello'); // 2秒后执行
}, 2000);

Decurrying in JavaScript

concept

Decurrying ( Uncurrying) is the opposite of currying, which converts a function that accepts a single parameter into a function that accepts multiple parameters.

// 柯里化函数  
function curriedAdd(a) {
    
    
  return function(b) {
    
    
    return a + b;
  }
}

// 反柯里化后
function uncurriedAdd(a, b) {
    
    
  return a + b; 
}

Implementation principle

The key to decurrying is to call the function recursively and pass in the parameters until the number of parameters reaches the number of parameters required by the function.



function uncurry(fn) {
    
    
return function(...args) {
    
    
let context = this;
return args.reduce((acc, cur) => {
    
    
return acc.call(context, cur);
}, fn);
}
}

uncurryReceives a function fnand returns a function. This function uses reducecontinuous calls fnand passes in parameters, Untilpassing argsall parameters fn.

Decurrying can be easily achieved using this pattern:



const curriedAdd = a => b => a + b;

const uncurriedAdd = uncurry(curriedAdd);
uncurriedAdd(1, 2); // 3

Application scenarios

  • Unified Interface Specification
    Sometimes we receive a curried function from another module, but our interface requires a normal multi-argument function. At this time, unification can be achieved through anti-currying.

// 模块A导出
export const curriedGetUser = id => callback => {
    
    
// 调用callback(user)
};

// 模块B中
import {
    
     curriedGetUser } from './moduleA';

// 反柯里化以符合接口
const getUser = uncurry(curriedGetUser);

getUser(123, user => {
    
    
// use user
});
  • Improve parameter flexibility.
    Anti-currying allows us to passesenter parameters in any order, increasing the flexibility of the function.

const uncurriedLog = uncurry(console.log);

uncurriedLog('a', 'b');
uncurriedLog('b', 'a'); // 参数顺序灵活
支持默认参数
柯里化函数不容易实现默认参数,而反柯里化后可以方便地设置默认参数。

function uncurriedRequest(url, method='GET', payload) {
    
    
// 请求逻辑
}

Analysis of interview questions from big factories

Function that implements add(1)(2)(3)output 6
This is a typical currying interview question. Analysis:


function curry(fn) {
    
    
return function curried(a) {
    
    
return function(b) {
    
    
return fn(a, b);
}
}
}

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

const curriedAdd = curry(add);

curriedAdd(1)(2)(3); // 6

Using currying technology, we can addtransform the ordinary function into a function curriedAddthat only receives one parameter at a time and returns the function to wait for the next parameter, thus achieving the add(1)(2)(3)effect of .

Implement the single parameter compose function

composeFunctions can combine multiple functions into one function, which is also a common currying interview question. Analysis:


function compose(fn1) {
    
    
return function(fn2) {
    
    
return function(x) {
    
    
return fn1(fn2(x));
};
};
}

function double(x) {
    
    
return x * 2;
}

function square(x) {
    
    
return x * x;
}

const func = compose(double)(square);

func(5); // 50

Using currying, we create a single-argument composefunction that returns a function each time waiting for the next function argument. This finally achieved compose(double)(square)the effect.

Uncurrying Function.bind

Function.bindThe function implements partial parameter binding, which is essentially an anti-currying process. Analysis:


Function.prototype.uncurriedBind = function(context) {
    
    
const fn = this;
return function(...args) {
    
    
return fn.call(context, ...args);
}
}

function greet(greeting, name) {
    
    
console.log(greeting, name);
}

const greetHello = greet.uncurriedBind('Hello');
greetHello('John'); // Hello John
uncurriedBind 通过递归调用并传参实现了反柯里化,使 bind 参数从两步变成一步传入,这也是 Function.bind 的工作原理。

Summarize

Currying and decurrying are very useful programming techniques that allow us to write more flexible and versatile functions. Understanding the implementation principles of these two technologies can help us use them better. In coding, we can decide whether to curry ordinary functions or decurry curried functions according to our needs. Reasonable use of these two technologies can greatly improve our programming efficiency.

Guess you like

Origin blog.csdn.net/weixin_44885062/article/details/132063650