柯里化到底是什么?字面意思是:如何写一个柯里化函数?与辅助函数一起使用;基于现有函数创建新函数;创建我们自己的辅助函数。太棒了🎉 延伸阅读

2025-06-04

柯里化到底是什么?

字面上地,

如何编写柯里化函数?

与辅助函数一起使用

从现有函数创建新函数

制作我们自己的辅助函数

万岁🎉

进一步阅读

大家好,很高兴在我的网络小窝里见到你们!我正在复习一些我刚开始学习 JavaScript 时忽略的概念,现在就来聊聊吧。现在,我们来看看 JavaScript 中一个很花哨的术语——柯里化(Currying)到底是怎么回事。

字面上地,

尽管“柯里化”这个术语听起来很花哨,但(根据维基百科它是一种将一个接受多个参数的函数转换为一系列每个接受单个参数的函数的技术。现在,无论你是 JavaScript 爱好者,还是在高中学过基础代数,你都能理解它的含义。

但如果不是,它只是说我们有一个接受多个参数的函数,并且我们将它转​​换为另一个接受相同数量参数的函数,但每次接受一个参数或按顺序接受。下图清楚地说明了这一点。

无论如何,这在现实世界中是如何发生的(或以编程方式),让我们来找出答案!

柯里化示例

如何编写柯里化函数?

我们知道如何编写一个普通函数(不带柯里化),例如add()上面的函数,就这么简单。

function add(a, b, c) {
  return a + b + c;
}

console.log(add(1, 2, 3)); // 6
Enter fullscreen mode Exit fullscreen mode

但这就是我们以可柯里化的方式编写相同函数的方式。

function add(a) {
  return function (b) {
    return function (c) {
      return a + b + c;
    };
  };
}

console.log(add(1)(2)(3)); // 6
Enter fullscreen mode Exit fullscreen mode

如果你注意到柯里函数,我们会发现,对于每个传入的参数,add()我们都会返回一个新函数,这个新函数接受另一个参数并返回另一个函数。最后,在传递最后一个参数后,我们返回最终结果。这就是柯里函数的基本结构。

因此add(1),在 curry 函数中,我们不会得到结果值,而是一个以(2)参数为参数的全新函数,它会一直运行直到我们在输出中获得一个值。

与辅助函数一起使用

由于柯里化函数并不容易编写(总是),但作为函数式编程中的一个主要概念,我们有许多辅助函数可以帮助我们将普通函数转换为柯里化函数。

大多数 JavaScript 实用程序库(例如LodashRambda都可以完成这些操作,只需一个简单的步骤即可完成。此外,我在此示例中使用了 lodash。

const _ = require("lodash");

function add(a, b, c) {
  return a + b + c;
}

const curryAdd = _.curry(add);

console.log(add(1, 2, 3)); // 6
console.log(curryAdd(1)(2)(3)); // 6
Enter fullscreen mode Exit fullscreen mode

从现有函数创建新函数

这就是 curry 函数在现实生活中的主要用途,因为它可以帮助我们创建全新的函数并将其导出以在任何地方使用。

例如,我们有这个对象数组。

const items = [
  { name: "Mango", type: "Fruit" },
  { name: "Tomato", type: "Vegetable" },
  { name: "Strawberry", type: "Fruit" },
  { name: "Potato", type: "Vegetable" },
  { name: "Turnip", type: "Vegetable" },
  { name: "Banana", type: "Fruit" },
  { name: "Carrot", type: "Vegetable" },
];
Enter fullscreen mode Exit fullscreen mode

现在我们需要创建一个函数,返回所有类型为Fruits或 的项Vegetables。那么,让我们使用刚刚学到的柯里化概念来实现它。

const isType = obj => type => obj.type === type;

const isFruit = item => isType(item)("Fruit");
const isVegetable = item => isType(item)("Vegetable");

const fruits = items.filter(isFruit);
const vegetables = items.filter(isVegetable);
Enter fullscreen mode Exit fullscreen mode

哇,看起来真干净。但这是怎么工作的呢?

首先,我们将其isType()作为一个 curry 函数,它接受一个对象并在检查其类型是否等于传递的参数类型后返回一个布尔值(true/false)。

但是,我们并不直接使用这个函数,而是创建了另外两个函数,分别检查类型是水果还是蔬菜。通过查看它的结构,你会注意到它只接受一个参数,即当前项目,并isType()通过传递item所需的类型来调用它。

最后,为了获取水果和蔬菜,我们运行一个.filter()数组方法,并将isFruitisVegetable作为回调函数传递。默认情况下,这会将items数组中的当前项传递给回调函数。更清晰地说,我们最终所做的与下面的相同。

const fruits = items.filter(item => isFruit(item));
const vegetables = items.filter(item => isVegetable(item));
Enter fullscreen mode Exit fullscreen mode

结果

console.log(fruits)

[
  { name: 'Mango', type: 'Fruit' },
  { name: 'Strawberry', type: 'Fruit' },
  { name: 'Banana', type: 'Fruit' }
]
Enter fullscreen mode Exit fullscreen mode

console.log(vegetables)

[
  { name: 'Tomato', type: 'Vegetable' },
  { name: 'Potato', type: 'Vegetable' },
  { name: 'Turnip', type: 'Vegetable' },
  { name: 'Carrot', type: 'Vegetable' }
]
Enter fullscreen mode Exit fullscreen mode

最终,我们知道了如何编写 curry 函数,这比我们想象的要容易得多。

编辑

你能想出一种稍微不同的方法来编写上面例子中的函数,以减少代码量并使其更直观吗?这可能吗?

是的,感谢Rudy Nappée在评论中向我们展示同样的内容,我认为这对阅读这篇文章的每个人都很有用。

所以,我们应该做的是:“始终将点(部分应用最终操作的参数)放在最后的位置”。

const isType = (type) => (obj) => obj.type === type
Enter fullscreen mode Exit fullscreen mode

这样,我们就不必像对isFruit和那样重新定义另一个函数,isVegetable而是可以这样写。

const isFruit = isType("Fruit")
const isVegetable = isType("Vegetable")
Enter fullscreen mode Exit fullscreen mode

或者您可以用更直接的方式编写它,直接在过滤器回调中编写。

const fruits = items.filter(isType("Fruit"))
const vegetables = items.filter(isType("Vegetable"))
Enter fullscreen mode Exit fullscreen mode

制作我们自己的辅助函数

在学会了使用辅助函数(例如Lodash.curry()中的)将普通函数转换为柯里化函数之后,我很好奇如何自己构建一个。因为,你知道,有时候我们只需要几个函数,不需要像Lodash这样的大型实用程序库。

让我们首先创建一个简单的用例,然后再创建实际的 curry 函数。

// Assume that curry is a valid function

const multiply = (a, b) => a * b;

const curryMultiply = curry(multiply);

console.log(curryMultiply(2, 4)); // 8
console.log(curryMultiply(2)(4)); // 8
Enter fullscreen mode Exit fullscreen mode

现在先不要运行,因为我们还需要满足假设,创建一个有效的 curry 函数。运行后肯定会报错,Reference Error因为curry“is notdefined”。

构建实际curry()功能。

function curry(func) {
  return function curried(...args) {
    if (args.length >= func.length) {
      return func.apply(undefined, args);
    } else {
      return function (...rest) {
        return curried.apply(undefined, rest.concat(args));
      };
    }
  };
}
Enter fullscreen mode Exit fullscreen mode

首先,我们创建一个名为 curry 的函数,并从中返回另一个名为 curried 的函数。如你所见,在返回的函数中,我们检查传递给它的参数数量。如果该数量超过或等于函数实际func期望的参数数量(也称为arity,则我们通过调用 apply 并传入所有参数来返回该函数。

如果参数数量较少(这种情况在我们逐个传递参数时发生),我们会返回另一个函数,该函数将其参数存储在名为 的变量中rest。最后,我们递归调用同一个函数curried(),并将新的参数传递给rest变量,并将其与变量中先前获得的参数连接起来args

另外,如果你好奇那三个点( )是什么,它们是ES6ECMAScript2015...中的新特性。它们返回传递给函数的所有参数。

现在,如果您按下回车键或运行代码,您将看到获得正确的输出,如下所示。

console.log(curryMultiply(2, 4)); // 8
console.log(curryMultiply(2)(4)); // 8
Enter fullscreen mode Exit fullscreen mode

您可以将此代码片段保存在您的 Gist 中或正确理解它,因此,任何时候您只想使用带有柯里化的函数而不是再次编写该函数,您可以通过将旧的非柯里化函数传递到curry()我们刚刚创建的这个方法中来创建一个新函数。

万岁🎉

如果你已经跟上我的思路,那就太棒了!希望你能学到一些新东西,或者在修复老 bug 方面有所进步。如果你发现我遗漏了什么,或者只是想打个招呼(这在当今社会真的变得很重要),可以发推文到@heytulsiprasad。接下来几天,你可以期待更多关于函数式编程的博客。

进一步阅读

文章来源:https://dev.to/thebuildguy/what-the-heck-is-currying-anyway-p83
PREV
Spring Boot - 速成课程
NEXT
您最不想遇到的 5 大 CORS 问题 什么是 CORS? 不存在访问控制允许来源标头 响应中的访问控制允许来源标头不得为通配符 * 对预检请求的响应未通过访问控制检查 响应中的访问控制允许凭据标头为“ ”,当请求凭据模式为“include”时,该标头必须为“true” 专业提示 我的应用仍然在控制台中显示 CORS 问题,但不知道哪里出了问题 总结 ✨