我在几篇文章和博客中都看到过关于咖喱函数的引用,但我找不到很好的解释(或者至少有一个合理的解释!)
add x y = x+y
(咖喱)是不同的add (x, y)=x+y
(uncurried)
我在几篇文章和博客中都看到过关于咖喱函数的引用,但我找不到很好的解释(或者至少有一个合理的解释!)
add x y = x+y
(咖喱)是不同的add (x, y)=x+y
(uncurried)
Answers:
咖喱化是指将一个包含多个参数的函数分解为一系列仅包含一个参数的函数。这是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, 2, 3, 4, 5]
希望将其乘以任意数字。在Haskell中,我可以编写map (* 5) [1, 2, 3, 4, 5]
将整个列表乘以5
,从而生成list [5, 10, 15, 20, 25]
。
map
必须是仅接受一个参数的函数-列表中的元素。作为数学概念,乘法是一种二进制运算。它需要2个参数。但是,在Haskell中*
是咖喱函数,类似于add
此答案的第二个版本。结果(* 5)
是一个函数,该函数接受单个参数并将其乘以5,并允许我们将其与map一起使用。
在函数的代数中,处理带有多个参数(或等效的一个参数为N元组)的函数有些微不足道-但是,正如MosesSchönfinkel(以及独立的Haskell Curry)证明的那样,不需要它:所有人需要的是带有一个参数的函数。
那么,您如何处理自然表达的东西f(x,y)
呢?好吧,您将其等同于f(x)(y)
- f(x)
,称为它g
,是一个函数,然后将该函数应用于y
。换句话说,您只有带有一个参数的函数-但是其中一些函数会返回其他函数(也带有一个参数;-)。
像往常一样,维基百科对此有一个很好的总结条目,其中包含许多有用的指针(可能包括与您喜欢的语言有关的指针;-)以及更为严格的数学处理方法。
div :: Integral a => a -> a -> a
-注意那些多个箭头?“将a映射为功能,将a映射为a”是一个读法;-)。您可以为div
&c 使用(单个)元组参数,但这在Haskell中确实是反习惯用法的。
这是一个具体的例子:
假设您有一个计算作用在物体上的重力的函数。如果您不知道公式,可以在这里找到。此函数将三个必需参数作为参数。
现在,在地球上,您只想计算该星球上物体的力。用功能语言,您可以将地球质量传递给功能,然后对其进行部分评估。您会得到的是另一个仅包含两个参数并计算地球上物体的重力的函数。这被称为咖喱。
Currying是一种可以应用于函数的转换,以使它们比以前少使用一个参数。
例如,在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-> 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。
结论:-
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
它可以是使用功能来实现其他功能的一种方式。
在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)
我们可以以类似的方式获得抽象。
curried函数是重写了几个参数的函数,这样它可以接受第一个参数,并返回一个接受第二个参数的函数,依此类推。这允许几个自变量的功能部分地应用其某些初始自变量。
map
某个函数,则可以执行。f
xss
map (map f) xss
这是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实现此方法的部分对象与函数的区别。
咖喱化是将函数从可调用f(a, b, c)
转换为可调用f(a)(b)(c)
。
否则,当您分解一个将多个参数合并为一部分参数的函数时,就会遇到麻烦。
从字面上看,currying是功能的转换:从一种调用方式转换为另一种调用方式。在JavaScript中,我们通常做一个包装来保留原始功能。
咖喱不会调用函数。它只是改变了它。
让我们使咖喱函数对两个参数的函数执行curring。换句话说,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)
,参数将保存在Lexical Environment中,并返回一个新的包装器function(b)
。sum(1)(2)
最终调用function(b)
提供2,并将调用传递给原始的多参数和。如果您知道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
这是某些语言的行为方式。当人们希望将功能组合成更大的转换时,它特别有用。这将导致换能器。
我发现本文及其引用的文章对更好地理解curring很有用: 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为您生成新的咖喱函数(双精度)
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);
这对于使复杂的代码整齐并处理不同步的方法等非常有用。
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
,您实际上背对背获得了两个传统的函数调用。第一个函数调用采用一个名为的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
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 /闭包才能将其发送给函数。
func callback
返回自己?它被称为@ callback(str)
so let callback = callback(str)
,回调只是func callback
func callback(_:data:)
接受两个参数,这里我只给它一个,String
所以它在等待下一个(NSData
),这就是为什么现在let callback
另一个函数在等待数据传递
这是n为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)());
在这里,您可以找到有关C#中的currying实现的简单说明。在评论中,我试图说明如何使用curring:
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);
咖喱是Java Script的高级功能之一。
Currying是许多参数的函数,这些参数被重写,使得它采用第一个参数并返回一个函数,该函数又使用剩余的参数并返回值。
困惑?
让我们看一个例子,
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);
那么这段代码是什么意思呢?
现在再次阅读定义,
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
酷吧?
因此,更少的代码和更少的错误!
借助这些高阶函数,您可以轻松开发出无错误的代码。
我挑战你!
希望,您了解正在发生的事情。如果您需要任何说明,请随时在这里发表评论。
谢谢,祝你有美好的一天!
有一个“在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));
};
curry
和uncurry
函数。这里重要的是这些同构是预先固定的,因此是语言的“内置”。