1213 字
6 分钟
函数柯里化
柯里化
在计算机科学中,柯里化(Currying)是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数且返回结果的新函数的技术。
例如需要实现以下得示例
add(1, 2, 3) // 6add(1) // 1add(1)(2) // 3add(1, 2)(3) // 6add(1)(2)(3) // 6add(1)(2)(3)(4) // 10本来有这么一个求和函数dynamicAdd(),接受任意个参数。
function dynamicAdd() { return [...arguments].reduce((prev, curr) => { return prev + curr }, 0)}现在需要通过柯里化把它变成一个新的函数,这个新的函数预置了第一个参数,并且可以在调用时继续传入剩余参数。
function curry(fn,firstArg){ // 返回一个新函数 return function(){ // 将arguments转化为真正数组 var restArgs = Array.from(arguments); return fn.apply(this,[firstArg,...restArgs]) }}
// 柯里化,预置参数10var add10 = curry(dynamicAdd, 10)add10(5); // 15// 柯里化,预置参数20var add20 = curry(dynamicAdd, 20);add20(5); // 25// 也可以对一个已经柯里化的函数add10继续柯里化,此时预置参数10即可var anotherAdd20 = curry(add10, 10);anotherAdd20(5); // 25柯里化是在一个函数的基础上进行变换,得到一个新的预置了参数的函数。最后在调用新函数时,实际上还是会调用柯里化前的原函数。 并且柯里化得到的新函数可以继续被柯里化
实际使用时也会出现柯里化的变体,不局限于只预置一个参数。
function curry(fn) { // 保存预置参数 var presetArgs = [].slice.call(arguments, 1) // 返回一个新函数 return function() { // 新函数调用时会继续传参 var restArgs = [].slice.call(arguments) // 参数合并,通过apply调用原函数 return fn.apply(this, [...presetArgs, ...restArgs]) }}1. 参数定长的柯里化
假设存在一个原函数fn,fn接受三个参数a, b, c,那么函数fn最多被柯里化三次(有效地绑定参数算一次)。
function fn(a, b, c) { return a + b + c}var c1 = curry(fn, 1);var c2 = curry(c1, 2);var c3 = curry(c2, 3);c3(); // 6// 再次柯里化也没有意义,原函数只需要三个参数var c4 = curry(c3, 4);c4();也就是说,我们可以通过柯里化缓存的参数数量,来判断是否到达了执行时机。那么我们就得到了一个柯里化的通用模式。
function curry(fn) { // 获取原函数的参数长度 const argLen = fn.length; // 保存预置参数 const presetArgs = [].slice.call(arguments, 1) // 返回一个新函数 return function() { // 新函数调用时会继续传参 const restArgs = [].slice.call(arguments) const allArgs = [...presetArgs, ...restArgs] if (allArgs.length >= argLen) { // 如果参数够了,就执行原函数 return fn.apply(this, allArgs) } else { // 否则继续柯里化 return curry.call(null, fn, ...allArgs) } }}function fn(a,b,c){ return a+b+c;}var curried = curry(fn);curried(1,2,3);curried(1,2)(3);curried(1)(2,3);curried(1)(2)(3);2. 参数不定长地柯里化
在参数不定长的情况下,要同时支持1~N次调用还是挺难
如果要支持参数不定长的场景,已经柯里化的函数在执行完毕时不能返回一个值,只能返回一个函数;同时要让JS引擎在解析得到的这个结果时,能求出我们预期的值
-
经
curry处理,得到一个新函数,这一点不变。
// curry是一个函数 var curried = curry(add);
```2. 新函数执行后仍然返回一个结果函数。
// curried10也是一个函数var curried10 = curried(10);var curried30 = curried10(20);- 结果函数可以被Javascript引擎解析,得到一个预期的值。
curried10; // 10
关键点在于3,如何让Javascript引擎按我们的预期进行解析,这就回到Javascript基础了。在解析一个函数的原始值时,会用到toString
function curry(fn) { // 保存预置参数 const presetArgs = [].slice.call(arguments, 1) // 返回一个新函数 function curried () { // 新函数调用时会继续传参 const restArgs = [].slice.call(arguments) const allArgs = [...presetArgs, ...restArgs] return curry.call(null, fn, ...allArgs) } // 重写toString curried.toString = function() { return fn.apply(null, presetArgs) } return curried;}总结
柯里化是一种函数式编程思想,实际上在项目中可能用得少,或者说用得不深入,但是如果你掌握了这种思想,也许在未来的某个时间点,你会用得上!
大概来说,柯里化有如下特点:
- 简洁代码:柯里化应用在较复杂的场景中,有简洁代码,可读性高的优点。
- 参数复用:公共的参数已经通过柯里化预置了。
- 延迟执行:柯里化时只是返回一个预置参数的新函数,并没有立刻执行,实际上在满足条件后才会执行。
- 管道式流水线编程:利于使用函数组装管道式的流水线工序,不污染原函数。