开篇
你是否曾经为了函数的参数数量和顺序而感到困扰?
你是否曾经为了函数的复杂度而感到头疼?
如果是的话,那么柯里化函数
可能会成为你的救星!
柯里化函数是一种将多个参数的函数转换成一系列单参数函数的技术,可以让你的代码更加优雅和灵活。
接下来,让我们一起来探讨一下如何用柯里化函数让你的代码更加出色吧!
概念
柯里化函数(Currying Function)
是一种函数式编程技术,它将多个参数的函数转换为接收单一参数(最初函数的第一个参数)的一系列函数。
核心思想
柯里化函数的核心思想是
函数的“延迟执行”
,它能够将一个函数的参数逐个收集起来,直到所有的参数都收集齐了才开始执行函数
。柯里化函数使得函数的可组合性更强,同时也支持给部分参数赋值的情况下调用函数。这种方式能够使得代码更加灵活,易于重用
。
实现
柯里化函数
的实现方式通常有两种:
通用的柯里化函数
通用的柯里化函数能够自适应接收不同数量参数的函数
,例如:
function currying(fn, ...args) {if (args.length >= fn.length) {return fn(...args);} else {return function(...args2) {return currying(fn, ...args, ...args2);}}}function currying(fn, ...args) { if (args.length >= fn.length) { return fn(...args); } else { return function(...args2) { return currying(fn, ...args, ...args2); } } }function currying(fn, ...args) { if (args.length >= fn.length) { return fn(...args); } else { return function(...args2) { return currying(fn, ...args, ...args2); } } }
这个函数接收一个函数
和需要传递给该函数的参数列表
,如果参数列表足够原函数的参数数量就直接返回原函数的执行结果,否则就返回一个再次调用 currying
函数的新函数。
针对特定函数的柯里化函数
有些时候我们需要只针对特定函数进行柯里化操作,例如:
function add(x, y) {return x + y;}const curryAdd = x => y => x + y;function add(x, y) { return x + y; } const curryAdd = x => y => x + y;function add(x, y) { return x + y; } const curryAdd = x => y => x + y;
这个函数定义了一个接收两个参数的 add 函数
和一个只接收一个参数的 curryAdd 函数
。curryAdd
实际上是一个柯里化函数,它将 add 函数的第一个参数 x 绑定在自己的作用域中,然后返回一个需要单一参数 y 的函数。
以 JavaScript
语言为例,柯里化函数可以通过以下方式实现:
function add(x) {return function(y) {return x + y;}}const add5 = add(5);console.log(add5(3)); // 8console.log(add5(5));// 10function add(x) { return function(y) { return x + y; } } const add5 = add(5); console.log(add5(3)); // 8 console.log(add5(5));// 10function add(x) { return function(y) { return x + y; } } const add5 = add(5); console.log(add5(3)); // 8 console.log(add5(5));// 10
在上面的例子中,我们定义了一个 add 函数
,这个函数接收一个参数 x
,然后返回一个接收参数 y
的函数,这个函数将 x
和 y
相加并返回结果。
在调用 add 函数时,我们首先传递一个参数 5 给它,然后将返回的函数赋值给变量 add5,之后我们用 add5 来调用这个函数来计算不同的 y 值的和。
通过这种方式,我们可以把一个需要多个参数的函数变成一系列只需要一个参数的单一功能函数。
优点
简化多参数函数调用
柯里化函数可以将多个参数的函数
简化成单一参数的函数
,从而使得函数调用更加简洁和易于理解。
例如,对于一个求和函数:
function sum(x, y, z) {return x + y + z;}function sum(x, y, z) { return x + y + z; }function sum(x, y, z) { return x + y + z; }
我们可以使用柯里化来将其转化为一系列单一参数函数
:
const curriedSum = x => y => z => x + y + z;const curriedSum = x => y => z => x + y + z;const curriedSum = x => y => z => x + y + z;
然后我们可以使用以下方式调用:
curriedSum(1)(2)(3); // 6curriedSum(1)(2)(3); // 6curriedSum(1)(2)(3); // 6
更加灵活的函数组合
柯里化函数使得函数组合的方式更加灵活。例如,我们可以通过柯里化函数的方式将多个函数组合在一起,例如:
const add = x => y => x + y;const multiply = x => y => x * y;const addAndMultiply = x => add(2)(multiply(3)(x));addAndMultiply(4); // 14const add = x => y => x + y; const multiply = x => y => x * y; const addAndMultiply = x => add(2)(multiply(3)(x)); addAndMultiply(4); // 14const add = x => y => x + y; const multiply = x => y => x * y; const addAndMultiply = x => add(2)(multiply(3)(x)); addAndMultiply(4); // 14
支持延迟执行
柯里化函数支持延迟执行,因为它们只会执行到所有参数都被收集完毕才会执行。这种方式能够提高函数的性能和可复用性。
例如:
function add(x, y) {return x + y;}function curry(fn) {return function curried() {const args = Array.from(arguments);if (args.length >= fn.length) {return fn.apply(null, args);} else {return function() {const newArgs = Array.from(arguments);return curried.apply(null, args.concat(newArgs));}}}}const lazyAdd = curry(add);const result1 = lazyAdd(1)(2); // 不执行const result2 = lazyAdd(1, 2); // 执行console.log(result1); // 返回一个函数console.log(result2); // 返回结果 3function add(x, y) { return x + y; } function curry(fn) { return function curried() { const args = Array.from(arguments); if (args.length >= fn.length) { return fn.apply(null, args); } else { return function() { const newArgs = Array.from(arguments); return curried.apply(null, args.concat(newArgs)); } } } } const lazyAdd = curry(add); const result1 = lazyAdd(1)(2); // 不执行 const result2 = lazyAdd(1, 2); // 执行 console.log(result1); // 返回一个函数 console.log(result2); // 返回结果 3function add(x, y) { return x + y; } function curry(fn) { return function curried() { const args = Array.from(arguments); if (args.length >= fn.length) { return fn.apply(null, args); } else { return function() { const newArgs = Array.from(arguments); return curried.apply(null, args.concat(newArgs)); } } } } const lazyAdd = curry(add); const result1 = lazyAdd(1)(2); // 不执行 const result2 = lazyAdd(1, 2); // 执行 console.log(result1); // 返回一个函数 console.log(result2); // 返回结果 3
上面的例子中,我们使用柯里化函数 lazyAdd
对 add
函数进行了包装,使其能够支持延迟执行。当我们传递一个参数时,它不会马上执行 add
函数,而是返回一个新函数去等待接收下一个参数。直到满足执行条件后再一次性执行。而当我们传递足够的参数时,它会立即执行 add
函数并返回结果。
支持函数组合
柯里化函数支持函数组合,这样能够更加方便地进行函数的组合和重用,同时也使得代码更加简洁和易于维护。
例如:
function add(x, y) {return x + y;}function multiply(x, y) {return x * y;}const compose = (...fns) => {return function(result) {return fns.reduceRight((result, fn) => fn(result), result);}}const curriedAdd = x => y => add(x, y);const curriedMultiply = x => y => multiply(x, y);const composed = compose(curriedAdd(2), curriedMultiply(3));console.log(composed(4)); // 返回 14function add(x, y) { return x + y; } function multiply(x, y) { return x * y; } const compose = (...fns) => { return function(result) { return fns.reduceRight((result, fn) => fn(result), result); } } const curriedAdd = x => y => add(x, y); const curriedMultiply = x => y => multiply(x, y); const composed = compose(curriedAdd(2), curriedMultiply(3)); console.log(composed(4)); // 返回 14function add(x, y) { return x + y; } function multiply(x, y) { return x * y; } const compose = (...fns) => { return function(result) { return fns.reduceRight((result, fn) => fn(result), result); } } const curriedAdd = x => y => add(x, y); const curriedMultiply = x => y => multiply(x, y); const composed = compose(curriedAdd(2), curriedMultiply(3)); console.log(composed(4)); // 返回 14
上面的例子中,我们使用柯里化函数 curriedAdd
和 curriedMultiply
将 add
和 multiply
函数进行了包装。然后使用 compose
函数将这两个函数组合在一起,以实现更加复杂的功能。当我们传递一个参数时,柯里化函数会先收集参数,然后组成一个新的函数,直到接收到足够的参数后再执行。
使用场景
柯里化函数在以下场景中有很广泛的应用:
需要部分应用的函数
当一个函数需要多个参数时,柯里化函数能够支持部分应用,这样能够提高函数的可重用性和可维护性。
例如,假设我们有如下的 add 函数:
function add(x, y, z) {return x + y + z;}function add(x, y, z) { return x + y + z; }function add(x, y, z) { return x + y + z; }
我们可以通过如下方式来进行部分应用和柯里化来生成新的函数:
const add5 = currying(add, 5);const add5And6 = currying(add5, 6);const add5And6And7 = currying(add5And6, 7);add5And6And7(); // 18const add5 = currying(add, 5); const add5And6 = currying(add5, 6); const add5And6And7 = currying(add5And6, 7); add5And6And7(); // 18const add5 = currying(add, 5); const add5And6 = currying(add5, 6); const add5And6And7 = currying(add5And6, 7); add5And6And7(); // 18
函数式编程中的高阶函数
函数式编程中大量使用高阶函数,柯里化函数能够帮助我们更好地使用高阶函数,例如:
const compose = (...fns) => x => fns.reduceRight((y, f) => f(y), x);const add = x => y => x + y;const multiply = x => y => x * y;const addAndMultiply = compose(multiply(2),add(3));addAndMultiply(4); // 14const compose = (...fns) => x => fns.reduceRight((y, f) => f(y), x); const add = x => y => x + y; const multiply = x => y => x * y; const addAndMultiply = compose( multiply(2), add(3) ); addAndMultiply(4); // 14const compose = (...fns) => x => fns.reduceRight((y, f) => f(y), x); const add = x => y => x + y; const multiply = x => y => x * y; const addAndMultiply = compose( multiply(2), add(3) ); addAndMultiply(4); // 14
这个例子演示了使用柯里化函数来使用组合模式。
链式调用
通过使用柯里化函数,我们可以将函数的调用转化成链式调用。
例如:
const Person = function(name) {let _name = name;return {getName: () => _name,setName: newName => {_name = newName;return this;}};};const person = new Person('jack');person.setName('jane').setName('tom').getName(); // 'tom'const Person = function(name) { let _name = name; return { getName: () => _name, setName: newName => { _name = newName; return this; } }; }; const person = new Person('jack'); person.setName('jane').setName('tom').getName(); // 'tom'const Person = function(name) { let _name = name; return { getName: () => _name, setName: newName => { _name = newName; return this; } }; }; const person = new Person('jack'); person.setName('jane').setName('tom').getName(); // 'tom'
在这个例子中,通过使用柯里化函数的方式把 setName
函数“导出”到了 Person 对象
的原型上,并且使得函数在返回对象的同时,又能够链式调用。这种方式相对于单一的函数调用来说,使得代码更加优雅和易于维护。
拓展
除了基本的柯里化函数,还有一些其他高阶写法可以在柯里化的基础上进一步扩展其功能和灵活性,例如偏函数、反柯里化和函数绑定
等。
偏函数(Partial Application)
偏函数是指使用柯里化函数对函数的一部分参数进行预处理,并返回一个函数来完成余下的工作
。
例如,假设我们有一个计算税收的函数:
function calculateTax(rate, price) {return rate * price;}function calculateTax(rate, price) { return rate * price; }function calculateTax(rate, price) { return rate * price; }
其中 rate
是税率,price
是商品价格。我们可以通过偏函数的方式来为这个函数前置一部分参数:
const calculate5PercentTax = currying(calculateTax, 0.05);console.log(calculate5PercentTax(100)); // 5const calculate5PercentTax = currying(calculateTax, 0.05); console.log(calculate5PercentTax(100)); // 5const calculate5PercentTax = currying(calculateTax, 0.05); console.log(calculate5PercentTax(100)); // 5
通过这种方式,我们可以创建一个新的函数来计算 5% 的税收。
反柯里化(Uncurrying)
反柯里化是指将柯里化函数还原成原函数的形式,使其能够接收多个参数
。
例如,假设我们有一个柯里化函数:
const add = x => y => x + y;const add = x => y => x + y;const add = x => y => x + y;
我们可以通过反柯里化的方式将其还原成原函数的形式:
function uncurrying(fn) {return function() {const args = Array.from(arguments);return args.reduce((pre, cur) => pre(cur), fn);}}const add2 = uncurrying(add);console.log(add2(1, 2)); // 3function uncurrying(fn) { return function() { const args = Array.from(arguments); return args.reduce((pre, cur) => pre(cur), fn); } } const add2 = uncurrying(add); console.log(add2(1, 2)); // 3function uncurrying(fn) { return function() { const args = Array.from(arguments); return args.reduce((pre, cur) => pre(cur), fn); } } const add2 = uncurrying(add); console.log(add2(1, 2)); // 3
通过这种方式,我们可以将柯里化函数还原成可以接收多个参数的形式。
函数绑定(Function Binding)
函数绑定是指将某个函数绑定到特定对象上,并且让这个函数的 this 指向该对象
。
例如,假设我们有一个对象:
const person = {name: 'jack',sayName() {console.log(this.name);}};const person = { name: 'jack', sayName() { console.log(this.name); } };const person = { name: 'jack', sayName() { console.log(this.name); } };
我们可以使用函数绑定的方式来创建一个新的函数,将其 this 指向 person 对象
:
const sayPersonName = person.sayName.bind(person);sayPersonName(); // 'jack'const sayPersonName = person.sayName.bind(person); sayPersonName(); // 'jack'const sayPersonName = person.sayName.bind(person); sayPersonName(); // 'jack'
通过这种方式,我们可以在不改变原有函数的情况下,将其绑定到特定对象上,从而使函数更加灵活和易于使用。
组合函数(Compose)
组合函数是指将多个函数组合到一起以达到某种效果的函数
。
例如,假设我们有如下两个函数:
function double(x) {return x * 2;}function add(x, y) {return x + y;}function double(x) { return x * 2; } function add(x, y) { return x + y; }function double(x) { return x * 2; } function add(x, y) { return x + y; }
我们可以使用组合函数的方式将这两个函数组合到一起:
const doubleAndAdd5 = compose(add(5),double);doubleAndAdd5(2) // 9const doubleAndAdd5 = compose( add(5), double ); doubleAndAdd5(2) // 9const doubleAndAdd5 = compose( add(5), double ); doubleAndAdd5(2) // 9
通过 compose
函数,我们可以对多个函数进行组合,实现更加复杂的功能。
惰性求值(Lazy Evaluation)
惰性求值是指在需要的时候才进行计算,这种方式能够提高程序的性能和响应速度
。
例如,假设我们有如下的函数:
function add(x, y) {return x + y;}function add(x, y) { return x + y; }function add(x, y) { return x + y; }
我们可以使用惰性求值的方式将其转化成柯里化函数:
const add = x => {return function(y) {if (y !== undefined) {return x + y;} else {return function(y) {return x + y;}}};};const add = x => { return function(y) { if (y !== undefined) { return x + y; } else { return function(y) { return x + y; } } }; };const add = x => { return function(y) { if (y !== undefined) { return x + y; } else { return function(y) { return x + y; } } }; };
通过这种方式,我们可以在真正需要计算结果的时候再进行计算,从而避免了不必要的计算,提高了程序的性能。
总结
通过柯里化函数,我们可以将一个接受多个参数的函数转换成一系列只接受一个参数的函数,从而让我们更加灵活地使用函数,并且可以让我们的代码更加简洁和优雅。同时,柯里化函数还可以让我们更加方便地进行函数组合和函数复合,从而实现更加复杂的功能。总之,柯里化函数是一种非常有用的技术,可以让我们的代码更加出色。如果你还没有尝试过柯里化函数,那么赶快学习一下吧,相信它一定会让你的代码更加优雅和灵活!