什么是“咖喱”?

IT技术 javascript functional-programming terminology definition currying
2021-01-15 22:31:29

我在几篇文章和博客中看到了对柯里化函数的引用,但我找不到一个好的解释(或者至少一个有意义的解释!)

6个回答

柯里化是将一个接受多个参数的函数分解成一系列函数,每个函数只接受一个参数。这是 JavaScript 中的示例:

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

add(3, 4); // returns 7

这是一个函数,它接受两个参数 a 和 b,并返回它们的总和。我们现在将咖喱这个函数:

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

这是一个函数,它接受一个参数 ,a并返回一个接受另一个参数 的函数b,并且该函数返回它们的总和。

add(3)(4);

var add3 = add(3);

add3(4);

第一个语句返回 7,就像add(3, 4)语句一样。第二个语句定义了一个新函数add3,该函数将把 3 加到它的参数上。(有些人可能会称之为闭包。)第三个语句使用该add3操作将 3 与 4 相加,结果再次产生 7。

我了解 map 函数的作用,但我不确定我是否理解您试图为我说明的要点。你是说map函数代表了柯里化的概念?
2021-03-12 22:31:29
@Strawberry,举例来说,你有一个数字列表[1, 2, 3, 4, 5],你希望乘以任意数字。在 Haskell 中,我可以编写map (* 5) [1, 2, 3, 4, 5]将整个列表乘以5,从而生成列表[5, 10, 15, 20, 25]
2021-03-14 22:31:29
@Strawberry 第一个参数map必须是一个只接受 1 个参数的函数 - 列表中的一个元素。乘法——作为一个数学概念——是一种二元运算;它需要 2 个参数。但是,在 Haskell 中*是一个柯里化函数,类似于add这个答案中的第二个版本的结果(* 5)是一个接受单个参数并将其乘以 5 的函数,这允许我们将其与 map 一起使用。
2021-03-19 22:31:29
在实际意义上,我该如何利用这个概念?
2021-03-24 22:31:29
@Strawberry 像 Standard ML 或 Haskell 这样的函数式语言的好处是,您可以“免费”使用柯里化。您可以像在任何其他语言中一样定义多参数函数,并且您会自动获得它的柯里化版本,而不必自己投入一堆 lambda 表达式。因此,您可以生成从任何现有函数中获取更少参数的新函数,而无需大惊小怪,并且可以轻松地将它们传递给其他函数。
2021-04-04 22:31:29

在函数代数中,处理带有多个参数的函数(或等效的一个 N 元组参数)有点不优雅——但是,正如 Moses Schönfinkel(以及独立的 Haskell Curry)证明的那样,不需要:所有你需要是接受一个参数的函数。

那么你如何处理你自然表达的东西,比如,f(x,y)好吧,您将其视为等效于f(x)(y)-- f(x),将其称为g,是一个函数,然后将该函数应用于y. 换句话说,您只有接受一个参数的函数——但其中一些函数返回其他函数(这些函数也接受一个参数;-)。

像往常一样,维基百科对此有一个很好的摘要条目,其中包含许多有用的指针(可能包括有关您最喜欢的语言的指针;-)以及稍微更严格的数学处理。

好的。那么另一个问题。以下是真实的陈述吗?Lambda 演算可以用作函数式编程的模型,但函数式编程不一定应用 lambda 演算。
2021-03-19 22:31:29
正如维基百科页面所指出的,大多数 FP 语言“修饰”或“增强”lambda 演算(例如,使用一些常量和数据类型),而不仅仅是“应用”它,但并不是那么接近。顺便说一句,是什么给你的印象是 Haskell 不“限制函数使用单个 arg”?确实如此,尽管这与柯里化无关;例如div :: Integral a => a -> a -> a——注意那些多个箭头?“将 a 映射到函数将 a 映射到 a”是一种阅读方式;-)。可以div&c使用(单个)元组参数,但这在 Haskell 中确实是反惯用语。
2021-03-25 22:31:29
我想我上面有类似的评论 - 我没有看到函数式语言将函数限制为采用单个 arg。我错了吗?
2021-04-02 22:31:29
@Alex - wrt Haskell 和 arg 计数,我没有在 Haskell 上花很多时间,这都是几周前的事。所以这是一个容易犯的错误。
2021-04-03 22:31:29
@hoohoo:函数式语言通常不会将函数限制为单个参数。然而,在更低、更数学的层面上,处理只接受一个参数的函数要容易得多。(例如,在 lambda 演算中,函数一次只接受一个参数。)
2021-04-05 22:31:29

这是一个具体的例子:

假设您有一个计算作用在物体上的重力的函数。如果你不知道公式,你可以在这里找到它这个函数接受三个必要的参数作为参数。

现在,在地球上,您只想计算这个星球上物体的力。在函数式语言中,您可以将地球的质量传递给函数,然后对其进行部分评估。你会得到另一个函数,它只接受两个参数并计算地球上物体的引力。这称为柯里化。

这对我来说听起来像是部分应用。我的理解是,如果应用柯里化,则可以创建带有单个参数的函数并将它们组合成更复杂的函数。我错过了什么吗?
2021-03-10 22:31:29
出于好奇,JavaScript 的 Prototype 库提供了一个“curry”函数,它几乎完全符合您在此处解释的内容:prototypejs.org/api/function/curry
2021-03-11 22:31:29
@neontapir 是正确的。谢伊所描述的并不是咖喱。它是部分应用。如果一个三参数函数被柯里化并且你称它为 f(1),你得到的不是一个二参数函数。您会返回一个单参数函数,该函数返回另一个单参数函数。柯里化函数只能传递一个参数。PrototypeJS 中的 curry 函数也不是柯里化的。这是部分应用。
2021-03-18 22:31:29
no(部分评估)和 no(柯里化)。这称为部分应用。需要使用柯里化来启用它。
2021-04-01 22:31:29
2021-04-05 22:31:29

它可以是一种使用函数来制作其他函数的方法。

在 JavaScript 中:

let add = function(x){
  return function(y){ 
   return x + y
  };
};

允许我们这样称呼它:

let addTen = add(10);

当它运行时10,作为x;

let add = function(10){
  return function(y){
    return 10 + y 
  };
};

这意味着我们返回了这个函数:

function(y) { return 10 + y };

所以当你打电话

 addTen();

你真的在打电话:

 function(y) { return 10 + y };

所以如果你这样做:

 addTen(4)

它与以下相同:

function(4) { return 10 + 4} // 14

所以我们addTen()总是在我们传入的任何东西上加十。我们可以用同样的方式制作类似的函数:

let addTwo = add(2)       // addTwo(); will add two to whatever you pass in
let addSeventy = add(70)  // ... and so on...

现在显而易见的后续问题是你到底为什么要这样做?它将急切的操作x + y变成了可以延迟执行的操作,这意味着我们至少可以做两件事 1. 缓存昂贵的操作 2. 在函数范式中实现抽象。

想象一下我们的柯里化函数是这样的:

let doTheHardStuff = function(x) {
  let z = doSomethingComputationallyExpensive(x)
  return function (y){
    z + y
  }
}

我们可以调用这个函数一次,然后传递结果以用于很多地方,这意味着我们只做一次计算成本高的事情:

let finishTheJob = doTheHardStuff(10)
finishTheJob(20)
finishTheJob(30)

我们可以用类似的方式获得抽象。

最初的问题是“它是什么”,而不是它为什么有用。
2021-03-12 22:31:29
咖喱模式是一种将固定参数应用于现有函数的方法,目的是创建新的可重用函数,而无需重新创建原始函数。这个答案很好地证明了这一点。
2021-03-12 22:31:29
“我们至少可以做两件事 1. 缓存昂贵的操作 2. 在功能范式中实现抽象。” 这是其他答案所缺乏的“为什么它有用”的解释。而且我认为这个答案也很好地解释了“什么”。
2021-03-15 22:31:29
@jonsilver 我会说相反的,不是一个很好的解释。我同意它很好地解释了所提出的例子,但人们往往会默认思考,“是的,非常清楚,但我可以用另一种方式做同样的事情,那么柯里化有什么好处?” 换句话说,我希望它有足够的上下文或解释来说明柯里化的工作原理,而且说明为什么与其他加十的方法相比,它不是一个无用和微不足道的观察。
2021-03-25 22:31:29
我在这里看到的对固有顺序过程的最好的逐步解释,也许是最好的,最具解释性的答案。
2021-04-08 22:31:29

柯里化是一种可以应用于函数的转换,以允许它们比以前少一个参数。

例如,在 F# 中,您可以这样定义一个函数:-

let f x y z = x + y + z

这里函数 f 接受参数 x、y 和 z 并将它们相加,因此:-

f 1 2 3

返回 6。

因此,根据我们的定义,我们可以为 f 定义咖喱函数:-

let curry f = fun x -> f x

其中 'fun x -> fx' 是一个 lambda 函数,相当于 C# 中的 x => f(x)。这个函数输入你想要柯里化的函数并返回一个函数,它接受一个参数并返回指定的函数,第一个参数设置为输入参数。

使用我们之前的示例,我们可以获得 f 的咖喱,因此:-

let curryf = curry f

然后我们可以执行以下操作:-

let f1 = curryf 1

这为我们提供了一个函数 f1,它等价于 f1 yz = 1 + y + z。这意味着我们可以执行以下操作:-

f1 2 3

返回 6。

这个过程经常与“部分功能应用”相混淆,它可以这样定义:-

let papply f x = f x

虽然我们可以将其扩展到多个参数,即:-

let papply2 f x y = f x y
let papply3 f x y z = f x y z
etc.

部分应用程序将获取函数和参数并返回一个需要一个或多个更少参数的函数,并且如前两个示例所示,它是直接在标准 F# 函数定义中实现的,因此我们可以实现前面的结果:-

let f1 = f 1
f1 2 3

这将返回 6 的结果。

综上所述:-

柯里化和偏函数应用的区别在于:-

柯里化接受一个函数并提供一个接受单个参数的新函数,并返回指定的函数,其第一个参数设置为该参数。这允许我们将具有多个参数的函数表示为一系列单参数函数例子:-

let f x y z = x + y + z
let curryf = curry f
let f1 = curryf 1
let f2 = curryf 2
f1 2 3
6
f2 1 3
6

部分函数应用程序更直接——它接受一个函数和一个或多个参数,并返回一个函数,其中前 n 个参数设置为指定的 n 个参数。例子:-

let f x y z = x + y + z
let f1 = f 1
let f2 = f 2
f1 2 3
6
f2 1 3
6
“这允许我们将具有多个参数的函数表示为一系列单参数函数” - 完美,这对我来说很好地清除了它。谢谢
2021-03-19 22:31:29
那么 C# 中的方法需要在部分应用之前进行柯里化吗?
2021-03-28 22:31:29