柯里化和部分应用有什么区别?
我经常在互联网上看到各种各样的抱怨,其他民族的咖喱例子不是咖喱,而是实际上只是部分的应用。
我还没有find一个适当的部分应用程序的解释,或者它不同于咖喱。 似乎有一个普遍的混乱,相当的例子被描述为在一些地方curl,部分适用于其他地方。
有人能给我提供这两个术语的定义,以及它们有何不同的细节?
Currying将n个参数的单个函数转换为n个函数,每个函数都有一个参数。 鉴于以下function:
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);
许多function语言让你写fxyz
。 如果你只调用fxy
或者f(x)(y),那么你得到一个部分应用的函数 – 返回值是lambda(z){z(x(y))}
一个闭包,传入了x和y到f(x,y)
。
使用部分应用程序的一种方法是将函数定义为泛化函数的部分应用程序,如fold :
function fold(combineFunction, accumalator, 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(accumaltor,list) {/*...*/} f(0,list) //returns 10 @g = f(0) //same as sum g(list) //returns 10
看他们有什么不同的最简单的方法就是考虑一个真实的例子 。 让我们假设我们有一个函数Add
,它以2个数字作为input并返回一个数字作为输出,例如Add(7, 5)
返回12
。 在这种情况下:
-
部分应用function
Add
一个值7
将给我们一个新的function作为输出。 该函数本身需要1个数字作为input并输出一个数字。 因此: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函数
Add
会给我们一个新的函数作为输出。 该函数本身需要1个数字作为input,并输出另一个新的函数。 第三个函数然后将1个数字作为input并返回一个数字作为输出。 因此:Curry(Add); // returns a function f2 as output // f2 takes 1 number as input and returns a function f3 as output // ie f2(number) = f3 // f3 takes 1 number as input and returns a number as output // ie f3(number) = number
所以我们可以做到这一点:
f2 = Curry(Add); f3 = f2(7); f3(5); // returns 12
换句话说,“咖啡”和“部分应用”是两个完全不同的function。 Currying只需要1个input,而部分应用需要2个(或更多)input。
即使他们都返回一个函数作为输出,返回的函数是完全不同的forms,如上所示。
注意:这是从F#Basics中为.NET开发人员进入函数式编程的一篇极好的介绍性文章。
柯里意味着将具有许多参数的函数分解成一系列函数,每个函数都带有一个参数,并最终产生与原始函数相同的结果。 对于新开发函数式编程的开发人员来说,Currying可能是最具挑战性的话题,特别是因为它经常与部分应用程序混淆。 你可以在这个例子中看到两个工作:
let multiply xy = x * y let double = multiply 2 let ten = double 5
马上,你会看到与大多数命令式语言不同的行为。 第二个语句通过将一个parameter passing给一个带有两个参数的函数来创build一个名为double的新函数。 结果是一个函数,它接受一个int参数,并产生相同的输出,就好像你已经调用乘以x等于2,y等于该参数一样。 就行为而言,这和这段代码是一样的:
let double2 z = multiply 2 z
人们往往错误地说,繁殖是被压缩形成双重的。 但是这只是有点真实。 乘法函数是curried的,但是当它被定义时会发生这种情况,因为F#中的函数默认是curried的。 当创build双重function时,更确切地说,乘法function是部分应用的。
乘法函数实际上是一系列两个函数。 第一个函数接受一个int参数并返回另一个函数,将x有效地绑定到一个特定的值。 这个函数也接受一个int参数,你可以把它作为绑定到y的值。 在调用这个第二个函数之后,x和y都是绑定的,所以结果是double体中定义的x和y的乘积。
为了创build双精度,乘法函数链中的第一个函数被评估为部分应用乘法。 由此产生的函数名称为double。 当double被评估时,它使用它的参数和部分应用的值来创build结果。
有趣的问题。 经过一番search之后, “部分function应用程序不加curl”给了我最好的解释。 我不能说实际的区别对我来说特别明显,但是我不是FP专家。
另一个有用的页面(我承认我还没有完全阅读)是“用Java闭包的curl和部分应用程序” 。
看起来这是一个广泛混淆的术语,介意你。
我已经在另一个线程https://stackoverflow.com/a/12846865/1685865回答了这个问题。; 简而言之,部分函数应用是关于修复给定多variables函数的一些参数,以产生具有较less参数的另一个函数,而Currying则是关于将N个参数的函数转换为返回一元函数的一元函数。柯里里在这篇文章的末尾显示。]
Currying主要是理论上的兴趣:可以用一元函数表示计算(即每个函数都是一元的)。 在实践中,作为一种副产品,如果语言具有调用function,则这种技术可以使许多有用(但不是全部)的部分function应用变得微不足道。 同样,这不是实施部分申请的唯一手段。 所以你可能会遇到以其他方式进行部分申请的情况,但是人们却认为它是Currying。
(柯里的例子)
在实践中,不会只写
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
为了柯里。
通过以下JavaScript示例可以很好地说明咖喱和部分应用程序之间的区别:
function f(x, y, z) { return x + y + z; } var partial = f.bind(null, 1); 6 === partial(2, 3);
部分应用导致了较小的function; 在上面的例子中, f
有3个元素,而partial
元素只有2个元素。更重要的是,部分应用的函数会在调用的时候立刻返回结果 ,而不是在currying链中的另一个函数。 所以如果你看到partial(2)(3)
这样的东西,那不是现实中的部分应用。
进一步阅读:
- function编程5分钟
- 柯里:与部分function应用的对比
对于我来说,部分应用程序必须创build一个新的函数,使用的参数完全集成到结果函数中。
大多数函数式语言通过返回闭包来实现currying:部分应用时不要在lambda下进行评估。 因此,对于部分应用而言,有趣的是,我们需要在咖喱和部分应用之间做出区分,并将部分应用视为在lambda下的currying plus评估。
我可能在这里是非常错误的,因为我在理论math或函数式编程方面没有很强的背景,但是从我简短介绍到FP中,似乎currying将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
这显然是一个人为的例子,但是注意到部分应用一个接受任意数量参数的函数允许我们执行一个函数,但是有一些初步的数据。 curl函数是相似的,但是允许我们执行一个N参数函数,直到所有的N个参数都被计算出来。
再一次,这是我读过的东西。 如果有人不同意,我会感激评论为什么而不是立即downvote。 另外,如果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 ); }
所以currying为你提供了一个函数来创build函数,其中partial-application创build了一个包装函数来硬编码一个或多个参数。
如果你想复制和粘贴,以下是更嘈杂,但友好的工作,因为types更宽松:
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 ); }
我在学习的过程中遇到过很多这样的问题,并被多次问及。 我可以用最简单的方式来描述它们之间的区别,就是两者都是一样的:)让我来解释一下……有明显的区别。
部分申请和咖啡都涉及为一个function提供参数,可能不是一次全部。 一个相当规范的例子是添加两个数字。 在伪代码(实际上没有关键字的JS)中,基本函数可能如下:
add = (x, y) => x + y
如果我想要一个“addOne”函数,我可以部分应用它或咖喱它:
addOneC = curry(add, 1) addOneP = partial(add, 1)
现在使用它们是明确的:
addOneC(2) #=> 3 addOneP(2) #=> 3
那有什么区别? 好吧,这是微妙的,但部分应用涉及提供一些参数,返回的函数将在下一次调用时执行主函数,而currying将继续等待,直到它具有所有必要的参数:
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
简而言之,使用部分应用程序预先填充一些值,知道下一次调用该方法时,它将执行,留下未定义的所有未提供的参数; 当你想不断地返回一个部分应用的函数来根据需要执行函数签名时使用currying。 最后一个人为的例子是:
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
希望这可以帮助!
更新:一些语言或lib实现将允许你传递一个arity(最终评估中的参数总数)到部分应用程序的实现,这可能会混淆我的两个描述到一个混乱的混乱…但在这一点上,这两个技术是基本上可以互换。
在写这篇文章的时候,我把咖喱和不安分糊涂了。 它们是function的反转。 只要你得到了转换及其反转代表,你所称的是什么并不重要。
“不中断”的定义并不十分清楚(或者说,存在着“冲突”的定义,这些定义都体现了这个概念的精髓)。 基本上,这意味着将一个带有多个参数的函数转换为一个只有一个参数的函数。 例如,
(+) :: Int -> Int -> Int
现在,你如何将它变成一个只有一个参数的函数呢? 你当然是骗人!
plus :: (Int, Int) -> Int
注意,现在加上一个参数(这是由两个东西组成)。 超!
这是什么意思? 那么,如果你有一个带有两个参数的函数,并且你有一对参数,那么知道你可以将这个函数应用到参数上,并且仍然能够得到你所期望的是很好的。 事实上,做这件事的pipe道已经存在了,所以你不必像明确的模式匹配那样做事情。 你所要做的就是:
(uncurry (+)) (1,2)
那么什么是部分function应用? 将两个参数中的函数变成一个参数的函数是一种不同的方法。 它虽然工作不同。 再次以(+)为例。 我们怎样才能把它变成一个函数,把一个Int作为一个参数呢? 我们作弊!
((+) 0) :: Int -> Int
这是为任何Int增加零的函数。
((+) 1) :: Int -> Int
添加1到任何Int。 等等在每种情况下,(+)都是“部分应用”的。
Currying是一个函数f
的函数,它返回一个新函数h
:
curry(f) = h
部分应用程序是两个 (或更多)参数的函数,它接受一个函数f
和一个或多个附加参数给f
并返回一个新函数g
:
part(f, 2) = g
混淆的产生是因为在两个参数函数f
的情况下,下面的等式成立( 不适用于更高级的函数 ):
partial(f, a) = curry(f)(a)
双方将产生相同的一个参数function。
不同之处也在于行为,而柯里化recursion地转换整个原始函数(每个参数一次),部分应用只是一步replace,参见维基百科 。