咖喱和部分应用的区别是什么?

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

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

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

63222 次浏览

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

另一个看起来很有用的页面(我承认我还没有完全读完)是使用Java闭包的局部应用程序

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

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);

许多函数式语言允许你编写f x y z。如果你只调用f x yf (x) (y),那么你会得到一个部分应用的函数——返回值是lambda(z){z(x(y))}的一个闭包,将x和y的值传递给f(x,y)

使用部分应用程序的一种方法是将函数定义为广义函数的部分应用,如褶皱:

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

注意:本文摘自f#基础知识,这是一篇非常好的介绍性文章,供。net开发人员学习函数式编程。

curiling意思是把一个有很多参数的函数分解成一个系列 每个函数接受一个参数并最终生成 结果和原来的函数一样。咖喱可能是最常见的 对于函数式编程的新开发人员来说,这是一个具有挑战性的话题,特别是因为它 常与局部应用相混淆。两者都可以在工作中看到 本例中:

let multiply x y = x * y
let double = multiply 2
let ten = double 5
马上,你就会看到与大多数人不同的行为 命令式语言。第二条语句创建一个新函数 通过将一个参数传递给接受两个参数的函数来调用double。 函数的结果是接受一个int参数并产生 输出和你调用乘法x = 2和y是一样的 等于这个角。在行为方面,它和这个是一样的 代码:< / p >
let double2 z = multiply 2 z
通常,人们错误地认为multiply是curcury,即double。 但这只是在一定程度上是正确的。乘法函数是咖喱的,但是 这发生在定义它的时候,因为f#中的函数被curcurry 违约。当创建double函数时,它更准确 假设乘法函数被部分应用。

乘法函数实际上是两个函数的序列。第一个 函数接受一个int参数并返回另一个函数, 有效地将x绑定到一个特定的值。这个函数也接受 一个int型参数,你可以把它看作是绑定到y的值 调用第二个函数,x和y都被绑定,所以结果是 x和y的乘积在double函数体中定义。

创建double,即multiply序列中的第一个函数 函数求值以部分应用乘法。由此产生的 函数的名称为double。当计算double时,它使用 它的参数和部分应用值一起创建 结果。< / p >

我已经在另一个线程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

为了柯里林。

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

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

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链下的另一个函数。因此,如果你看到类似partial(2)(3)的东西,它实际上并不是部分应用。

进一步阅读:

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

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

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

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

plus :: (Int, Int) -> Int

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

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

(uncurry (+)) (1,2)

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

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

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

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

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

要了解它们的不同之处,最简单的方法是考虑真实的例子。假设我们有一个函数Add,它接受两个数字作为输入,并返回一个数字作为输出,例如Add(7, 5)返回12。在这种情况下:

  • 函数Add带有值7,将给我们一个新函数作为输出。该函数本身接受1个数字作为输入并输出一个数字。是这样的:

    Partial(Add, 7); // returns a function f2 as output
    
    
    // f2 takes 1 number as input and returns a number as output
    

    所以我们可以这样做:

    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
    

    所以我们可以这样做:

    f2 = Curry(Add);
    f3 = f2(7);
    f3(5); // returns 12
    

In other words, "currying" and "partial application" are two totally different functions. Currying takes exactly 1 input, whereas partial application takes 2 (or more) inputs.

Even though they both return a function as output, the returned functions are of totally different forms as demonstrated above.

我在这里可能大错特错,因为我在理论数学或函数式编程方面没有很强的背景,但从我对FP的短暂尝试来看,咖喱似乎倾向于将N个参数的函数转换为一个参数的N个函数,而部分应用(在实践中)更适合于参数数量不确定的变进函数。我知道之前回答中的一些例子与这个解释不符,但它对我区分概念最有帮助。看看下面这个例子(为了简洁起见,我用CoffeeScript写的,如果让你更困惑,我很抱歉,但如果需要,请要求澄清):

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

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

curry是一个带有一个参数的函数,它接受一个函数f并返回一个新函数h。注意,h接受来自X的参数,并返回一个函数,将Y映射到Z:

curry(f) = h
f: (X x Y) -> Z
h: X -> (Y -> Z)

部分应用是一个两个(或更多)参数函数,它接受函数f和一个或多个附加参数到f,并返回一个新函数g:

part(f, 2) = g
f: (X x Y) -> Z
g: Y -> Z

出现混淆是因为对于一个双参数函数,下面的等式成立:

partial(f, a) = curry(f)(a)

两边都将产生相同的单参数函数。

对于更高的函数,相等性不成立,因为在这种情况下,curry将返回一个单参数函数,而partial应用将返回一个多参数函数。

不同之处在于行为上,curry会递归地转换整个原始函数(每个参数一次),而局部应用只是一步替换。

来源:维基百科的鞭笞

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

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

简单的答案

咖喱:让你调用一个函数,把它分成多个调用,每次调用提供一个参数。

部分:让你调用一个函数,把它分成多个调用,每次调用提供多个参数。


简单的提示

这两种方法都允许您调用提供更少参数的函数(或者,更好的是,提供更多参数)。实际上,它们都(在每次调用时)将特定值绑定到函数的特定参数。

当函数有两个以上的参数时,可以看出真正的区别。


简单的e (c)(样本)

(在Javascript中)

我们想在不同的__abc1上运行下面的process函数(例如,假设我们的主题是"subject1""foobar"字符串):

function process(context, successCallback, errorCallback, subject) {...}

为什么总是传递参数,比如上下文和回调,如果它们总是一样的话?

只需为函数绑定一些值:

processSubject = _.partial(process, my_context, my_success, my_error)
// assign fixed values to the first 3 arguments of the `process` function

并在subject1foobar调用它,省略前3个参数的重复,使用:

processSubject('subject1');
processSubject('foobar');

很舒服,不是吗?😉


局部套用则需要每次传递一个参数

curriedProcess = _.curry(process);   // make the function curry-able
processWithBoundedContext = curriedProcess(my_context);
processWithCallbacks = processWithBoundedContext(my_success)(my_error); // note: these are two sequential calls


result1 = processWithCallbacks('subject1');
// same as: process(my_context, my_success, my_error, 'subject1');


result2 = processWithCallbacks('foobar');
// same as: process(my_context, my_success, my_error, 'foobar');

免责声明

我跳过了所有的学术/数学解释。因为我不知道。也许它有帮助🙃


编辑:

正如< >强@basickarl < / >强所添加的,这两个函数在使用上的另一个细微区别(参见Lodash的例子)是:

  • partial返回一个预先煮熟的函数,该函数可以用缺少的参数调用一次并返回最终结果;
  • curry 正在被多次调用(每个参数一次),每次返回一个预先煮熟的函数;除非使用最后一个实参调用,否则将返回所有实参处理的实际结果。

ES6:

这里是ECMAScript 6中即时curry和部分应用程序的简单的例子

const partialSum = math => (eng, geo) => math + eng + geo;
const curriedSum = math => eng => geo => math + eng + geo;

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

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

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

局部套用是当你定义函数时。

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

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

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

这里的很多人都没有正确地解决这个问题,也没有人谈论过重叠。

简单的答案

梳刷:让你调用一个函数,把它分成多个调用,每次调用提供一个参数。

部分应用程序:让你调用一个函数,把它分成多个调用,每次调用提供多个参数。

两者之间的一个显著区别是调用a 部分应用的函数立即返回结果,而不是另一个 沿着咖喱链;这种区别可以用例子来说明 显然,对于集度大于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是一种将带有多个参数的函数转换为带有单个参数的函数序列的技术。

例子

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


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

部分应用程序

文章Just Enough FP:部分应用

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

例子

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


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


add3(5) // 8

区别在于

  1. currying是一个技术(模式)
  2. partial application是一个带有一些预定义参数的函数(如前面例子中的add3)