我看过来自 什么是'Closure'? 的示例。
能否提供一个简单的例子说明何时使用闭包?
具体而言,闭包适用于哪些场景?
假设语言不支持闭包,如何实现类似的功能?
请勿冒犯任何人,请在像C#,Python,JavaScript,Ruby等语言中发布代码示例。
很抱歉,我还不了解函数式语言。
我看过来自 什么是'Closure'? 的示例。
能否提供一个简单的例子说明何时使用闭包?
具体而言,闭包适用于哪些场景?
假设语言不支持闭包,如何实现类似的功能?
请勿冒犯任何人,请在像C#,Python,JavaScript,Ruby等语言中发布代码示例。
很抱歉,我还不了解函数式语言。
Closures是非常好的工具。何时使用它们?任何你喜欢的时候...如已经提到的,另一种选择是编写一个类;例如,在C# 2.0之前,创建带参数线程真的很困难。有了C# 2.0,你甚至不需要`ParameterizedThreadStart',只需:
string name = // blah
int value = // blah
new Thread((ThreadStart)delegate { DoWork(name, value);}); // or inline if short
将其与创建具有名称和值的类进行比较
或者同样地,使用lambda搜索列表:
Person person = list.Find(x=>x.Age > minAge && x.Region == region);
再说一遍 - 另一种方法是编写一个具有两个属性和一个方法的类:
internal sealed class PersonFinder
{
public PersonFinder(int minAge, string region)
{
this.minAge = minAge;
this.region = region;
}
private readonly int minAge;
private readonly string region;
public bool IsMatch(Person person)
{
return person.Age > minAge && person.Region == region;
}
}
...
Person person = list.Find(new PersonFinder(minAge,region).IsMatch);
这在很大程度上类似于编译器在引擎盖下的操作(实际上,它使用public read/write字段而不是private readonly)。
C#捕获的最大注意事项是要注意作用域;例如:
for(int i = 0 ; i < 10 ; i++) {
ThreadPool.QueueUserWorkItem(delegate
{
Console.WriteLine(i);
});
}
由于变量 i 被用于每个循环,因此可能不会打印您期望的内容。您可以看到任何重复组合-甚至是10个10。在C#中,您需要仔细范围捕获变量:
for(int i = 0 ; i < 10 ; i++) {
int j = i;
ThreadPool.QueueUserWorkItem(delegate
{
Console.WriteLine(j);
});
}
每个j都会被单独捕获(即编译器会生成不同的类实例)。
Jon Skeet在他的博客文章中详细介绍了C#和Java闭包这里;或者想要更详细的信息,请参阅他的书C# in Depth,其中有一整章讲解闭包。
我同意之前回答中的"一直在用"。当你编写函数式语言或任何使用Lambda和闭包普遍的语言时,你甚至没有注意到它们的使用。就像问“函数的情境是什么?”或“循环的情境是什么?”这并不是为了让原始问题听起来愚蠢,而是要指出语言中的构造不是用特定情景来定义的,你只需一直使用它们,用于所有内容,这就像是本能一样。
这在某种程度上让人想起:
尊敬的Qc Na大师正带着他的学生Anton行走。 Anton希望借此促进与大师的谈话,便说:“大师,我听说对象非常好 - 是否属实?” Qc Na怜悯地看着他的学生,回答道:“愚蠢的学生 - 对象只是穷人的闭包。”
受到责备,Anton向他的导师请辞,并返回自己的单元格,专注于研究闭包。他仔细阅读了整个“Lambda: The Ultimate…”系列论文及其类似文章,并使用基于闭包的对象系统实现了一个小型Scheme解释器。他学到了很多,并期待着向他的导师汇报他的进展。
在与Qc Na的下一次散步中,Anton试图通过说:“大师,我认真研究了这个问题,现在明白对象确实是穷人的闭包。” 来给他的导师留下深刻印象。 Qc Na用棍子打了Anton一下,说:“你什么时候才能学会?闭包是穷人的对象。”那一刻,Anton茅塞顿开。
(http://people.csail.mit.edu/gregs/ll1-discuss-archive-html/msg03277.html)
使用闭包的最简单例子是柯里化。基本上,假设我们有一个函数f()
,当用两个参数a
和b
调用它时,会将它们相加。所以在Python中,我们有:
def f(a, b):
return a + b
但是,假设出于某种目的,我们只想一次调用一个参数的f()
函数。因此,我们需要将f(2, 3)
转换为f(2)(3)
。可以按照以下方式进行操作:
def f(a):
def g(b): # Function-within-a-function
return a + b # The value of a is present in the scope of g()
return g # f() returns a one-argument function g()
现在,当我们调用f(2)
时,我们会得到一个新的函数g()
;这个新的函数从f()
的作用域中携带变量,因此它被称为闭包。当我们调用g(3)
时,变量a
(由f
的定义绑定)被g()
访问,返回2 + 3 => 5
。
在几种情况下,这是非常有用的。例如,如果我有一个接受大量参数的函数,但只有其中一部分对我有用,我可以编写一个通用函数如下:
def many_arguments(a, b, c, d, e, f, g, h, i):
return # SOMETHING
def curry(function, **curry_args):
# call is a closure which closes over the environment of curry.
def call(*call_args):
# Call the function with both the curry args and the call args, returning
# the result.
return function(*call_args, **curry_args)
# Return the closure.
return call
useful_function = curry(many_arguments, a=1, b=2, c=3, d=4, e=5, f=6)
useful_function
现在是一个只需要3个参数的函数,而不是9个。我避免了重复自己,并且创建了一个通用的解决方案;如果我再写另一个多参数函数,我可以再次使用curry
工具。
通常,如果没有闭包的话,就必须定义一个类来携带与闭包环境相当的内容,并将其传递。
例如,在像Lisp这样的语言中,可以定义一个返回函数(具有封闭环境)以按照预定义数值添加其参数的函数,如下所示:
(defun make-adder (how-much)
(lambda (x)
(+ x how-much)))
然后像这样使用它:
cl-user(2): (make-adder 5)
#<Interpreted Closure (:internal make-adder) @ #x10009ef272>
cl-user(3): (funcall * 3) ; calls the function you just made with the argument '3'.
8
在一个没有闭包的语言中,你会像这样做:
public class Adder {
private int howMuch;
public Adder(int h) {
howMuch = h;
}
public int doAdd(int x) {
return x + howMuch;
}
}
然后像这样使用它:
Adder addFive = new Adder(5);
int addedFive = addFive.doAdd(3);
// addedFive is now 8.
闭包会隐式地携带其环境;您可以从执行部分(即lambda)内无缝地引用该环境。如果没有闭包,您必须显式地使该环境(environment)可用。
这应该向您解释了何时使用闭包:一直都要用。在支持闭包的语言中,大多数情况下,类被实例化以从计算的另一部分携带某些状态并在其他地方应用它的情况都可以优雅地用闭包来替代。
可以使用闭包实现一个对象系统。
def strseq(object, convert, join=joinseq):
"""Recursively walk a sequence, stringifying each element."""
if type(object) in (list, tuple):
return join(map(lambda o, c=convert, j=join: strseq(o, c, j), object))
else:
return convert(object)
这段代码有两个参数,一个是转换函数,另一个是连接函数,并且递归遍历列表和元组。递归使用map()实现,其中第一个参数是一个函数。由于Python在此之前不支持闭包,因此需要两个额外的默认参数将转换和连接传递到递归调用中。使用闭包可以改写为:
def strseq(object, convert, join=joinseq):
"""Recursively walk a sequence, stringifying each element."""
if type(object) in (list, tuple):
return join(map(lambda o: strseq(o, convert, join), object))
else:
return convert(object)
在面向对象的语言中,通常不会经常使用闭包,因为您可以使用对象来传递状态,当您的语言具有绑定方法时也可以使用它们。当Python没有闭包时,人们说Python使用对象模拟闭包,而Lisp使用闭包模拟对象。以下是来自IDLE(ClassBrowser.py)的示例:
class ClassBrowser: # shortened
def close(self, event=None):
self.top.destroy()
self.node.destroy()
def init(self, flist):
top.bind("<Escape>", self.close)
在这里,self.close是在按下Escape键时调用的无参回调函数。然而,close实现确实需要参数 - 即self,然后是self.top和self.node。如果Python没有绑定方法,你可以这样写:
class ClassBrowser:
def close(self, event=None):
self.top.destroy()
self.node.destroy()
def init(self, flist):
top.bind("<Escape>", lambda:self.close())
setColor()
函数的 button
和 color
参数的闭包:function setColor(button, color) {
button.addEventListener("click", function()
{
button.style.backgroundColor = color;
}, false);
}
window.onload = function() {
setColor(document.getElementById("StartButton"), "green");
setColor(document.getElementById("StopButton"), "red");
}
setColor()
函数退出后才创建的。我听说在Haskell中有更多的用途,但我只有在JavaScript中使用闭包的乐趣,并且在JavaScript中我并不太明白它的意义。我的第一反应是尖叫“哦不,又来了”,因为实现闭包必须要搞得一团糟。
但是当我了解了闭包是如何实现的(至少在JavaScript中),现在对我来说似乎并不那么糟糕,实现方式也有些优雅。
但从这个过程中我意识到,“闭包”并不是描述这个概念的最好词语。我认为它应该被称为“飞行作用域”。