我经常在网上看到各种各样的抱怨,说其他人的套用例子并不是套用,而实际上只是部分应用。
我还没有找到一个像样的解释来解释什么是部分应用,或者它与咖喱有什么不同。这似乎是一种普遍的混淆,类似的例子在一些地方被描述为套用,在另一些地方被描述为部分应用。
谁能给我提供这两个术语的定义,以及它们之间的区别?
我经常在网上看到各种各样的抱怨,说其他人的套用例子并不是套用,而实际上只是部分应用。
我还没有找到一个像样的解释来解释什么是部分应用,或者它与咖喱有什么不同。这似乎是一种普遍的混淆,类似的例子在一些地方被描述为套用,在另一些地方被描述为部分应用。
谁能给我提供这两个术语的定义,以及它们之间的区别?
当前回答
这里还有其他很好的答案,但我相信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 );
}
其他回答
要了解它们的不同之处,最简单的方法是考虑一个真实的例子。让我们假设我们有一个函数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个(或更多)输入。
尽管它们都返回一个函数作为输出,但返回的函数是完全不同的形式,如上所述。
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:当部分应用时,不要在lambda下求值。所以,为了让部分应用变得有趣,我们需要在局部应用和局部应用之间做出区别,并将局部应用视为在lambda下的局部应用加上求值。
在写这篇文章时,我混淆了咖喱和不咖喱。它们是函数的逆变换。不管你怎么称呼它,只要你知道这个变换和它的逆代表什么。
不咖喱的定义不是很清楚(或者说,有“冲突”的定义都抓住了这个想法的精神)。基本上,这意味着将一个接受多个参数的函数转换为一个接受单个参数的函数。例如,
(+) :: Int -> Int -> Int
现在,你如何把它变成一个只有一个参数的函数呢?你当然作弊了!
plus :: (Int, Int) -> Int
注意,加号现在只有一个参数(由两件事组成)。超级!
这有什么意义?好吧,如果你有一个带两个参数的函数,你有一对参数,知道你可以把函数应用到参数上,并且仍然得到你想要的是很好的。事实上,这样做的管道已经存在,所以你不需要做像显式模式匹配这样的事情。你所要做的就是:
(uncurry (+)) (1,2)
什么是偏函数应用?将有两个参数的函数转换为只有一个参数的函数是另一种方法。但它的工作方式不同。同样,让我们以(+)为例。我们如何将它转换为一个以单个Int作为参数的函数?我们作弊!
((+) 0) :: Int -> Int
这是一个函数,将0加到任何Int。
((+) 1) :: Int -> Int
对任意Int值加1。等。在每一种情况下,(+)是“部分应用”。
我在另一个帖子https://stackoverflow.com/a/12846865/1685865中回答了这个问题。简而言之,部分函数应用是关于固定一个给定的多变量函数的一些参数,以产生另一个具有更少参数的函数,而Currying是关于将一个有N个参数的函数转换为一个返回一元函数的一元函数…[一个curry的例子显示在这篇文章的最后。]
curry主要是理论方面的兴趣:可以只用一元函数来表示计算(即每个函数都是一元函数)。在实践中,它是一种技术,可以使许多有用的(但不是全部)部分函数式应用程序变得微不足道,如果语言有咖喱函数的话。同样,它不是实现部分应用程序的唯一方法。所以你可能会遇到这样的情况,部分应用程序是用其他方式完成的,但人们会把它误认为是curry。
(以咖喱为例)
在实践中,人们不只是写
lambda x: lambda y: lambda z: x + y + z
或者等价的javascript
function (x) { return function (y){ return function (z){ return x + y + z }}}
而不是
lambda x, y, z: x + y + z
为了柯里林。