怎么理解柯里化
柯里化的意思就是将一个多元函数,转换成一个依次调用的单元函数。
curry 的这种用途可以理解为:参数复用。本质上是降低通用性,提高适用性。
f(a,b,c) → f(a)(b)(c)
var add = function(x) {
return function(y) {
return x + y;
};
};
const increment = add(1);
increment(10); // 11
var { curry } = require('lodash')
var abc = function(a, b, c) {
return [a, b, c];
};
var curried = curry(abc);
console.log(curried(1)(2)(3));
// => [1, 2, 3]
var person = [{name: 'kevin', age: 12 }, {name: 'daisy', age: 13}]
var prop = curry(function (key, obj) {
return obj[key]
});
// 普通方式
var name = person.map(function (item) {
return item.name;
})
// 柯里化方式
var name = person.map(prop('name'))
console.log(name) // [ 'kevin', 'daisy' ]
var age = person.map(prop('age'))
console.log(age) // [ 12, 13 ]
上面代码,我们将返回对象属性的部分通过函数编程实现。
var prop = curry(function (key, obj) {
return obj[key]
});
curry函数将传入的function内的两个参数。变成两个使用一个参数的函数执行。
function(key, obj)
// 根据柯里化转换转换成两个函数,并且第一个参数调用之后返回的还是一个函数,参数为原函数的第二个参数。
props(key) => function(obj)
精髓理解:用闭包把参数保存起来,当参数的数量足够执行函数了,就开始执行函数
手写柯里化
初始版本
var abc = function(a, b, c) {
return [a, b, c];
};
function customerCurry(fn) {
const fnArgsCount = fn.length; // fn函数的形参个数
let curryArgs = []; // 柯里化函数已经传入的实惨个数
function wrapperFunction () {
curryArgs = curryArgs.concat([...arguments])
if (curryArgs.length < fnArgsCount) {
return wrapperFunction;
}
return fn.apply(this, curryArgs)
}
return wrapperFunction;
}
var incre = customerCurry(abc)
incre(1)(2)(3) // 正常返回 [1, 2, 3]
incre() // 返回 [1, 2, 3]
上面的代码可以正常返回,但是还是存在问题。
只能执行一次,incre不能多次执行。这里因为上面书写的方式将参数统一记录下来了。customerCurry参数是最外层必包在维护。所以在incre(1)(2)(3) 执行完之后。内部参数个数已经达到三个。
第二版本
var abc = function(a, b, c) {
return [a, b, c];
};
function customerCurry(fn) {
const fnArgsCount = fn.length; // fn函数的形参个数
let curryArgs = []; // 柯里化函数已经传入的实惨个数
if (fnArgsCount <= 1) {
throw new Error('函数至少传入两个参数');
}
function wrapperFunction () {
curryArgs = [...arguments];
if (curryArgs.length < fnArgsCount) {
return eval(`wrapperFunction.bind(this, ${curryArgs.reduce(function(x, y) {return x + ',' + JSON.stringify(y);})} )`);
}
return fn.apply(this, curryArgs)
}
return wrapperFunction;
}
var incre = customerCurry(abc)
incre(1)(2)(3) // 正常返回 [1, 2, 3]
当然这种还是有缺陷,因为对于参数的传递,这里处理不是很好,使用的是json的方法。
第三版本
var abc = function(a, b, c) {
return [a, b, c];
};
function customerCurry(fn) {
const fnArgsCount = fn.length; // fn函数的形参个数
const curryWrapperArgs = [...arguments].slice(1);
function wrapperFunction () {
let curryArgs = [fn, ...curryWrapperArgs, ...arguments];
if (curryArgs.length < fnArgsCount + 1) {
return customerCurry.apply(this, curryArgs);
}
return fn.apply(this, curryArgs.slice(1))
}
return wrapperFunction;
}
var incre = customerCurry(abc)
incre(1)(2)(3) // 正常返回 [1, 2, 3]
第四版本
这样写起来没什么问题,但是既然学到了柯里化,函数式编程的思想去书写一下。
例子来源:https://github.com/mqyqingfeng/Blog/issues/42
// 第二版
function sub_curry(fn) {
var args = [].slice.call(arguments, 1);
return function() {
return fn.apply(this, args.concat([].slice.call(arguments)));
};
}
function curry(fn, length) {
length = length || fn.length;
var slice = Array.prototype.slice;
return function() {
if (arguments.length < length) {
var combined = [fn].concat(slice.call(arguments));
return curry(sub_curry.apply(this, combined), length - arguments.length);
} else {
return fn.apply(this, arguments);
}
};
}
执行顺序:
换一个方向:
现在我们用函数式编程的思想来理解一下。
第五版本:高颜值写法
var curry = fn =>
judge = (...args) =>
args.length === fn.length
? fn(...args)
: (arg) => judge(...args, arg)
参照
- https://github.com/mqyqingfeng/Blog/issues/42
- https://juejin.cn/post/6844903936378273799#heading-14