什麼是偏函式
在電腦科學中,偏函式應用 是指將一些引數固定到一個函式,產生另一個較小的函式的過程。
舉個例子:
// 我們建立了一個做 乘法運算 的函式
function mult(a, b) {
return a * b;
};複製程式碼
基於這個函式,我們利用bind
創造一個新的函式 double
:
let double = mult.bind(null, 2);
console.log( double(3) ); // mult(2, 3) = 6;
console.log( double(4) ); // mult(2, 4) = 8;
console.log( double(5) ); // mult(2, 5) = 10; 複製程式碼
mult.bind(null, 2)
創造了一個新函式 double
,傳遞呼叫到mult
函式,以null
為context
,2
為第一個引數。其他引數等待傳入。
這就是 偏函式應用 —— 我們創造了一個新函式,同時將部分引數替換成特定值。
什麼時候使用偏函式
剛剛我們基於mult
建立了一個新的函式double
,這裡我們再建立一個新的函式triple
:
let triple = mult.bind(null, 3);
console.log( triple(3) ); // mult(3, 3) = 9;
console.log( triple(4) ); // mult(3, 4) = 12;
console.log( triple(5) ); // mult(3, 5) = 15;複製程式碼
使用 偏函式,我們能夠從中受益的是:我們建立了一個獨立的非匿名函式(double
,triple
)。我們可以使用它,而不需要每次都傳入第一個引數,因為bind
幫我們搞定了。
在其他的場景中,當我們有一個非常通用的函式,並且想要方便地獲取它的特定變體,偏函式也是非常有用。
舉個例子,我們擁有函式post(signature, api, data)
。在呼叫請求方法的時候有著相同的使用者簽名,這裡我們想要使用它的偏函式變體:post(api, data)
,表明該請求傳送自同一使用者簽名。
什麼是柯里化
有時候人們會把 偏函式應用 和另外一個名為 柯里化 的東西混淆,但那的確是另外一個和函式有關的有趣技術。
在 函數語言程式設計語言 和 其他許多語言 中,柯里化(currying)提供了一種自動管理引數如何傳遞給函式和異常的方法。
簡單來說,currying
是一項將一個呼叫形式為f(a, b, c)
的函式轉化為呼叫形式f(a)(b)(c)
的技術。
舉個例子:
function currying(fn) {
return function(a) {
return function(b) {
return fn(a, b);
};
};
}
// 用法
function sum(a + b) {
return a + b;
}
let carriedSum = currying(sum);
console.log( carriedSum(1)(2) ); // 3複製程式碼
從上面的例子可以看到,carrying
的實現就是一系列的封裝。
-
currying(fn)
的結果就是一層封裝function(a)
。 - 當它被呼叫,就像
carriedSum(1)
這樣,它的引數被儲存到 詞法環境 中,然後返回一層新的封裝function(b)
。 - 然後
carriedSum(1)(2)
呼叫function(b)
,傳入引數2
,它將呼叫傳遞給初始的多引數函式sum
。
結合之前的 偏函式 知識,我們可以看到,sum
函式在 柯里化 之後,逐個傳遞引數的時候返回的那一層封裝:其實就是 sum
函式 的 偏函式變體。
這可能也是大家容易將這個兩個概念搞混的原因之一吧。
高階柯里化
高階的柯里化同時允許 函式正常呼叫 和 獲取偏函式。
我們可以實現一下 高階的柯里化:
function currying(fn) {
return function curried(...args) {
if(args.length>=fn.length) {
// 傳入的實參長度 >= 初始函式形參長度 的時候,則直接執行初始函式
return fn.apply(this, args);
} else {
// 否則 得到一個 偏函式,遞迴carried方法,直到獲得所有引數後,直接執行
return function(...args2) {
return curried.apply(this, args.concat(args2));
}
}
}
}
function sum(a, b, c) {
return a + b + c;
}
let curriedSum = currying(sum);
// 常規呼叫
console.log( curriedSum(1, 2, 3) ); // 6
// 得到 curriedSum(1)的偏函式,然後用另外兩個引數呼叫它
console.log( curriedSum(1)(2, 3) ); // 6
// 完全柯里化呼叫
console.log( curriedSum(1)(2)(3) ); // 6複製程式碼
柯里化的目的是什麼?
從上面的 高階柯里化 實現的例子中可以發現:
-
sum
函式 在 柯里化 之後對於使用並沒有任何影響,仍然可以被正常呼叫。 - 在很多情況下,我們可以更方便的生成 偏函式 變體,可以更靈活的被我們使用。
總得來說,柯里化的目的 就是在不影響 初始函式 的呼叫形式的情況下,更方便的讓我們獲得初始函式的 偏函式 變體。
最後,歡迎各位小夥伴留言,相互討論。