curry函数在JavaScript函数式编程中十分重要。在网上搜索该函数,现有的基本上都不是我想要的;分析ramda,lodash等JavaScript函数式库,发现该函数的实现十分复杂,一时半会摸不清头绪。于是昨天晚上花了几个小时,自己实现了该函数,今天把它拿出来让大家参考一下。
柯里化函数
柯里化概念
所谓的柯里化就是把一个多参数的函数转换为一个嵌套的单参数函数的过程,它要求使用部分参数时返回一个新的函数,
在真正运行之前等待外部提供剩余参数,当参数准备完备后执行最初的多参数函数。
柯里化函数应该满足的要求
- 从柯里化的概念我们可以得出柯里化函数(即curry函数)应该满足以下要求:
- curry函数接受一个多参数函数作为参数
- curry调用后返回一个被柯里化的函数
- 被柯里化的函数调用时一次可以传入一个参数,也可以传入多个参数
- 被柯里化的函数调用后如果没有得到足够的参数,那么会返回一个新的函数
- 被柯里化的函数调用后如果得到了足够的参数,那会就会执行最初的那个多参数函数。
一个简单的柯里化函数实例
- 知道柯里化的概念,和柯里化函数应满足的要求后,我们实现一个简单的柯里化函数,来为后面真正curry函数的实现打下基础。
- 假设我们有一个函数sum,它的作用是求两个数字的和。sum函数的实现如下
const sum = (x, y) => x + y;
- 实现简单的curry函数:
const curry = (fn) => { return function recursive(...args) { // 如果args.length >= fn.length则表明传入了足够的参数,此时调用fn并返回 if (args.length >= fn.length) { return fn(...args); } // 否则表明没有传入足够的参数,此时返回一个函数,用这个函数接受后面传递的新参数 return (...newArgs) => { // 递归调用recursive函数,并返回 return recursive(...args.concat(newArgs)); }; }; };
- 将sum函数柯里化,并测试
const cSum = curry(sum); // => [Function] const cSum1 = cSum(1); //=> [Function] cSum1(2); // => 3
- 到此为止我们实现了一个简单的柯里化实例。那么我们用来柯里化sum的curry函数在执行过程中到底做了什么呢?
- 先让我们打印输出一下
console.log(cSum.toString());
发现得到下面的结果:
于是我们可以知道执行function recursive(...args) { // 如果args.length >= fn.length则表明传入了足够的参数,此时调用fn并返回 if (args.length >= fn.length) { return fn(...args); } // 否则表明没有传入足够的参数,此时返回一个函数,用这个函数接受后面传递的新的参数 return (...newArgs) => { // 递归调用recursive函数,并返回 return recursive(...args.concat(newArgs)); }; }
curry(sum)
返回了一个函数 - 再让我们打印输出一下
console.log(cSum(1).toString());
发现得到下面的结果:
于是我们可以知道调用(...newArgs) => { // 递归调用recursive函数,并返回 return recursive(...args.concat(newArgs)); }
cSum(1)
时参数没有准备完备,返回了一个函数,这个函数内部递归调用了recursive函数,并且recursive函数的参数是1
和我们调用cSum1
时传入的参数。 - 当我们执行
cSum1(2)
得到了结果3
,这表明参数准备完备了,这时执行了下面的代码,从而得到了我们想要的结果。// 如果args.length >= fn.length则表明传入了足够的参数,此时调用fn并返回 if (args.length >= fn.length) { return fn(...args); }
- 先让我们打印输出一下
柯里化函数的实现
我们要实现什么?
上面我们实现了一个简单的curry函数,用来柯里化sum函数;但实际情况要复杂的多。我们要柯里化的函数的参数是不确定的,有时也希望能够通过占位符表示将要传递的参数;因此我们真正要实现的curry函数将满足以下两个条件:
- 可以柯里化具有任意数量参数的函数
- 可以通过占位符表示将要传递的参数
实现真正的curry函数
一、占位符
- 这里我们通过
__
(双下划线)来表示占位符,它的值是一个对象,具体如下所示:const __ = { '@@/placeHolder': true}; // curry 函数的占位符
- 判断一个标识符是否是占位符,当一个标识符不为
null
,且是一个对象,且对象中的'@@/placeHolder'
属性全等于true
则认为它是一个占位符。通过函数isPlaceHolder
来实现该功能: