何时使用closures?
我已经看到closures的样本 – 什么是“closures”?
任何人都可以提供什么时候使用闭包的简单例子?
具体来说,closures有意义的场景?
假设语言没有closures支持,那么还会如何实现类似的事情呢?
不要冒犯任何人,请用c#,python,javascript,ruby等语言发布代码示例。
对不起,我还不懂function语言。
closures是非常好的工具。 何时使用它们? 任何时候你都喜欢……如前所述,另一种select是写一个class级; 例如,C#2.0之前,创build一个参数化的线程是一个真正的斗争。 在C#2.0中,你甚至不需要你只需要的`ParameterizedThreadStart':
string name = // blah int value = // blah new Thread((ThreadStart)delegate { DoWork(name, value);}); // or inline if short
比较一下,创build一个名称和值的类
或者同样search一个列表(这次使用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);
这与编译器在引擎盖下的工作方式相当 (实际上,它使用公共读/写字段,而不是专用的只读)。
C#捕捉的最大警告是观察范围; 例如:
for(int i = 0 ; i < 10 ; i++) { ThreadPool.QueueUserWorkItem(delegate { Console.WriteLine(i); }); }
这可能不会打印你所期望的,因为variables我被用于每个。 你可以看到重复的任何组合 – 甚至10 10。 你需要仔细的范围捕获的variables在C#中:
for(int i = 0 ; i < 10 ; i++) { int j = i; ThreadPool.QueueUserWorkItem(delegate { Console.WriteLine(j); }); }
这里每个j被分别捕获(即不同的编译器生成的类实例)。
Jon Skeet在这里有一个关于C#和javaclosures的好博客, 要了解更多细节,请参阅他的书C#深入 ,其中有一整章。
我同意以前的“所有时间”的答案。 当你使用函数式语言或任何lambdas和封闭常见的语言进行编程时,你甚至不会注意到它们。 这就像问“function的情况是什么?” 或者“循环的场景是什么?” 这并不是要使原来的问题听起来哑巴,而是要指出,有些语言中的结构不是根据特定的场景来定义的。 你只是一直用它们,一切都是第二天性的。
这在某种程度上让人联想到:
古老的主奎师那正和他的学生安东走在一起。 为了提醒主人进行讨论,安东说:“师父,我听说,物品是一件很好的事情,这是真的吗? Qa娜怜悯地看着他的学生,回答说:“愚蠢的学生 – 对象只不过是一个穷人的封闭”。
受到严厉惩罚,安东离开了他的主人,回到他的牢房,意图研究封锁。 他仔细阅读了整个“Lambda:The Ultimate …”系列论文及其堂兄弟,并实现了一个带有基于闭包的对象系统的小型Scheme解释器。 他学到了很多东西,并期待通知他的主人他的进步。
安东在下一次和齐娜走过时,试图给师傅留下深刻的印象,他说:“师父,我一直在认真研究这个问题,现在明白了那些东西确实是一个穷人的封闭。 Qc Na用棍子打了一下Anton,他回答说:“你什么时候学习?闭包是一个穷人的对象。 那一刻,安东开悟了。
( http://people.csail.mit.edu/gregs/ll1-discuss-archive-html/msg03277.html )
使用闭包最简单的例子就是所谓的currying。 基本上,我们假设我们有一个函数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()
范围内的variables,所以它被称为closures这些variables,因此closures这个术语。 当我们调用g(3)
,variablesa
(被f
的定义所约束)由g()
访问,返回2 + 3 => 5
这在几种情况下很有用。 例如,如果我有一个接受大量参数的函数,但是其中只有less数对我有用,我可以编写如下通用函数:
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
的function现在是一个函数,只需要3个参数,而不是9个。我不必重复自己,也创build了一个通用的解决scheme; 如果我写另一个多参数函数,我可以再次使用curry
工具。
通常情况下,如果没有closures,就必须定义一个类,并将其与封闭环境等效,并将其传递。
例如,在像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
在没有closures的语言中,你可以这样做:
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)内部无缝引用该环境。 没有closures,你必须使这个环境明确。
这应该向你解释什么时候你会使用闭包: 所有的时间 。 大多数情况下,一个类被实例化,从一个计算的另一部分携带一些状态,并将其应用到其他地方,而优雅地被支持它们的语言的闭包替代。
人们可以通过closures实现一个对象系统。
这是Python的标准库inspect.py的一个例子。 目前正在读取
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)
这有作为参数,一个转换函数和一个连接函数,recursion遍历列表和元组。 recursion是使用map()实现的,其中第一个参数是一个函数。 代码早于Python中对闭包的支持,所以需要两个额外的默认参数来传递convert和join到recursion调用中。 closures,这读取
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没有closures时,人们说Python会用对象来模拟closures,而Lisp则用closures来模拟对象。 作为来自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时调用的无参数callback函数。 然而,紧密的实现确实需要参数 – 即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())
在这里,lambda不是从参数中获得“self”,而是从上下文中获得。
在Lua和Python中,“仅仅编码”是非常自然的事情,因为当你引用不是参数的东西的时候,你正在closures。 (所以这些大部分将是相当枯燥的例子。)
至于具体情况,设想一个撤销/重做系统,其中步骤是(undo(),redo())closures对。 这样做的更麻烦的方法可能是:(a)使不可恢复的类有一个特殊的方法,使用通用的dorky参数,或者(b)UnReDoOperation的子类超过次数。
另一个具体的例子是无限列表:不用处理generics容器,而是使用一个检索下一个元素的函数。 (这是迭代器function的一部分)。在这种情况下,您可以保留一点状态(下一个整数,非整数列表或类似的列表)或者一个位置的引用实际的容器。 无论哪种方式,这是一个引用自身之外的东西的函数。 (在无限列表的情况下,状态variables必须是闭包variables,否则它们对于每个调用都是干净的)
我被告知在haskell中有更多的用途,但是我只有在javascript中使用闭包的乐趣,而在javascript中我并没有太多的看点。 我的第一本能就是尖叫“不,不是再一次”,为了让closures工作起到一定的作用。 在阅读了关于如何实现闭包的信息之后(不pipe怎么说),对我来说,现在看起来并不是那么糟糕,至less对我来说实现似乎有些优雅。
但是,从那里我意识到“封闭”并不是真正描述这个概念的最好的词。 我认为应该把它命名为“飞行范围”。
作为以前的答案笔记之一,你经常发现自己使用他们,几乎没有注意到你是。
一个恰当的例子就是它们在设置UI事件处理中非常常用,以获得代码重用,同时允许访问UI上下文。 下面是一个如何定义一个单击事件的匿名处理函数的例子,创build一个包含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()
函数退出之前实际上并没有创build闭包。
本文包括两个封闭实际有用的示例: Closure