函数的合成与科里化
函数式编程有两个最基本的运算:合成和柯里化
科里化 Curry
柯里化是一个把具有较多 arity 的函数转换成具有较少 arity 函数的过程。柯里化可以使我们只关心函数的部分参数,使函数的用途更加清晰,调用更加简单。
-
Curried Sum 例子
JavaScriptfunction 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 例子
JavaScriptfunction 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 例子
JavaScriptconst 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 例子(非最优例子)
JavaScriptfunction 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)();
实用例子
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
Discussion
留言与讨论
想法、补充和不同意见都欢迎。