什么是'Currying'?

时间:2008-08-30 20:12:56

标签: functional-programming terminology definition currying

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

21 个答案:

答案 0 :(得分:770)

Currying是指将一个函数分解为一系列函数的函数,这些函数将参与一部分参数。这是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。

答案 1 :(得分:119)

在函数的代数中,处理带有多个参数的函数(或等价的一个参数是一个N元组)有点不优雅 - 但是,正如MosesSchönfinkel(以及独立地,Haskell Curry)证明的那样,它不是必需的:你需要的只是一个参数的函数。

那么你如何处理你自然表达的事物,比如f(x,y)?好吧,你认为它等同于f(x)(y) - f(x),称之为g,是一个函数,然后将该函数应用于y。换句话说,你只有一个带有一个参数的函数 - 但是其中一些函数返回其他函数(也可以带一个参数; - )。

像往常一样,wikipedia有一个很好的摘要条目,有很多有用的指针(可能包括你最喜欢的语言;-)以及稍微严格的数学处理。

答案 2 :(得分:91)

这是一个具体的例子:

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

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

答案 3 :(得分:46)

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

例如,在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'是与x =>相等的lambda函数。 f#中的f(x)。此函数输入您希望咖喱的函数并返回采用单个参数的函数,并返回指定的函数,并将第一个参数设置为输入参数。

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

let curryf = curry f

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

let f1 = curryf 1

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

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.

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

let f1 = f 1
f1 2 3

将返回6的结果。

总之: -

currying和部分函数应用之间的区别在于: -

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

答案 4 :(得分:32)

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

在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...

答案 5 :(得分:28)

curried函数是几个重写的参数的函数,它接受第一个参数并返回一个接受第二个参数的函数,依此类推。这允许多个参数的函数部分应用它们的一些初始参数。

答案 6 :(得分:6)

这是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实现它的方式显示了部分对象与函数的区别。

答案 7 :(得分:4)

如果你理解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

这是某些语言的行为方式。当人们希望将函数组合成更大的变换时,它非常有用。这将导致一个换能器。

答案 8 :(得分:3)

我发现这篇文章及其引用的文章很有用,可以更好地理解curry: http://blogs.msdn.com/wesdyer/archive/2007/01/29/currying-and-partial-function-application.aspx

正如其他人所提到的,这只是一种具有一个参数功能的方法。

这很有用,因为您不必假设将传入多少参数,因此您不需要2参数,3参数和4参数函数。

答案 9 :(得分:3)

固化是Java Script的高级功能之一。

Currying是一个包含许多参数的函数,这些参数被重写为使得它使用第一个参数并返回一个函数,该函数又使用剩余的参数并返回值。

困惑吗?

让我们看一个例子,

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

这类似于以下的currying函数,

function add(a)
    {
        return function(b){
            return a+b;
        }
    }
var curryAdd = add(5);
curryAdd(6);

那么这段代码是什么意思?

现在再次阅读定义,

Currying是一个包含许多参数的函数,这些参数被重写为使得它使用第一个参数并返回一个函数,该函数又使用剩余的参数并返回值。

还是,感到困惑吗? 让我深入解释!

调用此函数时,

var curryAdd = add(5);

它将返回一个这样的函数,

curryAdd=function(y){return 5+y;}

因此,这称为高阶函数。意思是,依次调用一个函数会返回另一个函数,这是对高阶函数的精确定义。对于传奇Java脚本,这是最大的优势。 因此,回到混乱的地方,

此行会将第二个参数传递给curryAdd函数。

curryAdd(6);

这反过来会导致结果,

curryAdd=function(6){return 5+6;}
// Which results in 11

希望您在这里了解curring的用法。 因此,发挥优势,

为什么要咖喱?

它利用了代码的可重用性。 更少的代码,更少的错误。 您可能会问代码少了吗?

我可以使用ECMA脚本6个新功能箭头功能来证明这一点。

是的! ECMA 6为我们提供了称为箭头功能的精彩功能,

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

借助arrow函数,我们可以编写以下函数,

x=>y=>x+y

很酷吗?

因此,更少的代码和更少的错误!

借助这些高阶函数,可以轻松开发出无错误的代码。

我挑战你!

希望,您了解正在发生的事情。如果您需要任何说明,请随时在此处评论。

谢谢,祝你有美好的一天!

答案 10 :(得分:3)

Currying正在将函数从可调用的f(a, b, c)转换为可调用的f(a)(b)(c)

否则,当您将一个将多个自变量分解为一系列包含一部分自变量的函数时,便会遇到麻烦。

从字面上看,currying是函数的一种转换:从一种调用方式转换为另一种调用方式。在JavaScript中,我们通常做一个包装来保留原始功能。

固化不会调用函数。它只是改变了它。

让make curry函数为两个参数的函数执行curring。换句话说,两个参数curry(f)的{​​{1}}会将其转换为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)并提供2,并将调用传递给原始的多参数和。

答案 11 :(得分:3)

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);

这对于使复杂的代码变得整洁和处理不同步的方法等非常有用。

答案 12 :(得分:2)

currying的一个例子就是当你拥有函数时,你只知道其中一个参数:

例如:

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 / closure来将它发送给函数。

答案 13 :(得分:2)

正如所有其他答案currying有助于创建部分应用的功能。 Javascript不提供自动currying的本机支持。因此,上面提供的示例可能无助于实际编码。在lifecript中有一些很好的例子(基本上编译为js) http://livescript.net/

times = (x, y) --> x * y
times 2, 3       #=> 6 (normal use works as expected)
double = times 2
double 5         #=> 10

在上面的例子中,当你给出较少的参数时,你会为你生成新的curried函数(double)

答案 14 :(得分:2)

curried函数应用于多个参数列表,而不仅仅是 一。

这是一个常规的非curried函数,它增加了两个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时,实际上你会得到两个传统的函数调用。第一个功能 调用采用名为x的单个Int参数,并返回一个函数 第二个函数的值。第二个函数采用Int参数 y

这是一个名为first的函数,它在精神上与第一个传统函数相同 curriedSum的函数调用会:

scala> def first(x: Int) = (y: Int) => x + y
first: (x: Int)(Int) => Int

将1应用于第一个函数 - 换句话说,调用第一个函数 并传入1 -yields第二个函数:

scala> val second = first(1)
second: (Int) => Int = <function1>

将2应用于第二个函数会产生结果:

scala> second(2)
res6: Int = 3

答案 15 :(得分:1)

这是一个有意义的真实单词示例:

你去 ATM 取钱。您刷卡,输入密码并进行选择,然后按回车键提交请求旁边的“金额”。

这里是提款的正常功能。

const withdraw=(cardInfo,pinNumber,request){
    // process it
       return request.amount
}

在这个实现中,函数期望我们一次输入所有参数。我们要刷卡,输入密码并发出请求,然后函数就会运行。如果这些步骤中的任何一个有问题,您会在输入所有参数后发现。使用柯里化函数,我们将创建更高数量的、纯粹的和简单的函数。纯函数将帮助我们轻松调试代码。

这是带有咖喱函数的 Atm:

const withdraw=(cardInfo)=>(pinNumber)=>(request)=>request.amount

ATM,将卡作为输入并返回一个需要 pinNumber 的函数,该函数返回一个接受请求对象的函数,成功处理后您将获得请求的金额。每一步,如果你有一个错误,你会很容易地预测哪里出了问题。另外,这里的每个函数都是可重用的

答案 16 :(得分:0)

在这里您可以找到有关C#中的currying实现的简单说明。在评论中,我试图说明如何使用currying:

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);

答案 17 :(得分:0)

这是泛型的示例,是函数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)());

答案 18 :(得分:0)

有一个“在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));
  };

答案 19 :(得分:0)

“ currying”是获取多个参数的函数并将其转换为严肃的函数的过程,每个函数均接受单个参数并返回单个参数的函数,或者对于最终函数,返回实际结果。

答案 20 :(得分:0)

下面是 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)}`);

输出

multiplyByTwo(8) : 16