什么是“Currying”?
我已经在几篇文章和博客中看到了curried函数的引用,但我找不到一个好的解释(或者至less有一个是合理的!)
当你将一个函数分解为一系列包含参数的函数的时候,Currying就是这样。 以下是JavaScript中的一个例子:
function add (a, b) { return a + b; } add(3, 4); returns 7
这是一个函数,它带有两个参数a和b,并返回它们的和。 我们现在将咖喱这个function:
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元组的相当于一个参数)有点不雅观 – 但是正如摩西·舍恩克林(独立于哈斯克尔·库里)certificate的那样,它是不需要的:需要的是带有一个参数的函数。
那么,你如何处理你自然expression的东西,比如f(x,y)
呢? 那么,你把它当作f(x)(y)
– f(x)
等价物,称之为g
,是一个函数,并且你将这个函数应用于y
。 换句话说,你只有一个函数需要一个参数 – 但其中一些函数返回其他函数(这也有一个参数;-)。
像往常一样, 维基百科有一个很好的总结条目,有很多有用的指针(可能包括关于你最喜欢的语言;-)以及稍微严格的math处理。
这里有一个具体的例子:
假设你有一个计算作用在物体上的重力的函数。 如果你不知道公式,你可以在这里find它。 这个函数需要三个必要的参数作为参数。
现在,在地球上,你只需要计算这个星球上的物体的力量。 在一种function语言中,你可以将地球的质量传递给函数,然后对其进行部分的评估。 你会得到的是另一个函数,只需要两个参数,并计算地球上物体的引力。 这就是所谓的咖喱。
柯里里(Currying)是一种可以应用于函数的转换,使得它们比以前less用一个参数。
例如,在F#中,您可以定义一个函数,从而: –
let fxyz = x + y + z
这里函数f取参数x,y和z并将它们相加在一起:
f 1 2 3
退货6。
根据我们的定义,我们可以定义f的咖喱函数:
let curry f = fun x -> fx
其中'fun x – > fx'是一个等价于x => f(x)的lambda函数。 这个函数input你想要curry的函数,并返回一个只带一个参数的函数,并返回指定的函数,第一个参数设置为input参数。
使用我们以前的例子,我们可以得到一个咖喱f:
let curryf = curry f
我们可以做到以下几点:
let f1 = curryf 1
这为我们提供了一个与f1 yz = 1 + y + z等价的函数f1。 这意味着我们可以做到以下几点:
f1 2 3
哪个返回6。
这个过程常常与可以这样定义的“部分function应用”混淆:
let papply fx = fx
虽然我们可以将其扩展到多个参数,即:
let papply2 fxy = fxy let papply3 fxyz = fxyz etc.
部分应用程序将采用函数和参数并返回一个需要一个或多个参数的函数,而前面的两个示例显示的是直接在标准F#函数定义中实现的,因此我们可以实现以前的结果:
let f1 = f 1 f1 2 3
这将返回6的结果。
结论是:-
咖喱和部分function应用的区别在于:
Currying接受一个函数,并提供一个接受单个参数的新函数,并返回指定的函数,其第一个参数设置为该参数。 这使我们可以用多个参数来表示一系列单参数函数 。 例:-
let fxyz = 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 fxyz = x + y + z let f1 = f 1 let f2 = f 2 f1 2 3 6 f2 1 3 6
curried函数是几个参数重写的函数,它接受第一个参数并返回一个接受第二个参数的函数等等。 这允许几个参数的函数部分地应用它们的一些初始参数。
它可以是使用函数来实现其他function的一种方式。
在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) { 10 + y };
所以当你打电话
addTen();
你真的在打电话:
function(y) { 10 + y };
所以如果你这样做:
addTen(4)
这是一样的:
function(4) { 10 + 4} // 14
所以我们的addTen()
总是将十个添加到我们传入的任何东西中。我们可以用相同的方法创build类似的函数:
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 ,它也以Python实现的方式显示了部分对象和function的区别。
我发现这篇文章,它的文章引用,有用,更好地理解柯里: http : //blogs.msdn.com/wesdyer/archive/2007/01/29/currying-and-partial-function-application.aspx
正如其他人提到的,这只是一种具有单一参数function的方法。
这是非常有用的,因为您不必假设将传入多less个参数,因此不需要2个参数,3个参数和4个参数函数。
如果你明白partial
你在中途。 partial
的想法是将参数预先应用于一个函数,并返回一个只需要剩余参数的新函数。 当这个新函数被调用时,它包括预加载的参数以及提供给它的任何参数。
在Clojure +
是一个函数,但要使事情变得清晰:
(defn add [ab] (+ ab))
你可能知道inc
函数简单地把1加到它传递的任何数字上。
(inc 7) # => 8
让我们使用partial
:
(def inc (partial add 1))
这里我们返回另外一个函数,它被加载到add
的第一个参数中。 由于add
需要两个参数,所以新的inc
函数只需要b
参数 – 而不是像以前那样使用2个参数,因为1已经部分应用了。 因此, partial
是一个工具,可以使用默认值创build新的函数。 这就是为什么在一个函数式语言中,函数往往会把泛泛的论点从一般的到具体的。 这使得重用这些function来构build其他function变得更加容易。
现在想象一下,如果这个语言足够聪明,可以自省地理解,就add
两个参数。 当我们通过一个论证,而不是喋喋不休的时候,如果这个函数部分应用了我们代表的论点,我们理解我们可能以后提供另一个论点的话,那该怎么办呢? 然后我们可以定义inc
而不明确使用partial
。
(def inc (add 1)) #partial is implied
这是一些语言的行为方式。 当希望将function组合成更大的转换时,这是特别有用的。 这将导致一个换能器。
curried函数应用于多个参数列表,而不是一个。
这是一个常规的非curry函数,它添加了两个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
这里是类似的function,咖喱。 而不是一个两个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,得到第二个函数:
scala> val second = first(1) second: (Int) => Int = <function1>
对第二个函数应用2得出结果:
scala> second(2) res6: Int = 3
一个咖啡的例子就是当你有function的时候你只知道其中一个参数:
例如:
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(_:)
时候,你不知道第二个callback参数,所以你必须创build另一个lambda / closure来发送这个参数给函数。
我真的不能帮助自己,但它看起来像一个如何向后写recursion的方式。
典型的recursion – 在底部我有函数返回函数(本身在这种情况下),这是返回的function,这是…除非一些条件得到满足,或者我的电脑已经炸毁,如果我写错了。
fact = (n) => n == 0 ? 1 : n*fact(n-1);
从这里如此currying发生在我身上像手动写recursion的每一步。 也许这个名字是来自这个事实 – 重新+ CURR +锡安 – > CURR + ying。 我不知道,只是猜测。
为了给一个现实世界(和可能有用的)currying的例子检查如何可以使用fetch库在JavaScript中的服务器调用
Get(url) { let fullUrl = toFullUrl(url); let promise = getPromiseForFetchWithToken((token) => { let headers = Object.assign( getDefaultHeaders(token), jsonHeaders); let config = { method: "GET", headers: headers }; return fetch(fullUrl, config); }); return promise; }
getPromiseForFetchWithToken
是一个curry函数,返回一个带有提取结果的Promise
,如下所示:
function getPromiseForFetchWithToken(tokenConsumingFetch) { function resolver(resolve, reject) { let token = localStorage.getItem("token"); tokenConsumingFetch(token) .then(checkForError) .then((response) => { if (response) resolve(response); }) .catch(reject); } var promise = new Promise(resolver); return promise; }
这允许您等待Get
函数的调用,然后适当地处理返回值而不pipe它是什么,您可以在需要进行服务器调用的任何地方重新使用getPromiseForFetchWithToken
函数,以便包含不记名令牌。 (放,删除,张贴等)