11

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

MySum = lambda x, y : x + y;

现在我正在修复一个参数以获取一个具有较小参数的函数,该函数返回的值与我使用相同参数调用 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); }

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

4

5 回答 5

7

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

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 没有适当的闭包。)

柯里化是将 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 的重点。

于 2012-10-11T19:13:05.320 回答
5

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

换句话说,如果您有 function F(a, b),则应用部分应用 of 的函数a看起来像B(fn, a)where 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

于 2012-07-21T09:07:45.047 回答
2

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

于 2013-09-30T13:32:50.420 回答
1

闭包不是语言中必需的功能。我正在试验一种自制语言lambdatalk,其中 lambdas 不创建闭包,但接受部分应用程序。例如,这就是如何在 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 并返回一个等待 :m 的新 lambda。因此,即使闭包(和词法范围)是有用的功能,也没有必要。没有任何自由变量,超出任何词法范围,函数是真正的黑盒子,没有任何副作用,完全独立,遵循真正的函数范式。你不这么认为吗?

于 2017-02-18T12:58:29.863 回答
0

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

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

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

于 2012-07-21T09:05:20.787 回答