柯里化是一種進階的函式處理技巧。它不只用於 JavaScript,也用於其他語言。
柯里化是一種函式轉換,它將一個可呼叫為 f(a, b, c)
的函式轉換為可呼叫為 f(a)(b)(c)
的函式。
柯里化不會呼叫函式。它只會轉換函式。
讓我們先看一個範例,以更了解我們在討論什麼,然後再看實際應用。
我們將建立一個輔助函式 curry(f)
,它會為一個雙引數 f
執行柯里化。換句話說,curry(f)
會將雙引數 f(a, b)
轉換為一個執行為 f(a)(b)
的函式
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
如您所見,實作很簡單:它只有兩個包裝函式。
curry(func)
的結果是一個包裝函式function(a)
。- 當它被呼叫為
curriedSum(1)
時,引數會儲存在詞法環境中,並傳回一個新的包裝函式function(b)
。 - 然後這個包裝器會以
2
作為參數被呼叫,並將呼叫傳遞給原始的sum
。
更進階的 currying 實作,例如 lodash 函式庫中的 _.curry,會回傳一個包裝器,允許函式同時以一般和部分的方式被呼叫。
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
Currying?用來幹嘛的?
要了解它的好處,我們需要一個有價值的真實範例。
例如,我們有一個記錄函式 log(date, importance, message)
,用於格式化和輸出資訊。在實際專案中,此類函式有許多有用的功能,例如透過網路傳送記錄,但我們在此僅使用 alert
function log(date, importance, message) {
alert(`[${date.getHours()}:${date.getMinutes()}] [${importance}] ${message}`);
}
讓我們來做 currying!
log = _.curry(log);
這樣之後,log
就可以正常運作了
log(new Date(), "DEBUG", "some debug"); // log(a, b, c)
…也可以使用 currying 形式
log(new Date())("DEBUG")("some debug"); // log(a)(b)(c)
現在,我們可以輕鬆地為目前的記錄建立一個便利函式
// 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
現在,logNow
是具有固定第一個參數的 log
,換句話說,就是「部分套用函式」或簡稱「部分函式」。
我們可以更進一步,為目前的除錯記錄建立一個便利函式
let debugNow = logNow("DEBUG");
debugNow("message"); // [HH:mm] DEBUG message
所以
- 在進行 currying 之後,我們並沒有失去任何東西:
log
仍然可以正常呼叫。 - 我們可以輕鬆地產生部分函式,例如針對今日的記錄。
進階的 curry 實作
如果你想深入了解細節,以下是我們可以在上面使用的進階 curry 實作,適用於多個參數的函式。
它很簡短
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
看起來可能很複雜,但實際上很容易理解。
curry(func)
呼叫的結果是包裝器 curried
,它看起來像這樣
// 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));
}
}
};
當我們執行它時,會有兩個 if
執行分支
- 如果傳遞的
args
數量與原始函式在定義中擁有的數量相同或更多(func.length
),則使用func.apply
將呼叫傳遞給它。 - 否則,取得一個部分函式:我們還沒有呼叫
func
。相反地,會回傳另一個包裝器,它將重新套用curried
,提供先前的參數和新的參數。
然後,如果我們再次呼叫它,我們將會得到一個新的部分(如果參數不足)或最終結果。
柯里化要求函式具有固定數量的參數。
使用剩餘參數的函式,例如 f(...args)
,無法以此方式進行柯里化。
根據定義,柯里化應將 sum(a, b, c)
轉換為 sum(a)(b)(c)
。
但是,JavaScript 中大多數柯里化實現都是進階的,如所述:它們還使函式在多參數變體中可呼叫。
摘要
柯里化是一種轉換,可將 f(a,b,c)
呼叫為 f(a)(b)(c)
。JavaScript 實作通常會同時保持函式正常呼叫,並在參數數量不足時傳回部分函式。
柯里化允許我們輕鬆取得部分函式。正如我們在記錄範例中所見,在對三參數通用函式 log(date, importance, message)
進行柯里化後,在呼叫時提供一個參數(例如 log(date)
)或兩個參數(例如 log(date, importance)
)時,會提供部分函式。
留言
<code>
標籤,若要插入多行程式碼,請將它們包覆在<pre>
標籤中,若要插入超過 10 行的程式碼,請使用沙盒(plnkr、jsbin、codepen…)