我在几篇文章和博客中看到了对柯里化函数的引用,但我找不到一个好的解释(或者至少一个有意义的解释!)
什么是“咖喱”?
柯里化是将一个接受多个参数的函数分解成一系列函数,每个函数只接受一个参数。这是 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。
在函数代数中,处理带有多个参数的函数(或等效的一个 N 元组参数)有点不优雅——但是,正如 Moses Schönfinkel(以及独立的 Haskell Curry)证明的那样,不需要:所有你需要是接受一个参数的函数。
那么你如何处理你自然表达的东西,比如,f(x,y)
?好吧,您将其视为等效于f(x)(y)
-- f(x)
,将其称为g
,是一个函数,然后将该函数应用于y
. 换句话说,您只有接受一个参数的函数——但其中一些函数返回其他函数(这些函数也接受一个参数;-)。
像往常一样,维基百科对此有一个很好的摘要条目,其中包含许多有用的指针(可能包括有关您最喜欢的语言的指针;-)以及稍微更严格的数学处理。
这是一个具体的例子:
假设您有一个计算作用在物体上的重力的函数。如果你不知道公式,你可以在这里找到它。这个函数接受三个必要的参数作为参数。
现在,在地球上,您只想计算这个星球上物体的力。在函数式语言中,您可以将地球的质量传递给函数,然后对其进行部分评估。你会得到另一个函数,它只接受两个参数并计算地球上物体的引力。这称为柯里化。
它可以是一种使用函数来制作其他函数的方法。
在 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)
我们可以用类似的方式获得抽象。
柯里化是一种可以应用于函数的转换,以允许它们比以前少一个参数。
例如,在 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