進(jìn)大廠之必會(huì)的函數(shù)柯里化(Currying)
深入了解函數(shù)柯里化
curry是一種處理函數(shù)的高級(jí)技術(shù)。它不僅在JavaScript中使用,也在其他語言中使用。
套用是函數(shù)的一種轉(zhuǎn)換,將函數(shù)從可調(diào)用的f(a, b, c)轉(zhuǎn)換為可調(diào)用的f(a)(b)(c)。
curry不調(diào)用函數(shù)。它只是改變了它。
讓我們先看一個(gè)例子,以便更好地理解我們正在討論的內(nèi)容,然后看實(shí)際應(yīng)用程序。
我們將創(chuàng)建一個(gè)輔助函數(shù)curry(f),它執(zhí)行對(duì)兩個(gè)參數(shù)f的curry。換句話說,對(duì)于兩個(gè)參數(shù)f(a, b)的curry(f)將其轉(zhuǎn)換為一個(gè)以f(a)(b)的方式運(yùn)行的函數(shù):
function curry(f) { // curry(f) does the currying transform
return function(a) {
return function(b) {
return f(a, b);
};
};
}
// usage
function sum(a, b) {
return a + b;
}
let curriedSum = curry(sum);
alert( curriedSum(1)(2) ); // 3
如您所見,實(shí)現(xiàn)很簡單:它只是兩個(gè)包裝器。
curry(func)的結(jié)果是一個(gè)包裝函數(shù)(a)。
當(dāng)像curriedSum(1)那樣調(diào)用時(shí),參數(shù)被保存在詞法環(huán)境中,并返回一個(gè)新的包裝器函數(shù)(b)。
然后用2作為參數(shù)調(diào)用這個(gè)包裝器,并將調(diào)用傳遞給原始的sum。
更高級(jí)的套用實(shí)現(xiàn),例如lodash庫中,返回一個(gè)允許函數(shù)被正?;虿糠终{(diào)用的包裝器:
function sum(a, b) {
return a + b;
}
let curriedSum = _.curry(sum); // using _.curry from lodash library
alert( curriedSum(1, 2) ); // 3, still callable normally
alert( curriedSum(1)(2) ); // 3, called partially
為了理解這些好處,我們需要一個(gè)有價(jià)值的現(xiàn)實(shí)例子。
例如,我們有日志功能log(date、importance、message)來格式化和輸出信息。在實(shí)際的項(xiàng)目中,這樣的函數(shù)有很多有用的特性,比如通過網(wǎng)絡(luò)發(fā)送日志,這里我們只使用alert:
function log(date, importance, message) {
alert(`[${date.getHours()}:${date.getMinutes()}] [${importance}] ${message}`);
}
對(duì)其進(jìn)行函數(shù)柯里化
log = _.curry(log);
日志正常工作后:
log(new Date(), "DEBUG", "some debug"); // log(a, b, c)
也可以使用 柯里化
log(new Date())("DEBUG")("some debug"); // log(a)(b)(c)
現(xiàn)在我們可以很容易地為當(dāng)前日志創(chuàng)建一個(gè)方便的函數(shù):
// logNow will be the partial of log with fixed first argument
let logNow = log(new Date());
// use it
logNow("INFO", "message"); // [HH:mm] INFO message
現(xiàn)在logNow是帶有固定第一個(gè)參數(shù)的日志,換句話說就是“部分應(yīng)用函數(shù)”或簡稱為“partial”。
我們可以更進(jìn)一步,為當(dāng)前調(diào)試日志創(chuàng)建一個(gè)方便的函數(shù):
let debugNow = logNow("DEBUG");
debugNow("message"); // [HH:mm] DEBUG message
所以:
curry后我們沒有丟失任何東西:log仍然可以正常調(diào)用。
我們可以很容易地生成部分函數(shù),比如今天的日志。
進(jìn)階的柯里化實(shí)現(xiàn)
如果您想了解更多細(xì)節(jié),這里是我們可以在上面使用的多參數(shù)函數(shù)的“高級(jí)”curry實(shí)現(xiàn)。
很短:
function curry(func) {
return function curried(...args) {
if (args.length >= func.length) {
return func.apply(this, args);
} else {
return function(...args2) {
return curried.apply(this, args.concat(args2));
}
}
};
}
案例
function sum(a, b, c) {
return a + b + c;
}
let curriedSum = curry(sum);
alert( curriedSum(1, 2, 3) ); // 6, still callable normally
alert( curriedSum(1)(2,3) ); // 6, currying of 1st arg
alert( curriedSum(1)(2)(3) ); // 6, full currying
新的curry看起來可能很復(fù)雜,但實(shí)際上很容易理解。
curry(func)調(diào)用的結(jié)果是這樣的包裝器curry:
// func is the function to transform
function curried(...args) {
if (args.length >= func.length) { // (1)
return func.apply(this, args);
} else {
return function(...args2) { // (2)
return curried.apply(this, args.concat(args2));
}
}
};
當(dāng)我們運(yùn)行它時(shí),有兩個(gè)if執(zhí)行分支:
如果傳入的args count與原始函數(shù)的定義(function.length)相同或更多,則只需使用function.apply將調(diào)用傳遞給它。
否則,得到一個(gè)部分:我們還沒有調(diào)用func。相反,將返回另一個(gè)包裝器,它將重新應(yīng)用curry,同時(shí)提供以前的參數(shù)和新的參數(shù)。
然后,如果我們?cè)俅握{(diào)用它,我們將得到一個(gè)新的部分(如果沒有足夠的參數(shù)),或者最終得到結(jié)果。
