groovy是否将部分应用程序称为“ currying”?


15

Groovy有一个称为“ currying”的概念。这是他们的Wiki中的一个示例:

def divide = { a, b -> a / b }

def halver = divide.rcurry(2)

assert halver(8) == 4

我对这里发生的事情的理解是,右边的参数divide绑定到了值2。这似乎是部分应用的一种形式。

术语currying通常用于将包含一系列参数的函数转换为仅包含一个参数并返回另一个函数的函数。例如,以下是curryHaskell 中的函数类型:

curry :: ((a, b) -> c) -> (a -> (b -> c))

对于谁没有用过Haskell的人ab并且c都是通用的参数。 curry接受一个带有两个参数的函数,然后返回一个函数,该函数接受a并返回一个b到的函数c。我为该类型添加了额外的一对括号,以使其更加清楚。

我是否误解了groovy示例中发生的事情,还是只是误命名了部分应用程序?或实际上是两者兼而有之:也就是说,将其转换divide为咖喱函数,然后部分地应用于2此新函数。


1
JK。

Answers:


14

Groovy的实现curry实际上在任何时候都不会咖喱,甚至是在幕后。它本质上与部分应用程序相同。

curryrcurry并且ncurry方法返回一个CurriedClosure对象持有的绑定参数。它也有一个方法getUncurriedArguments(名称不正确-您是咖喱函数,而不是参数),该方法返回传递给它的带有绑定参数的参数组成。

当关闭被调用,它最终调用invokeMethod方法MetaClassImpl,看看其中明确检查调用对象的实例CurriedClosure。如果是这样,它将使用上述内容getUncurriedArguments构成要应用的完整参数数组:

if (objectClass == CurriedClosure.class) {
    // ...
    final Object[] curriedArguments = cc.getUncurriedArguments(arguments);
    // [Ed: Yes, you read that right, curried = uncurried. :) ]
    // ...
    return ownerMetaClass.invokeMethod(owner, methodName, curriedArguments);
}

基于上述令人困惑且有些不一致的术语,我怀疑撰写此文章的人对概念有很好的理解,但也许有些着急,并且像许多聪明的人一样,将部分应用程序与匆忙混为一谈。如果有些不幸,这是可以理解的(请参阅保罗·金的答案)。在不破坏向后兼容性的情况下很难纠正此问题。

我建议的一种解决方案是重载该curry方法,以便在不传递任何参数的情况下进行真正的计算,并反对使用支持新partial功能的参数来调用该方法。这似乎有些奇怪,但是它将最大程度地实现向后兼容性(因为没有理由使用带有零参数的部分应用程序),同时避免了(IMHO)更为丑陋的情况,即使用新的名称不同的函数进行适当的计算,而该函数实际上named curry所做的事情与众不同且令人困惑。

curry毋庸置疑,通话结果与实际交易完全不同。如果确实使用了该函数,则可以编写:

def add = { x, y -> x + y }
def addCurried = add.curry()   // should work like { x -> { y -> x + y } }
def add1 = addCurried(1)       // should work like { y -> 1 + y }
assert add1(1) == 2 

…它会起作用,因为addCurried应该像那样起作用{ x -> { y -> x + y } }。相反,它引发了运行时异常,并且您在内部死了一些。


1
我认为参数> 2的函数上的rcurry和ncurry证明这确实只是部分应用程序而不是curry
jk。

@jk实际上,正如我在最后指出的那样,在带有参数== 2的函数上可以证明这一点。:)
乔丹·格雷

3
@matcauthon严格来说,currying的“目的”是将具有多个参数的函数转换为嵌套的函数链,每个函数具有一个参数。我认为您要问的是使用curring 的实际原因,在Groovy中比在LISP或Haskell中更难证明其合理性。关键是,您可能大部分时间想使用的是部分应用程序,而不是临时使用。
乔丹·格雷

4
+1代表and you die a little inside
Thomas Eding 2012年

1
哇,我明白在阅读您的答案后curring会更好:+1,谢谢!针对该问题,我喜欢您说的“将currying与部分应用程序相结合”。
GlenPeterson

3

我认为很明显,当考虑带有两个以上参数的函数时,常规的咖喱实际上是部分应用。考虑

f :: (a,b,c) -> d

它的咖喱形式是

fcurried :: a -> b -> c -> d

但是groovy的curry将返回等价的内容(假定使用1个参数x调用)

fgroovy :: (b,c) -> d 

它将以固定为x的值调用f

即,尽管groovy的curry可以返回带有N-1个参数的函数,但是curried函数只能正确地具有1个参数,因此groovy不能使用curry进行控制


2

Groovy从许多其他非纯FP语言中借用了其curry方法的命名方式,这些语言也对部分应用程序使用了类似的命名方式-也许不幸的是,这种以FP为中心的功能。有几种“真实的”可实现的实现被提议包含在Groovy中。这里有一个开始阅读它们的好线程:

http://groovy.markmail.org/thread/c4ycxdzm3ack6xxb

现有功能将保持某种形式,并且在调用新方法的名称等时将考虑向后兼容性-因此,在此阶段,我不能说新/旧方法的最终命名是什么是。可能在命名上有所妥协,但我们会看到。

对于大多数面向对象的程序员来说,两个术语(递归和部分应用)之间的区别在很大程度上可以说是学术上的。但是,一旦您习惯了它们(无论谁维护您的代码,都将受过训练,可以阅读这种编码风格),那么无点或隐式样式编程(“真正”的currying支持)将允许某些算法更紧凑地表达在某些情况下更优雅。这里显然有一些“情人眼中的美女”,但是能够支持这两种样式与Groovy的本质(OO / FP,静态/动态,类/脚本等)保持一致。


1

鉴于在IBM中发现的这一定义:

咖喱一词取自数学家Haskell Curry,他开发了部分函数的概念。咖喱化是指将多个参数带入具有多个参数的函数中,从而产生一个新函数,该函数将保留其余参数并返回结果。

halver是您的新(咖喱)函数(或闭包),现在只需一个参数。呼叫halver(10)将产生5。

因此,它在具有n-1个自变量的函数中转换具有n个自变量的函数。您的haskell示例也说明了咖喱的作用。


4
IBM的定义不正确。他们定义为currying的实际上是部分函数应用程序,它绑定(修复)函数的参数以使函数具有较小的Arity。Currying将一个带有多个参数的函数转换为一个带有一个参数的函数链。
乔丹·格雷

1
在Wikipedia的定义中,与IBM的定义相同:在数学和计算机科学中,currying是一种转换函数的技术,该函数采用多个自变量(或自变量的n个元组),可以将其称为每个具有单个参数的函数链(部分应用程序)。 Groovy确实将带有该函数(带有一个参数)的一个函数(带有两个参数rcurry)转换为一个函数(现在只有一个参数)。我将咖喱函数与基本函数的参数链接在一起,以得到结果函数。
matcauthon 2012年

3
没了维基百科的定义是不同的-部分应用程序是,当你调用咖喱功能-而不是当你定义它是什么呢常规
JK。

6
@jk是正确的。再次阅读Wikipedia的说明,您将看到返回的是一个带有一个参数的函数链,而不是一个带有n - 1参数的函数。参见答案末尾的示例。另请参见本文后面的内容,以了解有关区别的更多信息。en.wikipedia.org/wiki/…–
约旦·格雷

4
非常重要,请相信我。再次,我答案结尾的代码演示了正确实现的工作方式。一方面,它不需要任何争论。当前的实现应确实命名为例如partial
乔丹·格雷
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.