47

我发现很多人交替使用闭包阻塞这两个词。这些人中的大多数都无法解释他们在说什么。

一些 Java 程序员(甚至来自非常昂贵的咨询公司的程序员)将匿名内部类称为“块”和“闭包”——但我知道这不是真的。(您不能从定义它们的方法的范围内传递可变变量......)

我在找:

  • 块的精确的计算机科学定义
  • 闭包的精确计算机科学定义
  • 澄清两者之间的区别。

我真的很想看到关于这些的链接、文章或书籍参考

4

6 回答 6

33

虽然只是一段可以由语句和声明组成的代码,但没有别的,闭包是一个真正的第一类对象,一个以块为值的真正变量。

主要区别在于块只是将指令组合在一起(例如while语句的主体),而闭包是一个包含一些可以执行的代码的变量。

如果你有一个闭包,通常你可以将它作为参数传递给函数,对它进行currify和decurrify,主要是调用它!

Closure c = { println 'Hello!' }
/* now you have an object that contains code */
c.call()

当然闭包更强大,它们是变量,可用于定义对象的自定义行为(而通常您必须在编程中使用接口或其他 OOP 方法)。

您可以将闭包视为一个函数,该函数包含该函数在其内部执行的操作。

块很有用,因为它们允许对变量进行范围界定。通常,当您在范围内定义变量时,您可以毫无问题地覆盖外部定义,并且新定义将仅在块执行期间存在。

for (int i = 0; i < 10; ++i)
{
     int t = i*2;
     printf("%d\r\n", t);
}

t在块(for语句的主体)内定义,并将在该块内持续存在。

于 2009-11-28T12:25:43.743 回答
19

块是语法上的东西 - 语句的逻辑单元(与范围相关而不是闭包)。

if (Condition) {
    // Block here 
} 
else {
    // Another block
}

闭包与匿名函数或类有关 - 匿名(函数)对象,一段绑定到环境(及其变量)的代码。

def foo() {
   var x = 0
   return () => { x += 1; return x }
}

这里foo返回一个闭包!局部变量x即使在foo终止后仍然存在于闭包中,并且可以通过调用返回的匿名函数来递增。

val counter = foo()
print counter() // Returns 2
print counter() // Return 3

请注意,这只是 Ruby 中块和闭包的处理方式类似,因为 Ruby 调用 block是一个闭包:

(1..10).each do |x|
    p x
end

那里each-method 被传递了一个闭包函数(带一个参数 x),它在 Ruby中被称为一个块。

于 2009-11-28T12:29:55.377 回答
7

这里有很多混乱,因为有多个定义的术语,以及多个不同的事物被混为一谈,因为它们通常是一起发现的。

首先,我们有“块”。这只是组成一个单元的词汇代码块——例如循环体。如果该语言实际上具有块范围,则可以定义仅存在于该代码块中的变量。

其次,我们将可调用代码作为值类型。在函数式语言中,这些是函数值 - 有时称为“乐趣”、“匿名函数”(因为函数是在值中找到的,而不是它分配给的名称;您不需要名称来调用它们)或“ lambdas”(来自用于在 Church 的 Lambda 演算中创建它们的运算符)。它们可能被称为“闭包”,但它们不会自动成为真正的闭包;为了符合条件,它们必须封装(“关闭”)围绕其创建的词法范围 - 也就是说,在函数本身范围之外但在其定义范围内定义的变量在调用函数时仍然可用,

例如,考虑这个 Javascript:

function makeClosure() {
  var x = "Remember me!";
  return function() {
    return "x='" + x + "'";
  }
}

// console.log(x); 
// The above is an error; x is undefined
var f = makeClosure();
console.log(f());
// The above outputs a string that includes x as it existed when f was created.

变量x仅在函数体内定义makeClosure;在该定义之外,它不存在。在我们调用之后makeClosure,它里面的x声明应该消失了。从大多数代码的角度来看,确实如此。但是返回的函数makeClosure是在存在时声明的x,所以当你以后调用它时它仍然可以访问它。这使它成为一个真正的闭包。

您可以拥有不是闭包的函数值,因为它们不保留范围。你也可以有部分关闭;PHP 的函数值仅保留在创建值时必须列出的特定变量。

您还可以拥有根本不代表整个函数的可调用代码值。Smalltalk 将这些称为“块闭包”,而 Ruby 将它们称为“procs”,尽管许多 Ruby 主义者只是将它们称为“块”,因为它们是由{...}do...end语法创建的内容的具体化版本。它们与 lambda(或“函数闭包”)的不同之处在于它们没有引入新的调用级别。 如果块闭包主体中的代码调用return,它将从外部函数/方法返回块闭包存在于其中,而不仅仅是块本身。

这种行为对于保留 RD Tennent 标记的“对应原则”至关重要,该原则指出您应该能够用包含该代码的内联函数替换任何代码并立即调用。例如,在 Javascript 中,您可以替换它:

x=2
console.log(x)

有了这个:

(function(){x = 2;})();
console.log(x)

这个例子不是很有趣,但是在不影响程序行为的情况下进行这种转换的能力在功能重构中起着关键作用。但是对于 lambdas,一旦你嵌入return了语句,这个原则就不再成立:

function foo1() {
  if (1) {
    return;
  }
  console.log("foo1: This should never run.")
}
foo1()
function foo2() {
  if (1) {
    (function() { return; })();
  }
  console.log("foo2: This should never run.")
}
foo2()

第二个功能与第一个不同;theconsole.log被执行,因为return唯一从匿名函数返回,而不是从foo2. 这打破了对应原则。

这就是为什么 Ruby 同时具有 procs 和 lambdas 的原因,尽管这种区别对于新手来说是一个常年困惑的根源。procs 和 lambdas 都是 class 的对象Proc,但它们的行为不同,如上所示:areturn只是从 lambda 的主体返回,但它从 proc 周围的方法返回。

def test
  p = proc do return 1 end
  l = lambda do return 1 end
  r = l[]
  puts "Called l, got #{r}, still here."
  r = p[]
  puts "Called p, got #{r}, still here?"
end

上面的test方法永远不会到达第二个puts,因为调用p会导致test立即返回(返回值为 1)。如果 Javascript 有块闭包,你可以做同样的事情,但它没有(尽管有添加它们的提议)。

于 2014-05-09T03:05:41.337 回答
2

那个响亮的、留着胡子的人对闭包和块有这样的说法:

http://martinfowler.com/bliki/Closure.html

有一次他说闭包是一个可以作为参数传递给方法的块。

于 2009-11-29T11:36:23.710 回答
1

尽管这些结构以前出现在 Algol、Smalltalk 和 Scheme 中,但现在您使用的术语在 Ruby 中最常一起使用。如果有的话,我会引用 Ruby 标准。

我不确定我能否回答您的确切问题,但我可以说明。如果您已经知道这一点,我很抱歉...

def f &x
  yield
  x
end

def g
  y = "block"
  t = f { p "I'm a #{y}" }
  y = "closure"
  t
end

t = g
t.call

和...

$ ruby exam.rb
"I'm a block"
"I'm a closure"
$ 

因此,块是附加到方法调用的类似匿名函数的代码序列。它被用于整个 Ruby API。当您使创建匿名函数变得足够容易时,事实证明它们对各种事情都很有用。

但请注意,在f返回,然后g返回之后,我们通过从f(as x) 和从g(as t) 返回来保留块。现在我们再次调用该块。再次注意,它g()已经返回。但是该块引用了不再存在的函数实例(和范围)中的局部变量?!它获得了新的价值y?!

所以闭包是一个类似函数的对象,在其词法范围内是封闭的。它们的实现非常具有挑战性,因为它们破坏了对函数调用实例中的局部变量非常有用的堆栈模型。


1. Ruby 有各种风格的类闭包函数对象;这只是其中之一。

于 2009-11-29T01:46:07.817 回答
0

5

那是一个整数

诠释 workDaysInAWeek = 5

这是一个整数变量,它可以设置为不同的整数。(如果情况阻止您修改该值,则可以将其称为常量。)

上述涉及数字,闭包涉及算法。闭包之间的区别也分别等同于上述。

于 2016-01-14T14:43:33.203 回答