谈谈函数式编程curry
Curry概念
The concept is simple: You can call a function with fewer arguments than it expects. It returns a function that takes the remaining arguments.
当你调用一个函数的时候,传入的参数小于函数预期的个数时候它将返回一个新的函数,再来调用剩下的那些参数。
函数预期的参数个数其实就是函数的length
属性所返回的个数。其中不包括(ES6
的剩余参数)。
简单的实现
const add = x => y => x + y;
const increment = add(1);
const addTen = add(10);
increment(2); // 3
addTen(2); // 12
其中关键点就在于利用闭包来将参数收集起来。
curry有什么用
说实话当我第一次学习的时候,那已经是好几个月之前的事情了,当时看了下和上面代码类似的例子,也能看懂概念和原理。不过还是由于处于初学的状态加上没有感受过中大型项目的洗礼(现在也没233)开发经验不足,认为这个curry好像没啥特别的用处嘛。加10我就直接加10好了,加1就直接加1好了,不是本来就十分方便吗,反而这样更麻烦,因此没有加以重视。但是上面只是一个简单的例子从中来解释curry的概念,如果它的用途真的只是用来加法运算那却是是没什么用了。
以下几个例子来源于mostly-adequate-guide chapter04:Curring
const curry = require('lodash').curry;
const match = curry((what, s) => s.match(what));
const replace = curry((what, replacement, s) => s.replace(what, replacement));
const filter = curry((f, xs) => xs.filter(f));
const map = curry((f, xs) => xs.map(f));
策略性地把要操作的数据(String, Array)放到最后一个参数里。到使用它们的时候你就明白这样做的原因是什么了。
match(/r/g, 'hello world'); // [ 'r' ]
const hasLetterR = match(/r/g); // x => x.match(/r/g)
hasLetterR('hello world'); // [ 'r' ]
hasLetterR('just j and s and t etc'); // null
filter(hasLetterR, ['rock and roll', 'smooth jazz']); // ['rock and roll']
const removeStringsWithoutRs = filter(hasLetterR); // xs => xs.filter(x => x.match(/r/
g))
removeStringsWithoutRs(['rock and roll', 'smooth jazz', 'drum circle']); // ['rock and
roll', 'drum circle']
const noVowels = replace(/[aeiou]/ig); // (r,x) => x.replace(/[aeiou]/ig, r)
const censored = noVowels('*'); // x => x.replace(/[aeiou]/ig, '*')
censored('Chocolate Rain'); // 'Ch*c*l*t* R**n'
通过收集参数,传递小于函数预期个数的参数给调用函数,就能得到一个记住了这些参数的新函数。
这样通过不断地部分参数的传递得到新的能够通用的函数,就能将代码更好地复用。
成长有限,目前还是没能实际使用编写过函数式编程的代码,但也能确确实实地感受到它的价值了,也算是一种进步了。
讲讲curry的实现
lodash.js curry
var abc = function(a, b, c) {
return [a, b, c];
};
var curried = _.curry(abc);
curried(1)(2)(3);
// => [1, 2, 3]
curried(1, 2)(3);
// => [1, 2, 3]
curried(1, 2, 3);
// => [1, 2, 3]
curry使用
- 当我们传递的参数个数等于目标函数的预期个数的时候直接返回结果
- 当传递的参数小于预期函数的时候,返回一个新的函数,这个函数此时已经收集了之前传递进去的所有参数了,现在你只要将剩余的参数传入即可
实现的关键点
- 预期函数的个数,实际就是
function.length
- 返回新的包含之前参数的函数。实际就是利用
Function.prototype.bind
主体功能实现:
function curry(targetfn) {
// 预期参数个数
var numOfArgs = targetfn.length;
return function curried() {
// 参数个数小于的话把之前传入的参数收集起来,再次返回curry化的函数
if (arguments.length < numOfArgs) {
return curried.bind(null, ...arguments);
// without ES6
// return Function.prototype.bind.apply(curried, [null].concat(Array.prototype.slice.call(arguments)));
} else {
return targetfn.apply(null, arguments);
}
}
}
这种方法利用的是闭包,将targetfn
和numOfArgs
私有化,变成内部变量
当传递的参数等于numOfArgs
时候,调用targetfn