我经常在网上看到各种各样的抱怨,说其他人的套用例子并不是套用,而实际上只是部分应用。
我还没有找到一个像样的解释来解释什么是部分应用,或者它与咖喱有什么不同。这似乎是一种普遍的混淆,类似的例子在一些地方被描述为套用,在另一些地方被描述为部分应用。
谁能给我提供这两个术语的定义,以及它们之间的区别?
我经常在网上看到各种各样的抱怨,说其他人的套用例子并不是套用,而实际上只是部分应用。
我还没有找到一个像样的解释来解释什么是部分应用,或者它与咖喱有什么不同。这似乎是一种普遍的混淆,类似的例子在一些地方被描述为套用,在另一些地方被描述为部分应用。
谁能给我提供这两个术语的定义,以及它们之间的区别?
当前回答
注意:本文摘自f# Basics,这是一篇非常好的介绍性文章,供。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: let multiply x y = x * y let double = multiply 2 let ten = double 5 Right 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: let double2 z = multiply 2 z Often, 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中的这个例子(根据我的理解)可能对一些人有益:
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 );
}
I could be very wrong here, as I don't have a strong background in theoretical mathematics or functional programming, but from my brief foray into FP, it seems that currying tends to turn a function of N arguments into N functions of one argument, whereas partial application [in practice] works better with variadic functions with an indeterminate number of arguments. I know some of the examples in previous answers defy this explanation, but it has helped me the most to separate the concepts. Consider this example (written in CoffeeScript for succinctness, my apologies if it confuses further, but please ask for clarification, if needed):
# 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
这显然是一个人为的例子,但请注意,部分应用一个接受任意数量参数的函数允许我们使用一些初步数据来执行函数。curry函数与此类似,但允许我们分批执行N个参数的函数,直到,但只是直到,所有N个参数都被考虑。
再说一次,这是我从我读过的东西中得出的结论。如果有人不同意,我希望能就原因发表评论,而不是立即投反对票。另外,如果CoffeeScript很难阅读,请访问coffeescript.org,点击“尝试CoffeeScript”并粘贴我的代码来查看编译后的版本,这可能(希望)更有意义。谢谢!
在学习的过程中,我经常有这个问题,后来也被问过很多次。我能描述的最简单的方式是两者都是一样的:)让我解释一下…有明显的区别。
部分应用程序和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(最终计算中的参数总数)到部分应用程序实现,这可能会将我的两个描述合并成令人困惑的混乱…但在这一点上,这两种技术在很大程度上是可以互换的。
要了解它们的不同之处,最简单的方法是考虑一个真实的例子。让我们假设我们有一个函数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个(或更多)输入。
尽管它们都返回一个函数作为输出,但返回的函数是完全不同的形式,如上所述。
在写这篇文章时,我混淆了咖喱和不咖喱。它们是函数的逆变换。不管你怎么称呼它,只要你知道这个变换和它的逆代表什么。
不咖喱的定义不是很清楚(或者说,有“冲突”的定义都抓住了这个想法的精神)。基本上,这意味着将一个接受多个参数的函数转换为一个接受单个参数的函数。例如,
(+) :: Int -> Int -> Int
现在,你如何把它变成一个只有一个参数的函数呢?你当然作弊了!
plus :: (Int, Int) -> Int
注意,加号现在只有一个参数(由两件事组成)。超级!
这有什么意义?好吧,如果你有一个带两个参数的函数,你有一对参数,知道你可以把函数应用到参数上,并且仍然得到你想要的是很好的。事实上,这样做的管道已经存在,所以你不需要做像显式模式匹配这样的事情。你所要做的就是:
(uncurry (+)) (1,2)
什么是偏函数应用?将有两个参数的函数转换为只有一个参数的函数是另一种方法。但它的工作方式不同。同样,让我们以(+)为例。我们如何将它转换为一个以单个Int作为参数的函数?我们作弊!
((+) 0) :: Int -> Int
这是一个函数,将0加到任何Int。
((+) 1) :: Int -> Int
对任意Int值加1。等。在每一种情况下,(+)是“部分应用”。