Logo
Section 1 / 2

函数式编程的秘诀

深入探讨柯里化及其优势。(5分钟阅读)

导入

函数式编程的秘诀

深入探讨柯里化及其优势。(5分钟阅读)

柯里化

我希望上一节让你对柯里化有了一些初步了解,现在我们再深入一点。

其主要优势在于代码复用。柯里化函数在不同场景下更有可能被重用,从而让代码更加 DRY(减少重复)。

假设你有一个 add 函数:

1const add = (x, y) => { 2 console.log(`Adding ${x} and ${y}`); 3 4 return x + y; 5}; 6 7const result = add(2, 3); 8 9console.log({ result });

如果你不给 add 提供所需的参数,或者提供的参数太少,会发生什么?

1const add = (x, y) => { 2 console.log(`Adding ${x} and ${y}`); 3 4 return x + y; 5}; 6 7const result = add(2); 8 9console.log({ result });

这显然不太理想。你知道这会导致奇怪的错误,尤其是在 JavaScript 这种灵活的语言中,各种情况都可能发生。而当你对 add 进行柯里化后,这个问题就缓解了。

1import { curry } from 'ramda'; 2 3const add = (x, y) => { 4 console.log(`Adding ${x} and ${y}`); 5 6 return x + y; 7}; 8 9const curriedAdd = curry(add); 10 11// 返回一个函数 12const result = curriedAdd(2); 13 14console.log({ result });

你的眼睛没有看错,curriedAdd(2) 返回的是一个函数。它会持续返回函数,直到你提供最后一个参数 y 为止。

1import { curry } from 'ramda'; 2 3const add = (x, y) => { 4 console.log(`Adding ${x} and ${y}`); 5 6 return x + y; 7}; 8 9const curriedAdd = curry(add); 10const result = curriedAdd(2); 11 12console.log( 13 result(), 14 result(), 15 result() 16); 17 18console.log("我不会停止返回函数,直到你给我 Y!\n"); 19console.log("好吧兄弟,这是 y = 3...\n"); 20 21console.log(result(3)); 22 23console.log('\n-_-"');

并非完全教科书式

根据函数式编程书籍的说法,柯里化意味着每接收一个预期参数,就返回一个新的函数。

因此,如果你有一个 add3 或 greet 函数:

1const add3 = (x, y, z) => x + y + z; 2const greet = (greeting, first, last) => `${greeting}, ${first} ${last}`;

“正确”的柯里化写法应该是这样:

1const curriedAdd3 = (x) => (y) => (z) => x + y + z; 2const curriedGreet = (greeting) => (first) => (last) => `${greeting}, ${first} ${last}`;

但老实说,在 JavaScript 中这样写真的很别扭。我不希望调用函数时写成这样:

1curriedAdd3(1)(2)(3); // 6 2curriedGreet('Hello')('John')('Doe'); // Hello, John Doe

幸运的是,Ramda 的 curry 支持两种风格。你可以像上面那样逐个传参,也可以一次性传入所有参数。

1import { curry } from 'ramda'; 2 3const greet = (greeting, first, last) => `${greeting}, ${first} ${last}`; 4const curriedGreet = curry(greet); 5 6// 一次性传入所有参数 7const result1 = curriedGreet('Hello', 'John', 'Doe'); 8 9// 或者分步传参 10const result2 = curriedGreet('Hello')('John')('Doe'); 11 12// 甚至可以玩得更花哨 13const result3 = curriedGreet('Hello')()()()('John', 'Doe'); 14 15// 将柯里化函数保存为变量 16const greetJohn = curriedGreet('Hello', 'John'); 17const result4 = greetJohn('Smith'); 18 19console.log({ 20 result1, 21 result2, 22 result3, 23 result4 24});

总结

  • 柯里化函数会持续返回函数,直到所有必需的参数都被提供。

  • 这使得你可以部分应用函数,从而创建出更具体的版本。

  • “标准”的柯里化函数是每个参数对应一个函数:(a) => (b) => (c),而不是 (a, b, c)。

  • 但由于实际使用不便,Ramda 允许你以常规方式传参,或逐个传参,更加灵活实用。

函数组合

辛辣练习