What is the difference between currying and partial application?
我经常在网上看到各种各样的抱怨,说其他人用咖喱的例子不是咖喱,实际上只是部分应用而已。
我还没有找到一个恰当的解释来解释部分应用是什么,或者它与当前应用有什么不同。这似乎是一个普遍的混淆,在某些地方,等价的例子被描述为货币化,而在其他地方,部分应用。
有人能给我一个关于这两个术语的定义,以及它们的区别的细节吗?
currying是将n个参数的单个函数转换为n个函数,每个函数都有一个参数。给定以下函数:
1 | function f(x,y,z) { z(x(y));} |
课程设置后,变为:
1 | function f(x) { lambda(y) { lambda(z) { z(x(y)); } } } |
号
为了得到f(x,y,z)的完整应用,需要这样做:
1 | f(x)(y)(z); |
许多函数语言允许您编写
使用部分应用的一种方法是将函数定义为广义函数的部分应用,如fold:
1 2 3 4 5 6 7 8 9 10 11 12 | function fold(combineFunction, accumulator, list) {/* ... */} function sum = curry(fold)(lambda(accum,e){e+accum}))(0); function length = curry(fold)(lambda(accum,_){1+accum})(empty-list); function reverse = curry(fold)(lambda(accum,e){concat(e,accum)})(empty-list); /* ... */ @list = [1, 2, 3, 4] sum(list) //returns 10 @f = fold(lambda(accum,e){e+accum}) //f = lambda(accumulator,list) {/*...*/} f(0,list) //returns 10 @g = f(0) //same as sum g(list) //returns 10 |
。
最简单的方法是考虑一个真实的例子来看看它们有什么不同。假设我们有一个函数
部分应用值为
7 的函数Add ,我们将得到一个新的输出函数。该函数本身接受1个数字作为输入,并输出一个数字。像这样的:1
2
3Partial(Add, 7); // returns a function f2 as output
// f2 takes 1 number as input and returns a number as output。
所以我们可以这样做:
1
2
3f2 = Partial(Add, 7);
f2(5); // returns 12;
// f2(7)(5) is just a syntactic shortcut。
转换函数
Add 将为我们提供一个新的输出函数。该函数本身接受1个数字作为输入,并输出另一个新函数。然后,第三个函数接受1个数字作为输入,并返回一个数字作为输出。像这样的:1
2
3
4
5
6
7Curry(Add); // returns a function f2 as output
// f2 takes 1 number as input and returns a function f3 as output
// i.e. f2(number) = f3
// f3 takes 1 number as input and returns a number as output
// i.e. f3(number) = number所以我们可以这样做:
1
2
3f2 = Curry(Add);
f3 = f2(7);
f3(5); // returns 12。
换句话说,"currying"和"partial application"是两个完全不同的函数。当前只需要1个输入,而部分应用需要2个(或更多)输入。
尽管它们都返回一个函数作为输出,但返回的函数的形式完全不同,如上面所示。
注意:这是摘自F基础的一篇优秀的介绍性文章,供.NET开发人员进入函数式编程。
Currying means breaking a function with many arguments into a series
of functions that each take one argument and ultimately produce the
same result as the original function. Currying is probably the most
challenging topic for developers new to functional programming, particularly because it
is often confused with partial application. You can see both at work
in this example:
1
2
3 let multiply x y = x * y
let double = multiply 2
let ten = double 5Right away, you should see behavior that is different from most
imperative languages. The second statement creates a new function
called double by passing one argument to a function that takes two.
The result is a function that accepts one int argument and yields the
same output as if you had called multiply with x equal to 2 and y
equal to that argument. In terms of behavior, it’s the same as this
code:
1 let double2 z = multiply 2 zOften, people mistakenly say that multiply is curried to form double.
But this is only somewhat true. The multiply function is curried, but
that happens when it is defined because functions in F# are curried by
default. When the double function is created, it’s more accurate to
say that the multiply function is partially applied.The multiply function is really a series of two functions. The first
function takes one int argument and returns another function,
effectively binding x to a specific value. This function also accepts
an int argument that you can think of as the value to bind to y. After
calling this second function, x and y are both bound, so the result is
the product of x and y as defined in the body of double.To create double, the first function in the chain of multiply
functions is evaluated to partially apply multiply. The resulting
function is given the name double. When double is evaluated, it uses
its argument along with the partially applied value to create the
result.
号
有趣的问题。经过一番搜索,"部分功能应用不流行"给出了我发现的最好的解释。我不能说实际的区别对我来说特别明显,但我不是一个金融政策专家…
另一个有用的页面(我承认我还没有完全阅读)是"用Java闭包进行部分和应用"。
看起来这是一对被广泛混淆的术语,请注意。
我在另一个线程https://stackoverflow.com/a/12846865/1685865中回答了这个问题。简而言之,部分函数应用程序是关于固定给定多变量函数的一些参数以生成另一个参数较少的函数,而curring是关于将n个参数的函数转换为返回一元函数的一元函数…[本帖末尾显示了一个curring示例。]
货币化主要是理论上的兴趣:人们只能用一元函数(即每个函数都是一元函数)来表示计算。在实践中,作为一种副产品,它是一种技术,它可以使许多有用的(但不是全部)部分功能应用程序变得微不足道,如果语言具有课程功能的话。同样,它不是实现部分应用程序的唯一方法。因此,您可能会遇到这样的场景:部分应用程序是以其他方式完成的,但人们会将其误认为是货币化。
(货币示例)
在实践中,一个人不会只写
1 | lambda x: lambda y: lambda z: x + y + z |
或等效的javascript
1 | function (x) { return function (y){ return function (z){ return x + y + z }}} |
。
而不是
1 | lambda x, y, z: x + y + z |
。
为了讨价还价。
currying是一个参数的函数,它接受一个函数
1 2 3 | curry(f) = h f: (X x Y) -> Z h: X -> (Y -> Z) |
部分应用程序是两个(或多个)参数的函数,它接受一个函数
1 2 3 | part(f, 2) = g f: (X x Y) -> Z g: Y -> Z |
。
产生混淆的原因是,对于双参数函数,以下等式成立:
1 | partial(f, a) = curry(f)(a) |
双方将产生相同的单参数函数。
对于更高的arity函数,等式不是真的,因为在这种情况下,currying将返回单参数函数,而分部应用程序将返回多参数函数。
不同之处还在于行为,而currying递归地转换整个原始函数(每个参数一次),部分应用程序只是一步替换。
资料来源:维基百科。
咖喱和部分应用程序之间的区别可以通过下面的javascript示例得到最好的说明:
1 2 3 4 5 6 7 | function f(x, y, z) { return x + y + z; } var partial = f.bind(null, 1); 6 === partial(2, 3); |
部分应用会产生较小的arity函数;在上面的示例中,
进一步阅读:
- 在5分钟内完成功能编程
- 电流:与部分功能应用的对比
简单的答案
Curry:允许您调用一个函数,将其拆分为多个调用,每个调用提供一个参数。
部分:允许您调用一个函数,将其拆分为多个调用,为每个调用提供多个参数。
简单提示这两种方法都允许您调用提供较少参数的函数(或者更好的方法是累积地提供参数)。实际上,这两个函数都(在每次调用时)将特定的值绑定到函数的特定参数。
当函数有两个以上的参数时,可以看到实际的差异。
简单E(C)(示例)(在javascript中)
1 | function process(context, success_callback, error_callback, subject) {...} |
为什么总是传递参数,比如上下文和回调,如果它们总是相同的话?只需为函数绑定一些值
1 | processSubject = _.partial(process, my_context, my_success, my_error) |
号
在主题1和foobar上用
1 2 | processSubject('subject1'); processSubject('foobar'); |
舒服,不是吗????
用currying,你每次需要传递一个参数。
1 2 3 4 5 6 7 8 | curriedProcess = _.curry(process); processWithBoundedContext = curriedProcess(my_context); processWithCallbacks = processWithBoundedContext(my_success)(my_error); // note: these are two sequential calls result1 = processWithCallbacks('subject1'); // same as: process(my_context, my_success, my_error, 'subject1'); result2 = processWithCallbacks('foobar'); // same as: process(my_context, my_success, my_error, 'foobar'); |
。免责声明
我跳过了所有的学术/数学解释。因为我不知道。也许有帮助??
我在学习过程中经常遇到这个问题,后来被问了很多次。我可以用最简单的方式来描述两者的区别:让我解释一下……有明显的区别。
部分应用和货币化都涉及到为函数提供参数,可能不是同时提供所有参数。一个相当规范的例子是添加两个数字。在伪代码(实际上是没有关键字的JS)中,基函数可以是以下函数:
1 | add = (x, y) => x + y |
。
如果我想要一个"addone"函数,我可以部分地应用它,或者使用它:
1 2 | addOneC = curry(add, 1) addOneP = partial(add, 1) |
。
现在使用它们很明显:
1 2 | addOneC(2) #=> 3 addOneP(2) #=> 3 |
那有什么区别呢?嗯,这很微妙,但是部分应用程序需要提供一些参数,然后返回的函数将在下次调用时执行主函数,而curring将一直等待,直到它拥有所有必要的参数:
1 2 3 4 5 6 | curriedAdd = curry(add) # notice, no args are provided addOne = curriedAdd(1) # returns a function that can be used to provide the last argument addOne(2) #=> returns 3, as we want partialAdd = partial(add) # no args provided, but this still returns a function addOne = partialAdd(1) # oops! can only use a partially applied function once, so now we're trying to add one to an undefined value (no second argument), and we get an error |
。
简而言之,使用分部应用程序预先填充一些值,知道下次调用该方法时,它将执行,留下未定义的所有未提供的参数;如果您希望连续返回一个部分应用的函数,则使用currying来完成函数签名。最后一个人为的例子:
1 2 3 4 5 | curriedAdd = curry(add) curriedAdd()()()()()(1)(2) # ugly and dumb, but it works partialAdd = partial(add) partialAdd()()()()()(1)(2) # second invocation of those 7 calls fires it off with undefined parameters |
希望这有帮助!
更新:某些语言或lib实现将允许您向部分应用程序实现传递arity(最终评估中的参数总数),这可能会将我的两个描述混为一谈……但此时,这两种技术在很大程度上是可以互换的。
在这里我可能是错的,因为我在理论数学或函数编程方面没有很强的背景,但是从我对fp的简短研究来看,currying似乎倾向于将n个参数的函数转换为一个参数的n个函数,而部分应用[在实践中]更适用于具有不确定性的变量函数。参数数目。我知道前面答案中的一些例子与这个解释不符,但它帮助我最大程度地分离了概念。考虑下这个例子(写在咖啡描述中是为了简洁,如果它进一步混淆了我的道歉,但是如果需要,请要求澄清):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | # partial application partial_apply = (func) -> args = [].slice.call arguments, 1 -> func.apply null, args.concat [].slice.call arguments sum_variadic = -> [].reduce.call arguments, (acc, num) -> acc + num add_to_7_and_5 = partial_apply sum_variadic, 7, 5 add_to_7_and_5 10 # returns 22 add_to_7_and_5 10, 11, 12 # returns 45 # currying curry = (func) -> num_args = func.length helper = (prev) -> -> args = prev.concat [].slice.call arguments return if args.length < num_args then helper args else func.apply null, args helper [] sum_of_three = (x, y, z) -> x + y + z curried_sum_of_three = curry sum_of_three curried_sum_of_three 4 # returns a function expecting more arguments curried_sum_of_three(4)(5) # still returns a function expecting more arguments curried_sum_of_three(4)(5)(6) # returns 15 curried_sum_of_three 4, 5, 6 # returns 15 |
这显然是一个人为的例子,但是请注意,部分应用一个接受任意数量参数的函数可以让我们执行一个函数,但是使用一些初始数据。当前一个函数是类似的,但它允许我们以片段形式执行一个n参数函数,直到,但只有直到,所有n参数都被计算在内。
再说一遍,这是我从我读过的东西中得到的。如果有人不同意,我会很高兴就原因发表评论,而不是立即投反对票。此外,如果咖啡脚本很难阅读,请访问coffeescript.org,单击"Try coffeescript"并粘贴到我的代码中,以查看编译后的版本,这可能(希望)更有意义。谢谢!
对于我来说,部分应用程序必须创建一个新函数,其中使用的参数完全集成到结果函数中。
大多数函数语言通过返回一个闭包来实现curring:当部分应用时,不要在lambda下计算。因此,为了使部分应用有趣,我们需要在当前应用和部分应用之间做出区别,并将部分应用视为lambda下的currying plus计算。
这里还有其他很好的答案,但我相信这个例子(根据我的理解)在爪哇可能对某些人有益:
1 2 3 4 5 6 7 8 9 10 11 | public static <A,B,X> Function< B, X > partiallyApply( BiFunction< A, B, X > aBiFunction, A aValue ){ return b -> aBiFunction.apply( aValue, b ); } public static <A,X> Supplier< X > partiallyApply( Function< A, X > aFunction, A aValue ){ return () -> aFunction.apply( aValue ); } public static <A,B,X> Function< A, Function< B, X > > curry( BiFunction< A, B, X > bif ){ return a -> partiallyApply( bif, a ); } |
所以curring为您提供了一个单参数函数来创建函数,其中部分应用程序创建了一个包装函数,它硬编码一个或多个参数。
如果您想要复制和粘贴,那么下面的内容会比较嘈杂,但使用起来比较友好,因为这些类型比较宽松:
1 2 3 4 5 6 7 8 9 10 11 | public static <A,B,X> Function< ? super B, ? extends X > partiallyApply( final BiFunction< ? super A, ? super B, X > aBiFunction, final A aValue ){ return b -> aBiFunction.apply( aValue, b ); } public static <A,X> Supplier< ? extends X > partiallyApply( final Function< ? super A, X > aFunction, final A aValue ){ return () -> aFunction.apply( aValue ); } public static <A,B,X> Function< ? super A, Function< ? super B, ? extends X > > curry( final BiFunction< ? super A, ? super B, ? extends X > bif ){ return a -> partiallyApply( bif, a ); } |
号
我假设大多数问这个问题的人已经熟悉基本概念,所以他们不需要谈论这些。重叠部分才是令人困惑的部分。
您可能能够充分地使用这些概念,但是您将它们理解为这个伪原子的非晶态概念模糊。缺少的是知道它们之间的边界在哪里。
而不是定义每一个是什么,它更容易强调只是他们的差异的边界。
当你定义函数的时候,就产生了货币化。
部分应用程序是在调用函数时使用的。
应用程序是调用函数的数学语言。
部分应用程序要求调用curried函数并获取作为返回类型的函数。
在写这篇文章时,我把咖喱和未咖喱混为一谈。它们是函数的逆变换。只要你得到了转换和它的逆表示,你称之为哪一个其实并不重要。
未翻阅的定义不是很清楚(或者更确切地说,有"冲突"的定义,所有这些都抓住了思想的精神)。基本上,它意味着将一个接受多个参数的函数转换为一个接受单个参数的函数。例如,
1 | (+) :: Int -> Int -> Int |
现在,如何将其转换为一个接受单个参数的函数?你当然作弊了!
1 | plus :: (Int, Int) -> Int |
号
注意,plus现在只接受一个参数(由两个部分组成)。超级的!
这有什么意义?如果有一个函数接受两个参数,并且有一对参数,那么很高兴知道您可以将该函数应用于参数,并且仍然得到您期望的结果。而且,事实上,完成它的管道已经存在,所以您不必做像显式模式匹配这样的事情。你所要做的就是:
1 | (uncurry (+)) (1,2) |
那么什么是偏函数应用呢?将两个参数中的函数转换为一个参数中的函数是另一种方法。但它的工作方式不同。再次,让我们以(+)为例。我们怎样才能把它变成一个以单个int为参数的函数呢?我们作弊!
1 | ((+) 0) :: Int -> Int |
。
这是向任何int加零的函数。
1 | ((+) 1) :: Int -> Int |
在每种情况下,在任何int.etc.中加1,"(+)都是"部分应用的"。