什么是'Currying'?

Ben*_*Ben 628 functional-programming terminology definition currying

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

Kyl*_*nin 828

Currying是指您分解一个函数,该函数将多个参数带入一系列参数中的函数.这是JavaScript中的一个示例:

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

add(3, 4); // returns 7
Run Code Online (Sandbox Code Playgroud)

这是一个带有两个参数a和b的函数,并返回它们的总和.我们现在将讨论这个功能:

function add (a) {
  return function (b) {
    return a + b;
  }
}
Run Code Online (Sandbox Code Playgroud)

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

add(3)(4);

var add3 = add(3);

add3(4);
Run Code Online (Sandbox Code Playgroud)

第一个语句返回7,就像add(3,4)语句一样.第二个语句定义了一个名为add3的新函数,它将为其参数添加3.这是有些人可能称之为封闭的.第三个语句使用add3操作添加3到4,结果再次产生7.

  • 从实际意义上讲,我该如何利用这个概念? (221认同)
  • @Strawberry"map"的第一个参数必须是一个只接受1个参数的函数 - 列表中的一个元素.乘法 - 作为一个数学概念 - 是一个二元运算; 它需要2个参数.但是,在Haskell中,`*`是一个curried函数,类似于这个答案中第二个版本的`add`.`(*5)`的结果是一个函数,它接受一个参数并将它乘以5,这允许我们将它与map一起使用. (74认同)
  • 我理解地图功能的作用,但我不确定我是否理解你要为我说明的重点.你是说地图功能代表了currying的概念吗? (57认同)
  • @Strawberry,比如说你有一个"[1,2,3,4,5]"中的数字列表,你希望乘以一个任意数字.在Haskell中,我可以写`map(*5)[1,2,3,4,5]`将整个列表乘以`5`,从而生成列表`[5,10,15,20,25 ]`. (38认同)
  • @Strawberry关于标准ML或Haskell等函数式语言的优点在于你可以"免费"使用.您可以像在任何其他语言中一样定义多参数函数,并自动获得它的curried版本,而不必自己抛出一堆lambdas.因此,您可以生成新函数,从任何现有函数中获取较少的参数,而不必费心或烦恼,这使得将它们传递给其他函数变得容易. (24认同)
  • @Strawberry可能会进行面试.;) (11认同)
  • 我仍然不太明白你为什么要这样做. (10认同)
  • @SSHThis你知道JavaScript吗?我编辑了这个答案,使用JS而不是Scheme. (5认同)
  • 它对于缓存参数很有用,也许你需要多次调用一个函数,但第一个参数将是相同的,或者第一个参数可能在闭包中保持某种内部状态 (5认同)
  • 如果你发现你有一个函数,你传入一个永远不会改变的参数,那么柯里化很有用。(可能是一个有很多可重用方法的类?)而不是总是传入相同的参数,你把函数柯里化为只传入确实改变的参数 (5认同)
  • @OscarRyz这看起来不像是在跟我说话.如果你想使用过滤,比如`var greaterThan = x => y => y> x;`会让你讨厌更大的东西,这样你就可以像`[1,2,3,4,5]那样使用它.过滤器(GREATERTHAN(3))`. (2认同)

Ale*_*lli 124

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

那么你如何处理你自然表达的事情f(x,y)呢?好吧,你把它当作 - 等同于f(x)(y)- f(x),调用它g,是一个函数,并将该函数应用于y.换句话说,你只有一个参数的函数 - 但其中一些函数返回其他函数(也可以带一个参数;-).

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

  • 正如维基百科页面所述,大多数FP语言"修饰"或"增加"lambda演算(例如,使用一些常量和数据类型)而不仅仅是"应用"它,但它并不是那么接近.什么给你的印象是,例如Haskell不会"限制单一arg的功能"?可以肯定的是,尽管由于蜷缩而无关紧要; 例如`div :: Integral a => a - > a - > a` - 注意那些多个箭头?"将a映射到函数映射到a"是一个读数;-).你_could_为`div`&c使用(单个)元组参数,但这在Haskell中确实是反惯用的. (6认同)

She*_*els 96

这是一个具体的例子:

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

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

  • @neontapir是对的.Shea所描述的并不是在讨论.这是部分应用.如果一个三参数函数被curry并且你把它称为f(1),那么你得到的不是一个双参数函数.你得到一个单参数函数,它返回另一个单参数函数.curried函数只能传递一个参数.PrototypeJS中的咖喱功能也没有说明.这是部分应用. (9认同)
  • 这听起来像是对我的部分应用.我的理解是,如果你应用currying,你可以用一个参数创建函数并组合它们以形成更复杂的函数.我错过了什么吗? (7认同)
  • 作为一个好奇心,JavaScript的Prototype库提供了一个"curry"函数,它几乎完全按照你在这里解释的那样:http://www.prototypejs.org/api/function/curry (2认同)

ljs*_*ljs 46

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

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

let f x y z = x + y + z
Run Code Online (Sandbox Code Playgroud)

这里函数f取参数x,y和z并将它们加在一起,这样:

f 1 2 3
Run Code Online (Sandbox Code Playgroud)

退货6.

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

let curry f = fun x -> f x
Run Code Online (Sandbox Code Playgroud)

其中'fun x - > fx'是一个lambda函数,与C#中的x => f(x)等效.此函数输入您希望curry的函数并返回一个函数,该函数接受一个参数并返回指定的函数,并将第一个参数设置为input参数.

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

let curryf = curry f
Run Code Online (Sandbox Code Playgroud)

然后我们可以做以下事情: -

let f1 = curryf 1
Run Code Online (Sandbox Code Playgroud)

这为我们提供了函数f1,它与f1 yz = 1 + y + z等效.这意味着我们可以做到以下几点: -

f1 2 3
Run Code Online (Sandbox Code Playgroud)

哪个返回6.

这个过程经常与"部分功能应用"混淆,后者可以这样定义: -

let papply f x = f x
Run Code Online (Sandbox Code Playgroud)

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

let papply2 f x y = f x y
let papply3 f x y z = f x y z
etc.
Run Code Online (Sandbox Code Playgroud)

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

let f1 = f 1
f1 2 3
Run Code Online (Sandbox Code Playgroud)

这将返回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
Run Code Online (Sandbox Code Playgroud)

部分函数应用程序更直接 - 它接受一个函数和一个或多个参数,并返回一个函数,其中前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
Run Code Online (Sandbox Code Playgroud)


Adz*_*dzz 39

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

在javascript中:

let add = function(x){
  return function(y){ 
   return x + y
  };
};
Run Code Online (Sandbox Code Playgroud)

允许我们像这样称呼它:

let addTen = add(10);
Run Code Online (Sandbox Code Playgroud)

当这个运行时,10传入as x;

let add = function(10){
  return function(y){
    return 10 + y 
  };
};
Run Code Online (Sandbox Code Playgroud)

这意味着我们返回此功能:

function(y) { return 10 + y };
Run Code Online (Sandbox Code Playgroud)

所以当你打电话

 addTen();
Run Code Online (Sandbox Code Playgroud)

你真打电话:

 function(y) { return 10 + y };
Run Code Online (Sandbox Code Playgroud)

所以,如果你这样做:

 addTen(4)
Run Code Online (Sandbox Code Playgroud)

它是一样的:

function(4) { return 10 + 4} // 14
Run Code Online (Sandbox Code Playgroud)

因此,我们addTen()总是将10加入到我们传入的任何内容中.我们可以以相同的方式创建类似的函数:

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

  • 我在这里看到的一个固有顺序过程的最佳逐步解释,也许是该批次中最好的,最具解释性的答案. (5认同)
  • 最初的问题是“它是什么”,而不是它为什么有用。 (5认同)
  • 柯里化模式是一种将固定参数应用于现有函数的方法,其目的是创建新的可重用函数,而无需重新创建原始函数。这个答案很好地证明了这一点。 (4认同)
  • “我们至少可以做两件事 1. 缓存昂贵的操作 2. 在功能范式中实现抽象。” 这是其他答案所缺乏的“为什么它有用”的解释。我认为这个答案也很好地解释了“什么”。 (4认同)
  • @jonsilver我会说相反的话,不是一个很好的解释。我同意这很好地解释了所举的例子,但是人们倾向于不去思考,“是的,很清楚,但是我可以用另一种方式做同样的事情,所以有什么好处呢?”换句话说,我希望它有足够的上下文或说明,不仅阐明了如何进行弄乱,而且说明了与其他加法方法相比,它不是无用且琐碎的观察。 (2认同)

Jon*_*rop 27

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

  • "这允许多个参数的函数部分应用它们的一些初始参数." - 为什么这有益? (5认同)
  • @acarlon函数通常使用一个或多个参数重复调用.例如,如果你想在`xss`列表列表上"映射"函数`f`,你可以做`map(map f)xss`. (5认同)
  • 我认为这个答案以一种简洁的方式得到了正确的答案."currying"是获取多个参数的函数并将其转换为一系列函数的过程,每个函数都接受一个参数并返回单个参数的函数,或者在最终函数的情况下,返回实际结果.这可以通过语言自动完成,也可以调用其他语言的curry()函数来生成curried版本.请注意,使用参数调用curried函数不会产生干扰.讨论已经发生了. (4认同)

Ano*_*non 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..."
Run Code Online (Sandbox Code Playgroud)

(只需通过+使用连接,以避免分散非Python程序员的注意力.)

编辑添加:

请参阅http://docs.python.org/library/functools.html?highlight=partial#functools.partial,它还以Python实现此方式的方式显示了部分对象与函数的区别.


Pra*_*ani 6

这是通用的示例,也是带有 n 的函数柯里化的最短版本。参数。

const add = a => b => b ? add(a + b) : a; 
Run Code Online (Sandbox Code Playgroud)

const add = a => b => b ? add(a + b) : a; 
Run Code Online (Sandbox Code Playgroud)


Mar*_*rio 5

如果你明白partial你就成功了。的想法partial是将参数预应用于函数并返回一个只需要剩余参数的新函数。当这个新函数被调用时,它包括预加载的参数以及提供给它的任何参数。

在 Clojure 中+是一个函数,但让事情变得非常清楚:

(defn add [a b] (+ a b))
Run Code Online (Sandbox Code Playgroud)

您可能知道该inc函数只是将它传递的任何数字加 1。

(inc 7) # => 8
Run Code Online (Sandbox Code Playgroud)

让我们自己使用partial

(def inc (partial add 1))
Run Code Online (Sandbox Code Playgroud)

这里我们返回另一个函数,该函数将 1 加载到 的第一个参数中add。由于add需要两个参数,新inc函数只需要b参数——不像以前那样需要 2 个参数,因为 1 已经被部分应用。因此partial,它是一个工具,可以从中创建具有预先提供的默认值的新函数。这就是为什么在函数式语言中,函数通常将参数从一般排序到特定。这使得重用这些函数来构造其他函数变得更加容易。

现在想象一下,如果语言足够聪明,可以内省地理解add需要两个论点。当我们向它传递一个参数时,而不是犹豫不决,如果函数部分应用了我们代表我们传递的参数,我们理解我们可能打算稍后提供另一个参数怎么办?然后我们可以在inc不显式使用的情况下进行定义partial

(def inc (add 1)) #partial is implied
Run Code Online (Sandbox Code Playgroud)

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


小智 5

Currying is translating a function from callable as f(a, b, c) into callable as f(a)(b)(c).

Otherwise currying is when you break down a function that takes multiple arguments into a series of functions that take part of the arguments.

Literally, currying is a transformation of functions: from one way of calling into another. In JavaScript, we usually make a wrapper to keep the original function.

Currying doesn’t call a function. It just transforms it.

Let’s make curry function that performs currying for two-argument functions. In other words, curry(f) for two-argument f(a, b) translates it into 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
Run Code Online (Sandbox Code Playgroud)

As you can see, the implementation is a series of wrappers.

  • The result of curry(func) is a wrapper function(a).
  • When it is called like sum(1), the argument is saved in the Lexical Environment, and a new wrapper is returned function(b).
  • Then sum(1)(2) finally calls function(b) providing 2, and it passes the call to the original multi-argument sum.


小智 5

柯里化是 Java Script 的高阶函数之一。

柯里化是一个包含多个参数的函数,它被重写为接受第一个参数并返回一个函数,该函数依次使用剩余的参数并返回值。

使困惑?

看个例子,

function add(a,b)
    {
        return a+b;
    }
add(5,6);
Run Code Online (Sandbox Code Playgroud)

这类似于下面的柯里化函数,

function add(a)
    {
        return function(b){
            return a+b;
        }
    }
var curryAdd = add(5);
curryAdd(6);
Run Code Online (Sandbox Code Playgroud)

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

现在再次阅读定义,

柯里化是一个包含许多参数的函数,它被重写为接受第一个参数并返回一个函数,该函数依次使用剩余的参数并返回值。

还是,迷茫?让我深入解释一下!

当你调用这个函数时,

var curryAdd = add(5);
Run Code Online (Sandbox Code Playgroud)

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

curryAdd=function(y){return 5+y;}
Run Code Online (Sandbox Code Playgroud)

因此,这称为高阶函数。意思是,依次调用一个函数返回另一个函数是高阶函数的精确定义。这是传说中 Java Script 的最大优势。所以回到咖喱,

这一行将第二个参数传递给 curryAdd 函数。

curryAdd(6);
Run Code Online (Sandbox Code Playgroud)

结果,

curryAdd=function(6){return 5+6;}
// Which results in 11
Run Code Online (Sandbox Code Playgroud)

希望你在这里理解柯里化的用法。所以,来到优势,

为什么要咖喱?

它利用了代码的可重用性。更少的代码,更少的错误。你可能会问怎么是少代码?

我可以用 ECMA 脚本 6 新特性箭头函数来证明这一点。

是的!ECMA 6,为我们提供了一个叫做箭头函数的奇妙特性,

function add(a)
    {
        return function(b){
            return a+b;
        }
    }
Run Code Online (Sandbox Code Playgroud)

借助箭头函数,我们可以将上面的函数写成如下,

x=>y=>x+y
Run Code Online (Sandbox Code Playgroud)

酷吧?

所以,更少的代码和更少的错误!!

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

我挑战你!

希望,你明白什么是柯里化。如果您需要任何说明,请随时在此处发表评论。

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


Yil*_*maz 5

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

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

这是取款的正常功能。

const withdraw=(cardInfo,pinNumber,request){
    // process it
       return request.amount
}
Run Code Online (Sandbox Code Playgroud)

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

这是带有咖喱函数的 Atm:

const withdraw=(cardInfo)=>(pinNumber)=>(request)=>request.amount
Run Code Online (Sandbox Code Playgroud)

ATM 将卡作为输入并返回一个需要 pinNumber 的函数,该函数返回一个接受请求对象的函数,成功处理后,您将获得请求的金额。每一步,如果你有一个错误,你会很容易地预测哪里出了问题。假设您输入卡并出错,您知道它与卡或机器有关,但与密码无关。或者,如果您输入了 PIN 码但没有被接受,您就知道您输入了错误的 PIN 码。您将轻松调试错误。

此外,这里的每个函数都是可重用的,因此您可以在项目的不同部分使用相同的函数。