JS中的 偏函式 和 柯里化

MioGo發表於2019-05-07

什麼是偏函式

在電腦科學中,偏函式應用 是指將一些引數固定到一個函式,產生另一個較小的函式的過程。

舉個例子:

// 我們建立了一個做 乘法運算 的函式
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函式,以nullcontext2為第一個引數。其他引數等待傳入。

這就是 偏函式應用 —— 我們創造了一個新函式,同時將部分引數替換成特定值。

什麼時候使用偏函式

剛剛我們基於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;複製程式碼

使用 偏函式,我們能夠從中受益的是:我們建立了一個獨立的非匿名函式(doubletriple)。我們可以使用它,而不需要每次都傳入第一個引數,因為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的實現就是一系列的封裝。

  1.  currying(fn) 的結果就是一層封裝function(a)
  2. 當它被呼叫,就像 carriedSum(1) 這樣,它的引數被儲存到 詞法環境 中,然後返回一層新的封裝function(b)
  3. 然後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複製程式碼

柯里化的目的是什麼?

從上面的 高階柯里化 實現的例子中可以發現:

  1.  sum函式 在 柯里化 之後對於使用並沒有任何影響,仍然可以被正常呼叫。
  2.  在很多情況下,我們可以更方便的生成 偏函式 變體,可以更靈活的被我們使用。

總得來說,柯里化的目的 就是在不影響 初始函式 的呼叫形式的情況下,更方便的讓我們獲得初始函式的 偏函式 變體。

最後,歡迎各位小夥伴留言,相互討論。


相關文章