-
09-06-2019 - |
题
我在几篇文章和博客中看到了对柯里化函数的引用,但我找不到一个很好的解释(或者至少是一个有意义的解释!)
解决方案
柯里化是指将一个接受多个参数的函数分解为一系列接受部分参数的函数。这是一个 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
. 。换句话说,您只有带有一个参数的函数——但其中一些函数返回其他函数(也带有一个参数;-)。
照常, 维基百科 有一个关于此的很好的摘要条目,其中有许多有用的指针(可能包括有关您最喜欢的语言的指针;-)以及稍微更严格的数学处理。
这是一个具体的例子:
假设您有一个计算作用在物体上的重力的函数。如果你不知道公式,你可以找到它 这里. 。该函数接受三个必要的参数作为参数。
现在,在地球上,您只想计算这个星球上物体的力。在函数式语言中,您可以将地球的质量传递给函数,然后对其进行部分评估。您将得到另一个函数,它只接受两个参数并计算地球上物体的引力。这称为柯里化。
柯里化是一种可以应用于函数的转换,以允许它们比以前少一个参数。
例如,在 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 -> f x' 是一个 lambda 函数,相当于 C# 中的 x => f(x)。该函数输入您想要柯里化的函数并返回一个函数 采用单个参数 并返回指定函数,并将第一个参数设置为输入参数。
使用我们之前的例子,我们可以得到 f 的柯里化:-
let curryf = curry f
然后我们可以执行以下操作:-
let f1 = curryf 1
这为我们提供了一个函数 f1,它等价于 f1 y z = 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
它可以是一种使用函数来创建其他函数的方法。
在 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...
柯里化函数是一个由多个参数重写的函数,它接受第一个参数并返回一个接受第二个参数的函数,依此类推。这允许多个参数的函数部分应用它们的一些初始参数。
下面是一个 Python 玩具示例:
>>> from functools import partial as curry
>>> # Original function taking three parameters:
>>> def display_quote(who, subject, quote):
print who, 'said regarding', subject + ':'
print '"' + quote + '"'
>>> display_quote("hoohoo", "functional languages",
"I like Erlang, not sure yet about Haskell.")
hoohoo said regarding functional languages:
"I like Erlang, not sure yet about Haskell."
>>> # Let's curry the function to get another that always quotes Alex...
>>> am_quote = curry(display_quote, "Alex Martelli")
>>> am_quote("currying", "As usual, wikipedia has a nice summary...")
Alex Martelli said regarding currying:
"As usual, wikipedia has a nice summary..."
(仅使用 + 连接以避免非 Python 程序员分心。)
编辑添加:
看 http://docs.python.org/library/functools.html?highlight=partial#functools.partial,它也显示了部分对象vs。Python 实现此功能的方式存在函数差异。
如果你明白的话 partial
你已经成功了一半。的想法 partial
是将参数预先应用到函数并返回一个只需要剩余参数的新函数。当调用这个新函数时,它包括预加载的参数以及提供给它的任何参数。
在 Clojure 中 +
是一个函数,但为了让事情变得清楚:
(defn add [a b] (+ a b))
您可能知道 inc
函数只是将 1 添加到它传递的任何数字上。
(inc 7) # => 8
让我们自己构建它 partial
:
(def inc (partial add 1))
这里我们返回另一个函数,它的第一个参数中加载了 1 add
. 。作为 add
新的有两个参数 inc
函数只需要 b
参数——不是像以前那样的 2 个参数,因为 1 个参数已经存在 部分地 应用。因此 partial
是一个工具,可以使用预先提供的默认值创建新函数。这就是为什么在函数式语言中,函数经常将参数从一般到具体排序。这使得重用此类函数来构造其他函数变得更加容易。
现在想象一下,如果这种语言足够聪明,能够内省地理解 add
想要两个论点。当我们向它传递一个参数时,而不是犹豫不决,如果函数部分应用了我们代表我们传递的参数,并且理解我们可能打算稍后提供另一个参数,该怎么办?然后我们可以定义 inc
没有明确使用 partial
.
(def inc (add 1)) #partial is implied
这是某些语言的行为方式。当人们希望将函数组合成更大的转换时,它特别有用。这将导致人们走向换能器。
我发现这篇文章及其引用的文章对于更好地理解柯里化很有用:http://blogs.msdn.com/wesdyer/archive/2007/01/29/currying-and-partial-function-application.aspx
正如其他人提到的,这只是拥有单参数函数的一种方法。
这很有用,因为您不必假设将传入多少个参数,因此您不需要 2 参数、3 参数和 4 参数函数。
Curry 可以简化你的代码。这是使用它的主要原因之一。柯里化是一种将接受 n 个参数的函数转换为 n 个仅接受一个参数的函数的过程。
原理就是将被传递函数的参数,利用闭包(closure)属性,将它们存储到另一个函数中,并把它当作返回值,而这些函数形成一条链,最后将参数传入即可完成操作。
这样做的好处是可以通过一次处理一个参数来简化参数的处理,同时也可以提高程序的灵活性和可读性。这也使得程序更易于管理。将代码分成更小的部分也将使其易于重用。
例如:
function curryMinus(x)
{
return function(y)
{
return x - y;
}
}
var minus5 = curryMinus(1);
minus5(3);
minus5(5);
我也可以做...
var minus7 = curryMinus(7);
minus7(3);
minus7(5);
这对于使复杂的代码变得整洁以及处理不同步的方法等非常有用。
柯里化(Currying)是将可调用函数翻译为 f(a, b, c)
变成可调用的 f(a)(b)(c)
.
否则,柯里化就是将一个接受多个参数的函数分解为一系列接受部分参数的函数。
从字面上看,柯里化是函数的转换:从一种方式调用另一种方式。在 JavaScript 中,我们通常会制作一个包装器来保留原始函数。
柯里化并不调用函数。它只是改变它。
让我们创建一个对双参数函数执行柯里化的柯里化函数。换句话说, curry(f)
对于两个参数 f(a, b)
将其翻译成 f(a)(b)
function curry(f) { // curry(f) does the currying transform
return function(a) {
return function(b) {
return f(a, b);
};
};
}
// usage
function sum(a, b) {
return a + b;
}
let carriedSum = curry(sum);
alert( carriedSum(1)(2) ); // 3
正如您所看到的,实现是一系列包装器。
- 的结果
curry(func)
是一个包装器function(a)
. - 当它被称为像
sum(1)
, ,参数被保存在词法环境中,并返回一个新的包装器function(b)
. - 然后
sum(1)(2)
终于打电话了function(b)
提供 2,并将调用传递给原始多参数 sum。
咖喱函数应用于多个参数列表,而不仅仅是一个。
这是一个常规的非携带函数,它添加了两个int参数x和y::
scala> def plainOldSum(x: Int, y: Int) = x + y
plainOldSum: (x: Int,y: Int)Int
scala> plainOldSum(1, 2)
res4: Int = 3
这是柯里化的类似函数。您可以将此功能应用于每个INT参数的两个列表,而不是两个INT参数的列表:
scala> def curriedSum(x: Int)(y: Int) = x + y
curriedSum: (x: Int)(y: Int)Intscala> second(2)
res6: Int = 3
scala> curriedSum(1)(2)
res5: Int = 3
这里发生的事情是当你调用 curriedSum
, ,您实际上会连续获得两个传统函数调用。第一个函数调用采用单个INT参数命名 x
,并返回第二个函数的函数值。第二个函数采用 Int 参数y
.
这是一个名为 first
在精神上可以做到的第一个传统函数的调用 curriedSum
会做:
scala> def first(x: Int) = (y: Int) => x + y
first: (x: Int)(Int) => Int
将1应用于第一个函数 - 换句话说,调用第一个函数并通过1个函数 - 以下是第二个功能:
scala> val second = first(1)
second: (Int) => Int = <function1>
将 2 应用到第二个函数会得到结果:
scala> second(2)
res6: Int = 3
柯里化的一个例子是,当您拥有目前只知道其中一个参数的函数时:
例如:
func aFunction(str: String) {
let callback = callback(str) // signature now is `NSData -> ()`
performAsyncRequest(callback)
}
func callback(str: String, data: NSData) {
// Callback code
}
func performAsyncRequest(callback: NSData -> ()) {
// Async code that will call callback with NSData as parameter
}
这里,由于您在发送到时不知道回调的第二个参数 performAsyncRequest(_:)
您必须创建另一个 lambda / 闭包才能将其发送到函数。
与所有其他答案一样,柯里化有助于创建部分应用的函数。Javascript 不提供对自动柯里化的本机支持。所以上面提供的例子可能对实际编码没有帮助。livescript中有一些很好的例子(它本质上编译为js)http://livescript.net/
times = (x, y) --> x * y
times 2, 3 #=> 6 (normal use works as expected)
double = times 2
double 5 #=> 10
在上面的示例中,当您给出的参数较少时,livescript 会为您生成新的柯里化函数(双精度)
在这里您可以找到 C# 中柯里化实现的简单说明。在评论中,我试图展示柯里化如何有用:
public static class FuncExtensions {
public static Func<T1, Func<T2, TResult>> Curry<T1, T2, TResult>(this Func<T1, T2, TResult> func)
{
return x1 => x2 => func(x1, x2);
}
}
//Usage
var add = new Func<int, int, int>((x, y) => x + y).Curry();
var func = add(1);
//Obtaining the next parameter here, calling later the func with next parameter.
//Or you can prepare some base calculations at the previous step and then
//use the result of those calculations when calling the func multiple times
//with different input parameters.
int result = func(1);