闭包的确切定义是什么?

时间:2009-07-08 01:30:31

标签: functional-programming closures

我已经阅读了有关stackflow和其他来源的闭包的前几个主题,但有一件事让我感到困惑。从我能够拼凑起来的技术上来说,闭包只是包含函数代码和该函数中绑定变量值的数据集。

换句话说,从技术上讲,以下C函数应该是我理解的结果:

int count()
{
    static int x = 0;

    return x++;
}

然而我读到的所有内容似乎暗示闭包必须以某种方式将函数作为第一类对象传递。此外,通常似乎暗示闭包不是程序编程的一部分。这是一个解决方案与它解决的问题过度联系的情况,还是我误解了确切的定义?

8 个答案:

答案 0 :(得分:22)

不,那不是关闭。您的示例只是一个函数,它返回递增静态变量的结果。

以下是闭包的工作原理:

function makeCounter( int x )
{
  return int counter() {
    return x++;
  }
}

c = makeCounter( 3 );
printf( "%d" c() ); => 4
printf( "%d" c() ); => 5
d = makeCounter( 0 );
printf( "%d" d() ); => 1
printf( "%d" c() ); => 6

换句话说,makeCounter()的不同调用会产生不同的函数,它们在词汇环境中拥有自己的变量绑定,而这些变量已经“封闭”了。

编辑:我认为像这样的例子使得闭包比定义更容易理解,但是如果你想要一个定义我会说,“闭包是一个函数和一个环境的组合。环境包含定义的变量在函数中以及在函数创建时对函数可见的函数。只要函数存在,这些变量必须保持可用。“

答案 1 :(得分:12)

exact definition, I suggest looking at its Wikipedia entry。这特别好。我只想用一个例子来澄清它。

假设这个C#代码片段(应该在列表中执行AND搜索):

List<string> list = new List<string> { "hello world", "goodbye world" };
IEnumerable<string> filteredList = list;
var keywords = new [] { "hello", "world" };
foreach (var keyword in keywords)
    filteredList = filteredList.Where(item => item.Contains(keyword));

foreach (var s in filteredList)  // closure is called here
    Console.WriteLine(s);

在C#中做这样的事情是一个常见的陷阱。如果你看一下Where里面的lambda表达式,你会发现它定义了一个函数,它的行为取决于它定义站点上变量的值。这就像将变量本身传递给函数,而不是传递该变量的。实际上,当调用此闭包时,它会在那时检索keyword变量的值。这个样本的结果非常有趣。它打印出两个“hello world”和“goodbye world”,这不是我们想要的。发生了什么?如上所述,我们使用lambda表达式声明的函数是关于keyword 变量 的闭包,所以这就是:

filteredList = filteredList.Where(item => item.Contains(keyword))
                           .Where(item => item.Contains(keyword)); 

并且在关闭执行时,keyword具有值“world”,因此我们基本上使用相同的关键字过滤列表几次。解决方案是:

foreach (var keyword in keywords) {
    var temporaryVariable = keyword;
    filteredList = filteredList.Where(item => item.Contains(temporaryVariable));
}

由于temporaryVariable的范围限定为foreach循环的 body ,因此在每次迭代中,它都是一个不同的变量。实际上,每个闭包都将绑定到一个不同的变量(在每次迭代时都是temporaryVariable的不同实例)。这一次,它会给出正确的结果(“你好世界”):

filteredList = filteredList.Where(item => item.Contains(temporaryVariable_1))
                           .Where(item => item.Contains(temporaryVariable_2));

其中temporaryVariable_1的值为“hello”,而temporaryVariable_2在关闭执行时的值为“world”。

请注意,闭包导致了变量生命周期的延长(它们的生命应该在每次循环迭代后结束)。这也是封闭的重要副作用。

答案 2 :(得分:6)

据我所知,闭包还必须能够访问调用上下文中的变量。闭包通常与函数式编程相关联。语言可以包含来自不同类型的编程视角,功能,程序,命令,声明等的元素。它们的名称来自于在指定的上下文中被关闭。它们也可能具有词法绑定,因为它们可以使用在该上下文中使用的相同名称来引用指定的上下文。您的示例没有引用任何其他上下文,而是全局静态上下文。

来自Wikipedia

闭包关闭自由变量(不是局部变量的变量)

答案 3 :(得分:2)

闭包是一种用于表示具有本地状态的过程/函数的实现技术。 SICP中描述了实现闭包的一种方法。无论如何,我会提出它的要点。

所有表达式(包括函数)都在 environement 中进行评估,环境是一系列 frames 。框架将变量名称映射到值。每个框架也有一个 指向它的封闭环境。在新环境中评估函数,其中框架包含其参数的绑定。现在让我们看看以下有趣的场景。想象一下,我们有一个名为 accumulator 的函数,在评估时,它将返回另一个函数:

// This is some C like language that has first class functions and closures.
function accumulator(counter) {
    return (function() { return ++counter; });
}

当我们评估以下行时会发生什么?

accum1 = accumulator(0);

首先创建一个新环境,并在第一帧中将整数对象(对于计数器)绑定为0。返回的值是一个新函数,它绑定在全局环境中。通常新的环境将被垃圾收集一次功能 评估结束了。这不会发生。 accum1 持有对它的引用,因为它需要访问变量 counter 。当调用 accum1 时,它将在引用的环境中增加计数器的值。现在我们可以调用 accum1 一个具有本地状态或闭包的函数。

我在博客中描述了闭包的一些实际用法 http://vijaymathew.wordpress.com。 (见“危险设计”和“传递信息”)。

答案 4 :(得分:2)

已经有很多答案,但我会再增加一个人......

闭包不是函数式语言的唯一选择。例如,它们出现在Pascal(和family)中,它具有嵌套过程。标准C还没有它们,但是IIRC有一个GCC扩展。

基本问题是嵌套过程可能引用其父级中定义的变量。此外,父级可以将嵌套过程的引用返回给其调用者。

嵌套过程仍然引用父项本地的变量 - 特别是当执行函数引用的行时这些变量所具有的值 - 即使这些变量不再存在,因为父项已经退出。

如果永远不会从父级返回过程,则会出现此问题 - 对在不同时间构造的嵌套过程的不同引用可能使用相同变量的不同过去值。

对此的解决方案是,当引用嵌套函数时,它将打包在一个“闭包”中,其中包含稍后所需的变量值。

Python lambda是一个简单的函数式示例......

def parent () :
  a = "hello"
  return (lamda : a)

funcref = parent ()
print funcref ()

我的蟒蛇有点生疏,但我认为这是对的。关键是嵌套函数(lambda)仍然引用局部变量a的值,即使parent在调用时已经退出。该函数需要某处保存该值直到需要它,并且该位置称为闭包。

闭包有点像一组隐含的参数。

答案 5 :(得分:1)

好问题!鉴于OOP的OOP原则之一是对象具有行为和数据,闭包是一种特殊类型的对象,因为它们最重要的目的是它们的行为。那就是说,当我谈到他们的“行为”时,我的意思是什么?

(其中很多来自Dierk Konig的“Groovy in Action”,这是一本很棒的书)

在最简单的层面上,关闭实际上只是一些代码被包裹成为一个雌雄同体的对象/方法。这是一种方法,因为它可以使用参数并返回一个值,但它也是一个对象,因为你可以传递对它的引用。

用迪尔克的话来说,想象一下里面有一张纸的信封。一个典型的对象会在本文中写出变量及其值,但是闭包会有一个指令列表。让我们说这封信写着“把这封信和信寄给你的朋友。”

In Groovy: Closure envelope = { person -> new Letter(person).send() }
addressBookOfFriends.each (envelope)

闭包对象这里是包络变量的值,它的使用是它是每个方法的一个参数。

一些细节: 范围:闭包的范围是可以在其中访问的数据和成员。 从闭包返回:闭包通常使用回调机制来执行并从自身返回。 参数:如果闭包只需要1个参数,Groovy和其他langs提供一个默认名称:“it”,以使编码更快。 例如,在我们之前的例子中:

addressBookOfFriends.each (envelope) 
is the same as:
addressBookOfFriends.each { new Letter(it).send() }

希望这就是你要找的东西!

答案 6 :(得分:1)

对象是状态加功能。 闭包,是功能加状态。

函数f在关闭(捕获)x

时是一个闭包

答案 7 :(得分:0)

我认为彼得艾迪说得对,但这个例子可能会更有趣。您可以定义两个函数,这些函数关闭局部变量,递增和放大。减量。计数器将在这对功能之间共享,并且对它们是唯一的。如果定义一对新的递增/递减函数,它们将共享一个不同的计数器。

此外,您不需要传入x的初始值,您可以在功能块内将其默认为零。这样可以更清楚地知道它正在使用一个你不再能正常访问的值。

相关问题