我经常在网上看到各种各样的抱怨,说其他人的套用例子并不是套用,而实际上只是部分应用。
我还没有找到一个像样的解释来解释什么是部分应用,或者它与咖喱有什么不同。这似乎是一种普遍的混淆,类似的例子在一些地方被描述为套用,在另一些地方被描述为部分应用。
谁能给我提供这两个术语的定义,以及它们之间的区别?
我经常在网上看到各种各样的抱怨,说其他人的套用例子并不是套用,而实际上只是部分应用。
我还没有找到一个像样的解释来解释什么是部分应用,或者它与咖喱有什么不同。这似乎是一种普遍的混淆,类似的例子在一些地方被描述为套用,在另一些地方被描述为部分应用。
谁能给我提供这两个术语的定义,以及它们之间的区别?
当前回答
要了解它们的不同之处,最简单的方法是考虑一个真实的例子。让我们假设我们有一个函数Add,它接受2个数字作为输入,并返回一个数字作为输出,例如Add(7,5)返回12。在这种情况下:
Partial applying the function Add with a value 7 will give us a new function as output. That function itself takes 1 number as input and outputs a number. As such: Partial(Add, 7); // returns a function f2 as output // f2 takes 1 number as input and returns a number as output So we can do this: f2 = Partial(Add, 7); f2(5); // returns 12; // f2(7)(5) is just a syntactic shortcut Currying the function Add will give us a new function as output. That function itself takes 1 number as input and outputs yet another new function. That third function then takes 1 number as input and returns a number as output. As such: Curry(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 So we can do this: f2 = Curry(Add); f3 = f2(7); f3(5); // returns 12
换句话说,“套用”和“局部应用”是两种完全不同的功能。curry只需要1个输入,而partial应用程序需要2个(或更多)输入。
尽管它们都返回一个函数作为输出,但返回的函数是完全不同的形式,如上所述。
其他回答
有趣的问题。经过一番搜索,“部分函数应用程序不是咖喱”给出了我找到的最好的解释。我不能说实际的区别对我来说特别明显,但我不是一个FP专家……
另一个看起来很有用的页面(我承认我还没有完全读完)是“用Java闭包curry和Partial Application”。
注意,这看起来确实是一对被广泛混淆的术语。
这里的很多人都没有正确地解决这个问题,也没有人谈论过重叠。
简单的答案
curry:让你调用一个函数,把它分成多个调用,每次调用提供一个参数。
部分应用:允许你调用一个函数,把它分成多个调用,每次调用提供多个参数。
两者之间的重要区别之一是调用 部分应用的函数立即返回结果,而不是另一个 沿着咖喱链;这种区别可以用例子来说明 显然,对于集度大于2的函数。
这是什么意思?这意味着对一个局部函数最多有两次调用。curry的参数和参数的数量一样多。如果curry函数只有两个参数,那么它本质上与偏函数相同。
例子
部分应用和咖喱
function bothPartialAndCurry(firstArgument) {
return function(secondArgument) {
return firstArgument + secondArgument;
}
}
const partialAndCurry = bothPartialAndCurry(1);
const result = partialAndCurry(2);
部分应用程序
function partialOnly(firstArgument, secondArgument) {
return function(thirdArgument, fourthArgument, fifthArgument) {
return firstArgument + secondArgument + thirdArgument + fourthArgument + fifthArgument;
}
}
const partial = partialOnly(1, 2);
const result = partial(3, 4, 5);
局部套用
function curryOnly(firstArgument) {
return function(secondArgument) {
return function(thirdArgument) {
return function(fourthArgument ) {
return function(fifthArgument) {
return firstArgument + secondArgument + thirdArgument + fourthArgument + fifthArgument;
}
}
}
}
}
const curryFirst = curryOnly(1);
const currySecond = curryFirst(2);
const curryThird = currySecond(3);
const curryFourth = curryThird(4);
const result = curryFourth(5);
// or...
const result = curryOnly(1)(2)(3)(4)(5);
命名约定
等我有时间再写,很快就有时间了。
curry是将一个有n个参数的函数转换为n个每个函数都有一个参数的函数。给定以下函数:
function f(x,y,z) { z(x(y));}
咖喱后,变成:
function f(x) { lambda(y) { lambda(z) { z(x(y)); } } }
为了得到f(x,y,z)的完整应用,你需要这样做:
f(x)(y)(z);
许多函数式语言允许你写fx y z。如果你只调用fx y或f(x)(y),那么你会得到一个部分应用的函数——返回值是lambda(z){z(x(y))}的闭包,将x和y的值传递给f(x,y)。
使用部分应用的一种方法是将函数定义为广义函数的部分应用,如fold:
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
在写这篇文章时,我混淆了咖喱和不咖喱。它们是函数的逆变换。不管你怎么称呼它,只要你知道这个变换和它的逆代表什么。
不咖喱的定义不是很清楚(或者说,有“冲突”的定义都抓住了这个想法的精神)。基本上,这意味着将一个接受多个参数的函数转换为一个接受单个参数的函数。例如,
(+) :: Int -> Int -> Int
现在,你如何把它变成一个只有一个参数的函数呢?你当然作弊了!
plus :: (Int, Int) -> Int
注意,加号现在只有一个参数(由两件事组成)。超级!
这有什么意义?好吧,如果你有一个带两个参数的函数,你有一对参数,知道你可以把函数应用到参数上,并且仍然得到你想要的是很好的。事实上,这样做的管道已经存在,所以你不需要做像显式模式匹配这样的事情。你所要做的就是:
(uncurry (+)) (1,2)
什么是偏函数应用?将有两个参数的函数转换为只有一个参数的函数是另一种方法。但它的工作方式不同。同样,让我们以(+)为例。我们如何将它转换为一个以单个Int作为参数的函数?我们作弊!
((+) 0) :: Int -> Int
这是一个函数,将0加到任何Int。
((+) 1) :: Int -> Int
对任意Int值加1。等。在每一种情况下,(+)是“部分应用”。
这里还有其他很好的答案,但我相信Java中的这个例子(根据我的理解)可能对一些人有益:
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 );
}
因此curry为您提供了一个单参数函数来创建函数,而partial-application则创建一个包装器函数,该包装器函数硬编码一个或多个参数。
如果你想复制和粘贴,下面的文件比较吵闹,但是使用起来比较友好,因为类型比较宽松:
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 );
}