我经常在网上看到各种各样的抱怨,说其他人的套用例子并不是套用,而实际上只是部分应用。

我还没有找到一个像样的解释来解释什么是部分应用,或者它与咖喱有什么不同。这似乎是一种普遍的混淆,类似的例子在一些地方被描述为套用,在另一些地方被描述为部分应用。

谁能给我提供这两个术语的定义,以及它们之间的区别?


当前回答

在写这篇文章时,我混淆了咖喱和不咖喱。它们是函数的逆变换。不管你怎么称呼它,只要你知道这个变换和它的逆代表什么。

不咖喱的定义不是很清楚(或者说,有“冲突”的定义都抓住了这个想法的精神)。基本上,这意味着将一个接受多个参数的函数转换为一个接受单个参数的函数。例如,

(+) :: Int -> Int -> Int

现在,你如何把它变成一个只有一个参数的函数呢?你当然作弊了!

plus :: (Int, Int) -> Int

注意,加号现在只有一个参数(由两件事组成)。超级!

这有什么意义?好吧,如果你有一个带两个参数的函数,你有一对参数,知道你可以把函数应用到参数上,并且仍然得到你想要的是很好的。事实上,这样做的管道已经存在,所以你不需要做像显式模式匹配这样的事情。你所要做的就是:

(uncurry (+)) (1,2)

什么是偏函数应用?将有两个参数的函数转换为只有一个参数的函数是另一种方法。但它的工作方式不同。同样,让我们以(+)为例。我们如何将它转换为一个以单个Int作为参数的函数?我们作弊!

((+) 0) :: Int -> Int

这是一个函数,将0加到任何Int。

((+) 1) :: Int -> Int

对任意Int值加1。等。在每一种情况下,(+)是“部分应用”。

其他回答

curry和partial application之间的区别可以通过下面的JavaScript示例来最好地说明:

function f(x, y, z) {
    return x + y + z;
}

var partial = f.bind(null, 1);

6 === partial(2, 3);

局部应用的结果是一个更小的函数;在上面的例子中,f的arity是3,而partial的arity只有2。更重要的是,部分应用的函数将在被调用时立即返回结果,而不是沿着curry链向下的另一个函数。所以如果你看到的是偏(2)偏(3),实际上这不是偏应用。

进一步阅读:

函数式编程5分钟 咖喱:与部分函数应用的对比

我假设大多数问这个问题的人已经熟悉了基本概念,所以他们没有必要谈论这个。重叠是令人困惑的部分。

您可能能够充分使用这些概念,但您将它们一起理解为伪原子无定形的概念模糊。现在缺少的是知道它们之间的界限在哪里。

与其定义它们是什么,不如简单地强调它们的不同之处——边界。

curry是在定义函数的时候。

部分应用程序是在调用函数时。

应用程序是调用函数的数学术语。

部分应用程序需要调用一个curry函数并获得一个函数作为返回类型。

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

在学习的过程中,我经常有这个问题,后来也被问过很多次。我能描述的最简单的方式是两者都是一样的:)让我解释一下…有明显的区别。

部分应用程序和curry都涉及向函数提供参数,可能不是一次全部提供。一个相当典型的例子是两个数字相加。在伪代码(实际上是没有关键字的JS)中,基函数可能如下所示:

add = (x, y) => x + y

如果我想要一个“addOne”函数,我可以部分应用它或curry它:

addOneC = curry(add, 1)
addOneP = partial(add, 1)

现在使用它们是很清楚的:

addOneC(2) #=> 3
addOneP(2) #=> 3

那么有什么不同呢?好吧,这很微妙,但部分应用程序涉及提供一些参数,然后返回的函数将在下次调用时执行主函数,而curry将一直等待,直到它拥有所有必要的参数:

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

简而言之,使用partial application预填充一些值,知道下次调用该方法时,它将执行,所有未提供的参数都未定义;当您希望连续多次返回部分应用的函数以实现函数签名时,请使用curry。最后一个人为的例子:

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

希望这能有所帮助!

更新:一些语言或库实现将允许您传递一个arity(最终计算中的参数总数)到部分应用程序实现,这可能会将我的两个描述合并成令人困惑的混乱…但在这一点上,这两种技术在很大程度上是可以互换的。

这里还有其他很好的答案,但我相信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 );
}