JS中的curry化(柯里化)

2020-05-14  本文已影响0人  _BuzzLy

什么是 curry 化

curry 化也是一个常见的概念,维基百科对其解释为:

在计算机科学中,柯里化(currying),又译为卡瑞化或加里化,是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数而且返回结果的新函数的技术。这个技术由克里斯托弗·斯特雷奇以逻辑学家哈斯凯尔·加里命名的。

再简洁一些就是:柯里化是把接受多个参数的函数变换成接受一个单一参数的函数,并且返回接受余下的参数而且返回结果的新函数的技术。

还是不懂,没关系,下面就通过几个例子一步一步去了解柯里化。

怎么实现 curry 化

实现一个函数,对数组进行过滤,过滤掉小于10的项。
传统的做法是:

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;
};

实现起来并没有难度,但是当前要过滤的是小于10的项,如果这个阈值更改了呢,我们可以借用 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]

// 也可以这样简写
// filterLowerNumber(10)([1, 11, 8, 21, 2]);

另一个场景
实现一个求两数之和得方法
普通函数:

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

curry 化函数:

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

在此基础上提交更复杂得要求
按要求实现 add 方法:

add(1)(2); // 结果为3
add(1)(3)(5); // 结果为9
add(1)...(n); // 结果为sum

大家可能比较眼熟哈,很多 curry 化的面试题都是以此为原型的。

解题:

  1. 由调用方式可知,add 函数每次执行后一定返回一个函数,以供后续调用,且返回的函数依然要返回自身,供多级调用;
  2. 当最后一次调用结束,返回的是一个函数,为了满足题意,需要改写内部返回的函数 toString (代码中也解释);
  3. 为了进行求和,需要在 add 函数内部维护一个闭包变量 args,args 是个数组,存放了第一次调用 add 和 后续调用 fn 函数时传入的参数;
  4. 在调用 fn 的 toString 方法时,意味着最后一次调用结束,返回函数,那么就计算 args 数组中的所有值得和即可求出结果。
const add = (arg1) => {
  let args = [arg1];

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

  // 因为最后一次执行完毕后会返回 fn 函数体,相当于调用了 fn 的 toString 方法,所以改写 toString 方法求和即可
  fn.toString = function () {
    return args.reduce((prev, item) => prev + item, 0);
  };

  return fn;
};

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

这里只实现了每次调用传入单个参数,为了支持每次调用可以传入多参数,改动为:

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

虽然可以正确计算出结果,但是如果用 === 把表达式和结果进行一个判断

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

无一例外输出都是false,其实并不奇怪,上面代码中也说过,调用 add 函数返回的永远都是 Function ,这里只是通过修改了 fn 的 toString 方法达到了输出计算结果的目的,但是这并不能改变返回值的类型,依然是 Function。

反 curry 化

反 curry 化的意义在于扩大函数的适用性,使本来作为特定对象所拥有的功能函数可以被任意对象所使用。

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

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

Person 实例均可使用 speak 方法:

new Person().speak();

如果有一个变量对象:

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

该对象也想使用 Person 原型上的 speak 方法,就需要反 curry 化:

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

unCurry 就是我们要实现的反 curry 化的方法。
分析可知: unCurry 的参数是一个“希望被其他对象所调用的方法”,unCurry 执行后返回一个新的函数,该函数的第一个参数是预期要执行方法的对象(dog),后面的参数是执行这个方法时需要传递的参数。

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

如此实现即可,当然也可以将 uncurry 挂载在函数原型上实现。

结束

上一篇下一篇

猜你喜欢

热点阅读