返回专题

专题 2018

函数的合成与科里化

函数的合成与科里化 相关笔记

JavaScript原理解析面试题
本文目录5
  1. 函数的合成与科里化
  2. 科里化 Curry
  3. 合成 Compose
  4. 实用例子
  5. Reference

函数的合成与科里化

函数式编程有两个最基本的运算:合成和柯里化

科里化 Curry

柯里化是一个把具有较多 arity 的函数转换成具有较少 arity 函数的过程。柯里化可以使我们只关心函数的部分参数,使函数的用途更加清晰,调用更加简单。

  • Curried Sum 例子

    JavaScript
    function curriedSum(...args) {
      // 在第一次调用时维护一个变量
      let params = args;
      /**
       * 定义一个新的方法, 并返回:
       * 1. 更新缓存的变量, 之后计算
       * 2. 返回当前方法, 支持链式调用
       * 3. 提供结果计算方法
       */
      function tempSum() {
        params = [...params, ...arguments];
        return tempSum;
      }
      // 定义取值方法
      tempSum.count = function() {
        return params.reduce((acc, item) => {
          return acc + item;
        });
      };
      return tempSum;
    }
    console.log(curriedSum(1)(2)(3)(10)(10, 20).count());
    
  • Curry 例子

    JavaScript
    function curry(func) {
      // 返回一个方法, 根据情况返回执行结果或者返回偏函数
      return function curried(...args) {
        // 这里使用 func.length 判断函数参数的长度, 所以方法要要求固定参数长度, ...args 不支持
        if (args.length >= func.length) {
          return func.apply(this, args);
        } else {
          return function(...nextArgs) {
            return curried.apply(this, [...args, ...nextArgs]);
          };
        }
      };
    }
    
    function add(x, y) {
      return x + y;
    }
    
    const curriedAdd = curry(add);
    console.log(curriedAdd(1));
    console.log(curriedAdd(1)(2));
    

合成 Compose

将多个函数的能力合并,创造一个新的函数。compsoe函数可以接受任意的参数,所有的参数都是函数,且执行方向是自右向左的,初始函数一定放到参数的最右面。

  • Compose 例子

    JavaScript
    const compose = (...fns) => {
      return (...initParam) => {
        return fns.reverse().reduce((prev, current) => {
          // 这里传递的是每个函数执行的结果, 也就是下一个函数执行的参数
          return [current.apply(this, prev)];
        }, initParam);
      };
    };
    
    function compose2() {
      var fns = [].slice.call(arguments);
      return function(...initialArg) {
        // 这里同上缓存的是当前函数执行的结果, 也就是下一个函数执行的参数
        var res = initialArg;
        for (var i = fns.length - 1; i > -1; i--) {
          res = [fns[i].apply(this, res)];
        }
        return res;
      };
    }
    
    const compose3 = (...args) => {
      // 这里直接返回递归闭包的方法, 对内存有一定的消耗, 不建议这么使用
      return args.reverse().reduce((prev, current) => {
        return (...arg) => {
          return current.call(this, prev.apply(this, arg));
        };
      }, args.shift());
    };
    
    function fn1(params) {
      console.log('fn1', params);
      return 'fn1 return';
    }
    
    function fn2(params) {
      console.log('fn2', params);
      return 'fn2 return';
    }
    
    function fn3(...params) {
      console.log('fn3', ...params);
      return 'fn3 return';
    }
    compose3(fn1, fn2, fn3)('init input', 'init input2');
    
  • Compose Promise 例子(非最优例子)

    JavaScript
    function fn1() {
      return new Promise(resolve => {
        setTimeout(() => {
          console.log('fn1');
          resolve();
        }, 1000);
      });
    }
    function fn2() {
      return new Promise(resolve => {
        setTimeout(() => {
          console.log('fn2');
          resolve();
        }, 1000);
      });
    }
    function fn3() {
      return new Promise(resolve => {
        setTimeout(() => {
          console.log('fn3');
          resolve();
        }, 1000);
      });
    }
    
    const reduceFunc = (f, g) => {
      return (...arg) => {
        return f.apply(this, arg).then(res => g.call(this, res));
      };
    };
    
    const composePromise = (...args) => {
      // 这里直接返回递归闭包的方法, 对内存有一定的消耗, 不建议这么使用
      return args.reverse().reduce(reduceFunc, args.shift());
    };
    
    composePromise(fn1, fn2, fn3)();
    

实用例子

Koa middleware(koa-compose)

Reference

函数式编程入门教程

你可能听说过函数式编程(Functional programming),甚至已经使用了一段时间。 但是,你能说清楚,它到底是什么吗? 网上搜索一下,你会轻松找到好多答案。 与面向对象编程(Object-oriented programming)和过程式编程(Procedural programming)并列的编程范式。 最主要的特征是,函数是第一等公民。 强调将计算过程分解成可复用的函数,典型例子就是map方法和reduce方法组合而成 MapReduce 算法。 只有纯的、没有副作用的函数,才是合格的函数。 上面这些说法都对,但还不够,都没有回答下面这个更深层的问题。 这就是,本文要解答的问题。我会通过最简单的语言,帮你理解函数式编程,并且学会它那些基本写法。 需要声明的是,我不是专家,而是一个初学者,最近两年才真正开始学习函数式编程。一直苦于看不懂各种资料,立志要写一篇清晰易懂的教程。下面的内容肯定不够严密,甚至可能包含错误,但是我发现,像下面这样解释,初学者最容易懂。 另外,本文比较长,阅读时请保持耐心。结尾还有 Udacity 的 《前端工程师认证课程》 的推广,非常感谢他们对本文的赞助。 函数式编程的起源,是一门叫做范畴论(Category Theory)的数学分支。 理解函数式编程的关键,就是理解范畴论。它是一门很复杂的数学,认为世界上所有的概念体系,都可以抽象成一个个的"范畴"(category)。 什么是范畴呢? 维基百科 的一句话定义如下。 "范畴就是使用箭头连接的物体。"(In mathematics, a category is an algebraic structure that comprises "objects" that are linked by "arrows".
https://ruanyifeng.com/blog/2017/02/fp-tutorial.html

返回首页
上一篇Building a Debugger- Code Analysis下一篇DefineProperty

Discussion

留言与讨论

想法、补充和不同意见都欢迎。