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

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

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


当前回答

对于我来说,部分应用程序必须创建一个新函数,其中使用的参数完全集成到结果函数中。

大多数函数式语言通过返回一个闭包来实现curry:当部分应用时,不要在lambda下求值。所以,为了让部分应用变得有趣,我们需要在局部应用和局部应用之间做出区别,并将局部应用视为在lambda下的局部应用加上求值。

其他回答

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

部分应用程序和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(最终计算中的参数总数)到部分应用程序实现,这可能会将我的两个描述合并成令人困惑的混乱…但在这一点上,这两种技术在很大程度上是可以互换的。

局部套用

维基百科上说

curry是一种将带有多个参数的函数转换为带有单个参数的函数序列的技术。

例子

const add = (a, b) => a + b

const addC = (a) => (b) => a + b // curried function. Where C means curried

部分应用程序

文章刚刚好FP:部分应用

部分应用是将部分(而不是全部)参数应用到函数,并返回一个新函数等待其余参数。这些应用的参数存储在闭包中,并且将来对任何部分应用的返回函数仍然可用。

例子

const add = (a) => (b) => a + b

const add3 = add(3) // add3 is a partially applied function

add3(5) // 8

区别在于

咖喱是一种技术(模式) Partial application是一个带有一些预定义参数的函数(如前面示例中的add3)

有趣的问题。经过一番搜索,“部分函数应用程序不是咖喱”给出了我找到的最好的解释。我不能说实际的区别对我来说特别明显,但我不是一个FP专家……

另一个看起来很有用的页面(我承认我还没有完全读完)是“用Java闭包curry和Partial Application”。

注意,这看起来确实是一对被广泛混淆的术语。

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

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”并粘贴我的代码来查看编译后的版本,这可能(希望)更有意义。谢谢!