部分应用和关闭

时间:2012-07-21 08:57:30

标签: closures partial

我被问到部分功能应用程序和闭包之间的关系是什么。 我会说没有,除非我忽略了这一点。 假设我正在用python编写,我有一个非常简单的函数MySum定义如下:

MySum = lambda x, y : x + y;

现在我正在修复一个参数以获得一个较小arity的函数,如果我用相同的参数(部分应用程序)调用它,则返回MySum返回的相同值:

MyPartialSum = lambda x : MySum(x, 0);

我可以用C做同样的事情:

int MySum(int x, int y) { return x + y; }
int MyPartialSum(int x) { return MySum(x, 0); }

所以,愚蠢的问题是:有什么区别?为什么我需要关闭部分应用程序?这些代码是等价的,我不知道闭包和部分应用的界限是什么。

5 个答案:

答案 0 :(得分:4)

部分应用程序是一种技术,您可以使用现有函数及其参数的子集,并生成一个接受其余参数的新函数。

换句话说,如果您有函数F(a, b),则应用a的部分应用的函数看起来像B(fn, a) F(a, b) = B(F, a)(b)

在您的示例中,您只是创建新功能,而不是将部分应用程序应用于现有功能。

这是python中的一个例子:

def curry_first(fn, arg):
    def foo(*args):
       return fn(arg, *args)
    return foo

这会在提供的函数和参数上创建一个闭包。返回一个新函数,该函数使用新参数签名调用第一个函数。结束很重要 - 它允许fn访问arg。现在你可以做这样的事情:

add = lambda x, y : x + y;
add2 = curry_first(add, 2)
add2(4) # returns 6

我经常听到这被称为currying

答案 1 :(得分:1)

部分函数应用程序是关于修复给定函数的某些参数以产生具有较少参数的另一个函数,例如

sum = lambda x, y: x + y
inc = lambda x: sum(x, 1)

请注意,'inc'是部分应用的'sum',而不从上下文中捕获任何内容(如您所提到的那样)。

但是,这种手写(通常是匿名的)功能有点乏味。可以使用函数工厂,它返回一个内部函数。可以通过从上下文中捕获一些变量来参数化内部函数,例如

# sum = lambda x, y: x + y
def makePartialSumF(n):
    def partialSumF(x):
        return sum(x, n)
    return partialSumF

inc = makePartialSumF(1)
plusTwo = makePartialSumF(2)

这里调用工厂makePartialSumF两次。每次调用都会产生partialSumF函数(将不同的值捕获为n)。使用闭包使部分应用程序的实现变得方便。所以你可以说部分应用程序可以通过闭包来实现。当然,闭包可以做很多其他事情! (作为一个副节点,python没有正确的闭包。)

Currying是关于将N个参数的函数转换为返回一元函数的一元函数... 例如我们有一个函数,它接受三个参数并返回一个值:

sum = lambda x, y, z: x + y + z

咖喱版是

curriedSum = lambda x: lambda y: lambda z: x + y + z

我打赌你不会写那样的python代码。 IMO Currying 的动机主要是理论上的兴趣。 (仅使用一元函数表达计算的框架:每个函数都是一元的!)实际的副产品是,在函数被咖喱的语言中,一些部分应用程序(当你从左边'修复'参数时)与为咖喱功能提供论据一样微不足道。 (但并非所有部分应用程序都是如此。例如:给定f(x,y,z)= x + 2 * y + 3 * z,当你将y绑定到一个常量以产生两个变量的函数时。)所以你可以说,Currying是一种技术,在实践中和作为副产品,可以使许多有用的部分功能应用变得微不足道,但这不是Currying的重点。

答案 2 :(得分:1)

简单地说,部分应用程序的结果通常实现为闭包。

答案 3 :(得分:1)

闭包不是语言中的必需功能。我正在尝试一种自制语言lambdatalk,其中lambda不创建闭包但接受部分应用。例如,这是如何在SCHEME中定义集合[cons,car,cdr]:

(def cons (lambda (x y) (lambda (m) (m x y))))
(def car (lambda (z) (z (lambda (p q) p))))
(def cdr (lambda (z) (z (lambda (p q) q))))

(car (cons 12 34)) -> 12
(cdr (cons 12 34)) -> 34

和lambdatalk:

{def cons {lambda {:x :y :m} {:m :x :y}}} 
{def car {lambda {:z} {:z {lambda {:x :y} :x}}}}
{def cdr {lambda {:z} {:z {lambda {:x :y} :y}}}}

{car {cons 12 34}} -> 12
{cdr {cons 12 34}} -> 34

在SCHEME中,外部lambda将x和y保存在一个闭包中,内部lambda可以访问给定的m。在lambdatalk中,lambda保存:x和:y并返回一个等待的新lambda:m。因此,即使闭包(和词法范围)是有用的功能,也没有必要。没有任何自由变量,在任何词法范围之外,函数都是真正的黑盒子,没有任何副作用,完全独立,遵循真正的功能范例。你不这么认为吗?

答案 4 :(得分:0)

对我来说,以这种方式使用partialSum,确保你只依赖一个函数来对数字求和(MySum),如果出现问题,这将使调试变得更容易,因为你不必担心代码的逻辑在代码的两个不同部分。

如果您将来决定更改MySum的逻辑,(例如,让它返回x + y + 1),那么您将不必担心MyPartialSum,因为它调用了MySum

即使看起来很愚蠢,使用这种方式编写代码只是为了简化函数中的依赖过程。我相信你会在学习的后期注意到。