Javascript 柯里化(Currying)

2023-02-17 10:53 更新

柯里化(Currying)是一種關于函數(shù)的高階技術。它不僅被用于 JavaScript,還被用于其他編程語言。

柯里化是一種函數(shù)的轉(zhuǎn)換,它是指將一個函數(shù)從可調(diào)用的 f(a, b, c) 轉(zhuǎn)換為可調(diào)用的 f(a)(b)(c)。

柯里化不會調(diào)用函數(shù)。它只是對函數(shù)進行轉(zhuǎn)換。

讓我們先來看一個例子,以更好地理解我們正在講的內(nèi)容,然后再進行一個實際應用。

我們將創(chuàng)建一個輔助函數(shù) curry(f),該函數(shù)將對兩個參數(shù)的函數(shù) f 執(zhí)行柯里化。換句話說,對于兩個參數(shù)的函數(shù) f(a, b) 執(zhí)行 curry(f) 會將其轉(zhuǎn)換為以 f(a)(b) 形式運行的函數(shù):

function curry(f) { // curry(f) 執(zhí)行柯里化轉(zhuǎn)換
  return function(a) {
    return function(b) {
      return f(a, b);
    };
  };
}

// 用法
function sum(a, b) {
  return a + b;
}

let curriedSum = curry(sum);

alert( curriedSum(1)(2) ); // 3

正如你所看到的,實現(xiàn)非常簡單:只有兩個包裝器(wrapper)。

  • ?curry(func)? 的結(jié)果就是一個包裝器 ?function(a)?。
  • 當它被像 ?curriedSum(1)? 這樣調(diào)用時,它的參數(shù)會被保存在詞法環(huán)境中,然后返回一個新的包裝器 ?function(b)?。
  • 然后這個包裝器被以 ?2? 為參數(shù)調(diào)用,并且,它將該調(diào)用傳遞給原始的 ?sum? 函數(shù)。

柯里化更高級的實現(xiàn),例如 lodash 庫的 _.curry,會返回一個包裝器,該包裝器允許函數(shù)被正常調(diào)用或者以部分應用函數(shù)(partial)的方式調(diào)用:

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

let curriedSum = _.curry(sum); // 使用來自 lodash 庫的 _.curry

alert( curriedSum(1, 2) ); // 3,仍可正常調(diào)用
alert( curriedSum(1)(2) ); // 3,以部分應用函數(shù)的方式調(diào)用

柯里化?目的是什么?

要了解它的好處,我們需要一個實際中的例子。

例如,我們有一個用于格式化和輸出信息的日志(logging)函數(shù) log(date, importance, message)。在實際項目中,此類函數(shù)具有很多有用的功能,例如通過網(wǎng)絡發(fā)送日志(log),在這兒我們僅使用 alert

function log(date, importance, message) {
  alert(`[${date.getHours()}:${date.getMinutes()}] [${importance}] ${message}`);
}

讓我們將它柯里化!

log = _.curry(log);

柯里化之后,log 仍正常運行:

log(new Date(), "DEBUG", "some debug"); // log(a, b, c)

……但是也可以以柯里化形式運行:

log(new Date())("DEBUG")("some debug"); // log(a)(b)(c)

現(xiàn)在,我們可以輕松地為當前日志創(chuàng)建便捷函數(shù):

// logNow 會是帶有固定第一個參數(shù)的日志的部分應用函數(shù)
let logNow = log(new Date());

// 使用它
logNow("INFO", "message"); // [HH:mm] INFO message

現(xiàn)在,logNow 是具有固定第一個參數(shù)的 log,換句話說,就是更簡短的“部分應用函數(shù)(partially applied function)”或“部分函數(shù)(partial)”。

我們可以更進一步,為當前的調(diào)試日志(debug log)提供便捷函數(shù):

let debugNow = logNow("DEBUG");

debugNow("message"); // [HH:mm] DEBUG message

所以:

  1. 柯里化之后,我們沒有丟失任何東西:?log? 依然可以被正常調(diào)用。
  2. 我們可以輕松地生成部分應用函數(shù),例如用于生成今天的日志的部分應用函數(shù)。

高級柯里化實現(xiàn)

如果你想了解更多細節(jié),下面是用于多參數(shù)函數(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,仍然可以被正常調(diào)用
alert( curriedSum(1)(2,3) ); // 6,對第一個參數(shù)的柯里化
alert( curriedSum(1)(2)(3) ); // 6,全柯里化

新的 curry 可能看上去有點復雜,但是它很容易理解。

curry(func) 調(diào)用的結(jié)果是如下所示的包裝器 curried

// func 是要轉(zhuǎn)換的函數(shù)
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 執(zhí)行分支:

  1. 如果傳入的 ?args? 長度與原始函數(shù)所定義的(?func.length?)相同或者更長,那么只需要使用 ?func.apply? 將調(diào)用傳遞給它即可。
  2. 否則,獲取一個部分應用函數(shù):我們目前還沒調(diào)用 ?func?。取而代之的是,返回另一個包裝器 ?pass?,它將重新應用 ?curried?,將之前傳入的參數(shù)與新的參數(shù)一起傳入。

然后,如果我們再次調(diào)用它,我們將得到一個新的部分應用函數(shù)(如果沒有足夠的參數(shù)),或者最終的結(jié)果。

只允許確定參數(shù)長度的函數(shù)

柯里化要求函數(shù)具有固定數(shù)量的參數(shù)。

使用 rest 參數(shù)的函數(shù),例如 f(...args),不能以這種方式進行柯里化。

比柯里化多一點

根據(jù)定義,柯里化應該將 sum(a, b, c) 轉(zhuǎn)換為 sum(a)(b)(c)

但是,如前所述,JavaScript 中大多數(shù)的柯里化實現(xiàn)都是高級版的:它們使得函數(shù)可以被多參數(shù)變體調(diào)用。

總結(jié)

柯里化 是一種轉(zhuǎn)換,將 f(a,b,c) 轉(zhuǎn)換為可以被以 f(a)(b)(c) 的形式進行調(diào)用。JavaScript 實現(xiàn)通常都保持該函數(shù)可以被正常調(diào)用,并且如果參數(shù)數(shù)量不足,則返回部分應用函數(shù)。

柯里化讓我們能夠更容易地獲取部分應用函數(shù)。就像我們在日志記錄示例中看到的那樣,普通函數(shù) log(date, importance, message) 在被柯里化之后,當我們調(diào)用它的時候傳入一個參數(shù)(如 log(date))或兩個參數(shù)(log(date, importance))時,它會返回部分應用函數(shù)。


以上內(nèi)容是否對您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號