我在几篇文章和博客中看到了对 curried 函数的引用,但我找不到一个好的解释(或者至少是一个有意义的解释!)
22 回答
柯里化是将一个接受多个参数的函数分解为一系列函数,每个函数只接受一个参数。这是 JavaScript 中的一个示例:
function add (a, b) {
return a + b;
}
add(3, 4); // returns 7
这是一个函数,它接受两个参数 a 和 b,并返回它们的总和。我们现在将 curry 这个函数:
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)所证明的那样,不需要:所有你need 是接受一个参数的函数。
那么你如何处理一些你自然会表达为的东西,比如,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()
总是在我们传入的任何内容中添加 10。我们可以用相同的方式制作类似的函数:
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 的 curry 函数:-
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 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 的结果。
综上所述:-
柯里化和偏函数应用之间的区别在于:-
Currying 接受一个函数并提供一个接受单个参数的新函数,并返回指定的函数,并将其第一个参数设置为该参数。这允许我们将具有多个参数的函数表示为一系列单参数函数。例子:-
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
柯里化函数是一个重写了几个参数的函数,它接受第一个参数并返回一个接受第二个参数的函数,依此类推。这允许多个参数的函数部分应用它们的一些初始参数。
柯里化意味着将 N 元的函数转换为 N 元为 1 的arity
函数。函数的 是它需要的参数的数量。
这是正式的定义:
curry(f) :: (a,b,c) -> f(a) -> f(b)-> f(c)
这是一个有意义的真实示例:
你去 ATM 取钱。您刷卡,输入密码并做出选择,然后按回车键提交请求旁边的“金额”。
这是取款的正常功能。
const withdraw=(cardInfo,pinNumber,request){
// process it
return request.amount
}
在这个实现中,函数期望我们一次输入所有参数。我们要刷卡,输入密码并提出请求,然后函数就会运行。如果这些步骤中的任何一个有问题,您会在输入所有参数后发现。使用 curried 函数,我们将创建更高级、更纯粹、更简单的函数。纯函数将帮助我们轻松调试代码。
这是带有咖喱功能的 Atm:
const withdraw=(cardInfo)=>(pinNumber)=>(request)=>request.amount
ATM,将卡作为输入并返回一个需要 pinNumber 的函数,该函数返回一个接受请求对象的函数,在成功处理后,您将获得您请求的金额。每一步,如果你有一个错误,你会很容易地预测出哪里出了问题。假设您输入卡并出现错误,您知道它与卡或机器有关,但与密码无关。或者,如果您输入了 pin 并且如果它没有被接受,您就知道您输入了错误的 pin 号。您将轻松调试错误。
此外,这里的每个函数都是可重用的,因此您可以在项目的不同部分使用相同的函数。
柯里化是将一个函数从 callable asf(a, b, c)
转换为 callable as f(a)(b)(c)
。
否则,柯里化就是将一个接受多个参数的函数分解为一系列接受部分参数的函数。
从字面上看,柯里化是函数的转换:从一种调用方式到另一种调用方式。在 JavaScript 中,我们通常会制作一个包装器来保留原始函数。
柯里化不调用函数。它只是改变它。
让我们制作 curry 函数来执行两个参数函数的柯里化。换句话说,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)
是一个 wrapperfunction(a)
。 - 当它被调用时
sum(1)
,参数被保存在词法环境中,并返回一个新的包装器function(b)
。 - 然后
sum(1)(2)
最后调用function(b)
提供 2,并将调用传递给原始的多参数和。
这是 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,它还显示了 Python 实现此方法的部分对象与函数的区别。
Currying 是 Java Script 的高阶函数之一。
柯里化是一个包含许多参数的函数,它被重写为它接受第一个参数并返回一个函数,该函数又使用剩余的参数并返回值。
使困惑?
来看一个例子,
function add(a,b)
{
return a+b;
}
add(5,6);
这类似于下面的柯里化函数,
function add(a)
{
return function(b){
return a+b;
}
}
var curryAdd = add(5);
curryAdd(6);
那么这段代码是什么意思呢?
现在再次阅读定义,
柯里化是一个包含许多参数的函数,它被重写为它接受第一个参数并返回一个函数,该函数又使用剩余的参数并返回值。
还是,糊涂?让我深入解释一下!
当你调用这个函数时,
var curryAdd = add(5);
它会返回一个这样的函数,
curryAdd=function(y){return 5+y;}
因此,这称为高阶函数。意思是,依次调用一个函数返回另一个函数是高阶函数的精确定义。这是传说中的最大优势,Java Script。所以回到咖喱,
此行会将第二个参数传递给 curryAdd 函数。
curryAdd(6);
这反过来导致,
curryAdd=function(6){return 5+6;}
// Which results in 11
希望你能理解这里柯里化的用法。所以,说到优势,
为什么要咖喱?
它利用了代码的可重用性。更少的代码,更少的错误。你可能会问代码少了怎么办?
我可以用 ECMA 脚本 6 个新特性箭头函数来证明这一点。
是的!ECMA 6,为我们提供了称为箭头函数的奇妙功能,
function add(a)
{
return function(b){
return a+b;
}
}
借助箭头函数,我们可以将上述函数编写如下,
x=>y=>x+y
酷吧?
所以,更少的代码和更少的错误!
借助这些高阶函数,可以轻松开发出无错误的代码。
我挑战你!
希望,你明白什么是咖喱。如果您需要任何澄清,请随时在此处发表评论。
谢谢,祝你有美好的一天!
如果你明白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
这是某些语言的行为方式。当希望将函数组合成更大的转换时,它特别有用。这将导致一个传感器。
这是通用和最短版本的函数柯里化的示例,其中 n 没有。参数。
const add = a => b => b ? add(a + b) : a;
const add = a => b => b ? add(a + b) : a;
console.log(add(1)(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);
这对于使复杂的代码整洁和处理不同步的方法等非常有用。
我发现这篇文章及其引用的文章对更好地理解柯里化很有用:http: //blogs.msdn.com/wesdyer/archive/2007/01/29/currying-and-partial-function-application.aspx
正如其他人所提到的,这只是一种具有单参数功能的方法。
这很有用,因为您不必假设将传入多少个参数,因此您不需要 2 参数、3 参数和 4 参数函数。
正如所有其他答案一样,currying 有助于创建部分应用的函数。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 会为您生成新的 curried 函数(double)
柯里化函数应用于多个参数列表,而不仅仅是一个。
这是一个常规的非柯里化函数,它添加了两个 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 / 闭包以将其发送给函数。
在这里,您可以找到 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);
“柯里化”是获取多个参数的函数并将其转换为一系列函数的过程,每个函数接受一个参数并返回单个参数的函数,或者在最终函数的情况下,返回实际结果。
其他答案已经说了柯里化是什么:将比预期更少的参数传递给柯里化函数不是错误,而是返回一个函数,该函数期望其余参数并返回相同的结果,就好像你在一次。
我会尝试激励它为什么有用。它是您在做之前从未意识到自己需要的那些工具之一。柯里化首先是让您的程序更具表现力的一种方式——您可以将操作与更少的代码结合在一起。
例如,如果您有一个 curried 函数add
,您可以将 JS x => k + x
(或 Python lambda x: k + x
或 Ruby{ |x| k + x }
或 Lisp(lambda (x) (+ k x))
或……)的等价物编写为add(k)
. 在 Haskelll 中,您甚至可以使用运算符:(k +)
或(+ k)
(这两种形式让您可以对非交换运算符进行任意一种柯里化:(/ 9)
是一个将数字除以 9 的函数,这可能是更常见的用例,但您也(9 /)
有将 9 除以其参数的函数。)除了更短之外,curried 版本不包含像x
在所有其他版本中都可以找到。这不是必需的。您正在定义一个将一些常数 k 添加到数字的函数,并且您不需要为该数字命名只是为了讨论该函数。甚至可以定义它。这是所谓的“无点风格”的一个例子。除了操作本身,您可以将操作组合在一起。您不必声明只对它们的参数应用一些操作的匿名函数,因为 * 这就是操作已经是什么。
当以柯里化友好的方式定义高阶函数时,这变得非常方便。例如,一个 curriedmap(fn, list)
让您定义一个映射器,map(fn)
稍后可以将其应用于任何列表。但是,将映射定义为map(list, fn)
只允许您定义一个函数,该函数会将其他一些函数应用于常量列表,这可能不太有用。
柯里化减少了对管道和线程等东西的需求。在 Clojure 中,您可以使用 threading 宏定义温度转换函数->
: (defn f2c (deg) (-> deg (- 32) (* 5) (/ 9))
。这很酷,它从左到右读起来很好(“减去 32,乘以 5,然后除以 9。”)并且您只需为每个子操作提及参数两次而不是一次……但它之所以有效,是因为它->
是一个可以转换的宏在评估任何内容之前在语法上的整个形式。它在幕后变成了一个常规的嵌套表达式:(/ (* (- deg 32) 5) 9)
. 如果数学运算被柯里化了,你就不需要一个宏来将它们很好地结合起来,就像在 Haskell 中那样let f2c = (subtract 32) & (* 5) & (/ 9)
。(尽管不可否认,使用函数组合会更惯用,它从右到左读:(/ 9) . (* 5) . (subtract 32)
.)
同样,很难找到好的演示示例;currying 在复杂的情况下最有用,它确实有助于解决方案的可读性,但是为了让您理解问题,关于currying 的整体课程可能会迷失在噪音中。
有一个“在 ReasonML 中进行柯里化”的例子。
let run = () => {
Js.log("Curryed function: ");
let sum = (x, y) => x + y;
Printf.printf("sum(2, 3) : %d\n", sum(2, 3));
let per2 = sum(2);
Printf.printf("per2(3) : %d\n", per2(3));
};
下面是 JavaScript 中的柯里化示例之一,这里的multiply返回用于将x乘以2 的函数。
const multiply = (presetConstant) => {
return (x) => {
return presetConstant * x;
};
};
const multiplyByTwo = multiply(2);
// now multiplyByTwo is like below function & due to closure property in JavaScript it will always be able to access 'presetConstant' value
// const multiplyByTwo = (x) => {
// return presetConstant * x;
// };
console.log(`multiplyByTwo(8) : ${multiplyByTwo(8)}`);
输出
乘以二(8):16