JS中的curry化(柯里化)

什么是 curry 化

curry 化也是一個(gè)常見的概念,維基百科對(duì)其解釋為:

在計(jì)算機(jī)科學(xué)中,柯里化(currying),又譯為卡瑞化或加里化,是把接受多個(gè)參數(shù)的函數(shù)變換成接受一個(gè)單一參數(shù)(最初函數(shù)的第一個(gè)參數(shù))的函數(shù),并且返回接受余下的參數(shù)而且返回結(jié)果的新函數(shù)的技術(shù)。這個(gè)技術(shù)由克里斯托弗·斯特雷奇以邏輯學(xué)家哈斯凱爾·加里命名的。

再簡(jiǎn)潔一些就是:柯里化是把接受多個(gè)參數(shù)的函數(shù)變換成接受一個(gè)單一參數(shù)的函數(shù),并且返回接受余下的參數(shù)而且返回結(jié)果的新函數(shù)的技術(shù)。

還是不懂,沒關(guān)系,下面就通過幾個(gè)例子一步一步去了解柯里化。

怎么實(shí)現(xiàn) curry 化

實(shí)現(xiàn)一個(gè)函數(shù),對(duì)數(shù)組進(jìn)行過濾,過濾掉小于10的項(xiàng)。
傳統(tǒng)的做法是:

const filterLowerThan10 = (array) => {
  let result = [];
  for (let i = 0; i < array.length; i++) {
    let currentValue = array[i];
    if (currentValue < 10) {
      result.push(currentValue);
    }
  }
  return result;
};

實(shí)現(xiàn)起來(lái)并沒有難度,但是當(dāng)前要過濾的是小于10的項(xiàng),如果這個(gè)閾值更改了呢,我們可以借用 curry 化的思想將其改造:

const filterLowerNumber = (number) => {
  return (array) => {
    let result = [];
    for (let i = 0; i < array.length; i++) {
      let currentValue = array[i];
      if (currentValue < number) {
        result.push(currentValue);
      }
    }
    return result;
  };
};

const filterLowerThan10 = filterLowerNumber(10);
filterLowerThan10([1, 11, 8, 21, 2]); // [1,8,2]

// 也可以這樣簡(jiǎn)寫
// filterLowerNumber(10)([1, 11, 8, 21, 2]);

另一個(gè)場(chǎng)景
實(shí)現(xiàn)一個(gè)求兩數(shù)之和得方法
普通函數(shù):

function add(x, y) {
  return x + y;
}
add(1, 2); // 3

curry 化函數(shù):

var add = function (x) {
  return (y) => x + y;
};
add(1)(2); // 3

在此基礎(chǔ)上提交更復(fù)雜得要求
按要求實(shí)現(xiàn) add 方法:

add(1)(2); // 結(jié)果為3
add(1)(3)(5); // 結(jié)果為9
add(1)...(n); // 結(jié)果為sum

大家可能比較眼熟哈,很多 curry 化的面試題都是以此為原型的。

解題:

  1. 由調(diào)用方式可知,add 函數(shù)每次執(zhí)行后一定返回一個(gè)函數(shù),以供后續(xù)調(diào)用,且返回的函數(shù)依然要返回自身,供多級(jí)調(diào)用;
  2. 當(dāng)最后一次調(diào)用結(jié)束,返回的是一個(gè)函數(shù),為了滿足題意,需要改寫內(nèi)部返回的函數(shù) toString (代碼中也解釋);
  3. 為了進(jìn)行求和,需要在 add 函數(shù)內(nèi)部維護(hù)一個(gè)閉包變量 args,args 是個(gè)數(shù)組,存放了第一次調(diào)用 add 和 后續(xù)調(diào)用 fn 函數(shù)時(shí)傳入的參數(shù);
  4. 在調(diào)用 fn 的 toString 方法時(shí),意味著最后一次調(diào)用結(jié)束,返回函數(shù),那么就計(jì)算 args 數(shù)組中的所有值得和即可求出結(jié)果。
const add = (arg1) => {
  let args = [arg1];

  const fn = (arg2) => {
    args.push(arg2);
    return fn;
  };

  // 因?yàn)樽詈笠淮螆?zhí)行完畢后會(huì)返回 fn 函數(shù)體,相當(dāng)于調(diào)用了 fn 的 toString 方法,所以改寫 toString 方法求和即可
  fn.toString = function () {
    return args.reduce((prev, item) => prev + item, 0);
  };

  return fn;
};

add(1)(2)(3); // 6

這里只實(shí)現(xiàn)了每次調(diào)用傳入單個(gè)參數(shù),為了支持每次調(diào)用可以傳入多參數(shù),改動(dòng)為:

const add = (...arg1) => {
  let args = [...arg1];
  const fn = (...arg2) => {
    args = [...args, ...arg2];
    return fn;
  };
  fn.toString = function () {
    return args.reduce((prev, item) => prev + item, 0);
  };
  return fn;
};

add(1)(2, 3, 4)(5); // 17

雖然可以正確計(jì)算出結(jié)果,但是如果用 === 把表達(dá)式和結(jié)果進(jìn)行一個(gè)判斷

add(1)(2)(3) === 6; //false
add(1)(2, 3, 4)(5) === 15; //false

無(wú)一例外輸出都是false,其實(shí)并不奇怪,上面代碼中也說(shuō)過,調(diào)用 add 函數(shù)返回的永遠(yuǎn)都是 Function ,這里只是通過修改了 fn 的 toString 方法達(dá)到了輸出計(jì)算結(jié)果的目的,但是這并不能改變返回值的類型,依然是 Function。

反 curry 化

反 curry 化的意義在于擴(kuò)大函數(shù)的適用性,使本來(lái)作為特定對(duì)象所擁有的功能函數(shù)可以被任意對(duì)象所使用。

function Person() {
  this.message = "wowowo";
}

Person.prototype = {
  speak: function () {
    console.log(this.message);
  },
};

Person 實(shí)例均可使用 speak 方法:

new Person().speak();

如果有一個(gè)變量對(duì)象:

const dog = {
  message: "wang wang wang!",
};

該對(duì)象也想使用 Person 原型上的 speak 方法,就需要反 curry 化:

const unCurrySpeak = unCurry(Person.prototype.speak);
unCurrySpeak(dog);

unCurry 就是我們要實(shí)現(xiàn)的反 curry 化的方法。
分析可知: unCurry 的參數(shù)是一個(gè)“希望被其他對(duì)象所調(diào)用的方法”,unCurry 執(zhí)行后返回一個(gè)新的函數(shù),該函數(shù)的第一個(gè)參數(shù)是預(yù)期要執(zhí)行方法的對(duì)象(dog),后面的參數(shù)是執(zhí)行這個(gè)方法時(shí)需要傳遞的參數(shù)。

function unCurry(fn) {
  return function () {
    var obj = [].shift.call(arguments);
    return fn.apply(obj, arguments);
  };
}

如此實(shí)現(xiàn)即可,當(dāng)然也可以將 uncurry 掛載在函數(shù)原型上實(shí)現(xiàn)。

結(jié)束

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容