美文网首页
JS中的curry化(柯里化)

JS中的curry化(柯里化)

作者: _BuzzLy | 来源:发表于2020-05-14 16:39 被阅读0次

    什么是 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 挂载在函数原型上实现。

    结束

    相关文章

      网友评论

          本文标题:JS中的curry化(柯里化)

          本文链接:https://www.haomeiwen.com/subject/xxtxohtx.html